@seeed-studio/solution-cli 0.3.1 → 0.4.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.
Files changed (33) hide show
  1. package/README.md +157 -104
  2. package/bin/cli.js +410 -58
  3. package/bin/runner.js +1 -1
  4. package/catalog/client-config.json +2 -2
  5. package/catalog/knowledge-core.json +58452 -488
  6. package/docs/cli-guide.md +173 -200
  7. package/lib/agent.js +74 -15
  8. package/lib/catalog-dto.js +14 -1
  9. package/lib/detector.js +3 -3
  10. package/lib/doctor.js +19 -1
  11. package/lib/downloader.js +24 -0
  12. package/lib/gotchas.js +126 -0
  13. package/lib/installer.js +113 -19
  14. package/lib/integration.js +265 -0
  15. package/lib/knowledge.js +838 -100
  16. package/lib/ledger.js +1 -1
  17. package/package.json +3 -6
  18. package/skills/{sensecraft → solution-cli}/SKILL.md +125 -30
  19. package/skills/{sensecraft → solution-cli}/references/consult-and-select.md +61 -13
  20. package/skills/solution-cli/references/deploy.md +112 -0
  21. package/skills/solution-cli/references/device-management.md +298 -0
  22. package/skills/{sensecraft → solution-cli}/references/diagnose.md +15 -0
  23. package/skills/solution-cli/references/edge-gotchas.md +205 -0
  24. package/skills/solution-cli/references/generic-rk3588.md +106 -0
  25. package/skills/{sensecraft → solution-cli}/references/hardware-bom.md +9 -3
  26. package/skills/solution-cli/references/sales-feasibility.md +65 -0
  27. package/skills/{sensecraft → solution-cli}/references/secondary-development.md +10 -7
  28. package/skills/sensecraft/references/deploy.md +0 -300
  29. package/skills/sensecraft/references/device-management.md +0 -126
  30. /package/skills/{sensecraft → solution-cli}/references/app-only.md +0 -0
  31. /package/skills/{sensecraft → solution-cli}/references/error-catalog.md +0 -0
  32. /package/skills/{sensecraft → solution-cli}/references/offline.md +0 -0
  33. /package/skills/{sensecraft → solution-cli}/references/verify.md +0 -0
package/bin/cli.js CHANGED
@@ -2,10 +2,11 @@
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { runInstall, runStatus, runUninstall } from '../lib/installer.js';
5
+ import { runInstall, runUpdate, listAgents, runStatus, runUninstall } from '../lib/installer.js';
6
6
  import { runDoctor } from '../lib/doctor.js';
7
- import { searchHub, queryProductFamily, queryAccessories, createDeploymentLockfile } from '../lib/knowledge.js';
7
+ import { searchHub, listHub, getHubRecord, loadKnowledgeCore, queryProductFamily, queryAccessories, createDeploymentLockfile } from '../lib/knowledge.js';
8
8
  import { assertCompatibility } from '../lib/compatibility.js';
9
+ import { searchGotchas } from '../lib/gotchas.js';
9
10
 
10
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
12
  const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf8'));
@@ -15,41 +16,52 @@ const command = args[0] || 'install';
15
16
 
16
17
  function printHelp() {
17
18
  console.log(`
18
- SenseCraft Solution CLI & Skill Toolset (${pkg.name})
19
+ Solution CLI & Agent Skill Toolset (${pkg.name})
19
20
 
20
21
  Usage:
21
22
  npx @seeed-studio/solution-cli [command] [options]
22
23
  solution-cli [command] [options]
23
- sensecraft-solution-cli [command] [options]
24
24
 
25
25
  Commands:
26
26
  install Inspect environment, verify/download engine, and configure AI Agent skills (default)
27
+ update Check for Engine/catalog updates and synchronize all installed AI Agent skills
27
28
  status Display current engine, solution content, and agent skills status
28
29
  doctor Diagnose environment (platform, permissions, docker, PATH, ledger)
29
- uninstall Cleanly remove SenseCraft skills from specified agent(s)
30
+ agent Manage and list supported AI Agent skill destinations
31
+ Usage: solution-cli agent list [--json]
32
+ uninstall Cleanly remove Solution CLI skills from specified agent(s)
30
33
  hub Search scenarios and reference designs from Seeed Solution Hub
31
- Usage: solution-cli hub search <keyword> [--limit 5] [--locale zh|en] [--json]
34
+ Usage: solution-cli hub search <keyword> [--limit 5] [--locale zh|en|ja] [--json]
35
+ solution-cli hub list [--type scenario|tech|reference_design] [--page N] [--limit 50] [--locale zh|en|ja] [--json]
36
+ solution-cli hub get <id> [--type ...] [--section overview|links|description|architecture|products|deployment|playbook|integration] [--preset <id>] [--locale zh|en|ja] [--json]
32
37
  product Query product families, SKUs, and verified accessories
33
38
  Usage: solution-cli product query <family_id> [--locale zh|en] [--json]
34
- solution-cli product accessories <family_id> [--sku <sku>] [--json]
39
+ solution-cli product accessories <family_id> [--sku <sku>] [--category <cats>] [--compact] [--json]
35
40
  solution-cli product verify-compatibility --host <family_id> [--json]
41
+ gotchas Search edge hardware and chip troubleshooting knowledge (RK3588, reCamera, Jetson, Hailo)
42
+ Usage: solution-cli gotchas [keyword] [--chip rk3588|recamera|jetson|hailo] [--json]
36
43
  lockfile Create an immutable DeploymentLockfile to isolate deployment execution
37
44
  Usage: solution-cli lockfile create --solution <id> [--version <v>] [--hardware <fams>] [--json]
38
45
  help Show this help message
39
46
 
40
47
  Options:
41
- --agent <name|all> Target agent(s): workbuddy, claude, codex, codebuddy, or all
48
+ --agent <name|all> Target agent(s): workbuddy, doubao, claude, codex, codebuddy, antigravity, or all
49
+ --skills-dir <path> Install skill directly to custom directory (supports any third-party or proprietary Agent)
42
50
  -y, --yes Accept default detected selections non-interactively
43
51
  --dry-run Simulate operations without making changes to filesystem
44
52
  --skip-content Skip pulling IoT solution packages from CDN
45
53
  --force Re-download equal/older stable Engine and overwrite existing skill files, including local modifications; never downgrade a newer local Engine
46
54
  --json Output machine-readable JSON (supported by all query/diagnostic commands)
47
- --locale <zh|en> Language for hub and product results (default: zh)
55
+ --compact Output token-efficient minified response (omits verbose compatibility dimension trees)
56
+ --locale <zh|en|ja> Language for hub and product results (default: zh)
48
57
  --limit <num> Maximum results for search (default: 5)
49
58
  --engine-path <path> Use custom engine executable path
50
59
 
51
60
  Examples:
52
61
  npx @seeed-studio/solution-cli install
62
+ solution-cli update
63
+ solution-cli agent list
64
+ solution-cli install --skills-dir ~/.cursor/skills -y
53
65
  solution-cli hub search "跌倒"
54
66
  solution-cli product accessories recomputer_j40
55
67
  solution-cli product query sensecap_watcher --json
@@ -58,84 +70,297 @@ Examples:
58
70
  `);
59
71
  }
60
72
 
73
+ class CliArgumentError extends Error {
74
+ constructor(message) {
75
+ super(message);
76
+ this.name = 'CliArgumentError';
77
+ }
78
+ }
79
+
61
80
  function parseFlags(rawArgs) {
81
+ const usedFlags = new Set();
62
82
  const flags = {
63
83
  agent: null,
84
+ skillsDir: null,
64
85
  yes: false,
65
86
  dryRun: false,
66
87
  skipContent: false,
67
88
  force: false,
68
89
  json: false,
90
+ compact: false,
69
91
  enginePath: null,
70
92
  limit: 5,
93
+ page: 1,
94
+ type: null,
95
+ section: 'overview',
96
+ sections: null,
71
97
  locale: 'zh',
98
+ preset: null,
99
+ category: null,
100
+ chip: null,
72
101
  sku: null,
73
102
  host: null,
74
103
  accessory: null,
75
104
  solution: null,
76
105
  version: '1.0.0',
77
106
  hardware: null,
107
+ positionals: [],
108
+ usedFlags,
78
109
  };
79
110
 
80
111
  for (let i = 0; i < rawArgs.length; i++) {
81
112
  const a = rawArgs[i];
82
- if (a === '--agent' && rawArgs[i + 1]) {
83
- flags.agent = rawArgs[++i];
84
- } else if (a.startsWith('--agent=')) {
85
- flags.agent = a.split('=')[1];
113
+ const valueFlag = (name, setter) => {
114
+ const inlinePrefix = `${name}=`;
115
+ if (a.startsWith(inlinePrefix)) {
116
+ const value = a.slice(inlinePrefix.length);
117
+ if (!value) throw new CliArgumentError(`${name} requires a value`);
118
+ usedFlags.add(name);
119
+ setter(value);
120
+ return true;
121
+ }
122
+ if (a === name) {
123
+ const value = rawArgs[i + 1];
124
+ if (!value || value.startsWith('-')) {
125
+ throw new CliArgumentError(`${name} requires a value`);
126
+ }
127
+ usedFlags.add(name);
128
+ setter(value);
129
+ i += 1;
130
+ return true;
131
+ }
132
+ return false;
133
+ };
134
+
135
+ if (valueFlag('--agent', value => { flags.agent = value; })) {
136
+ } else if (valueFlag('--skills-dir', value => { flags.skillsDir = value; })) {
86
137
  } else if (a === '-y' || a === '--yes') {
138
+ usedFlags.add('--yes');
87
139
  flags.yes = true;
88
140
  } else if (a === '--dry-run') {
141
+ usedFlags.add('--dry-run');
89
142
  flags.dryRun = true;
90
143
  } else if (a === '--skip-content') {
144
+ usedFlags.add('--skip-content');
91
145
  flags.skipContent = true;
92
146
  } else if (a === '--force') {
147
+ usedFlags.add('--force');
93
148
  flags.force = true;
94
149
  } else if (a === '--json') {
150
+ usedFlags.add('--json');
95
151
  flags.json = true;
96
- } else if (a === '--engine-path' && rawArgs[i + 1]) {
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];
152
+ } else if (a === '--compact') {
153
+ usedFlags.add('--compact');
154
+ flags.compact = true;
155
+ } else if (valueFlag('--engine-path', value => { flags.enginePath = value; })) {
156
+ } else if (valueFlag('--limit', value => {
157
+ if (!/^[1-9]\d*$/.test(value)) throw new CliArgumentError('--limit requires a positive integer');
158
+ const limit = Number(value);
159
+ if (!Number.isSafeInteger(limit)) throw new CliArgumentError('--limit is too large');
160
+ flags.limit = limit;
161
+ })) {
162
+ } else if (valueFlag('--page', value => {
163
+ if (!/^[1-9]\d*$/.test(value)) throw new CliArgumentError('--page requires a positive integer');
164
+ const page = Number(value);
165
+ if (!Number.isSafeInteger(page)) throw new CliArgumentError('--page is too large');
166
+ flags.page = page;
167
+ })) {
168
+ } else if (valueFlag('--type', value => { flags.type = value; })) {
169
+ } else if (valueFlag('--section', value => { flags.section = value; })) {
170
+ } else if (valueFlag('--sections', value => { flags.sections = value; })) {
171
+ } else if (valueFlag('--preset', value => { flags.preset = value; })) {
172
+ } else if (valueFlag('--category', value => { flags.category = value; })) {
173
+ } else if (valueFlag('--chip', value => { flags.chip = value; })) {
174
+ } else if (valueFlag('--locale', value => { flags.locale = value; })) {
175
+ } else if (valueFlag('--sku', value => { flags.sku = value; })) {
176
+ } else if (valueFlag('--host', value => { flags.host = value; })) {
177
+ } else if (valueFlag('--accessory', value => { flags.accessory = value; })) {
178
+ } else if (valueFlag('--solution', value => { flags.solution = value; })) {
179
+ } else if (valueFlag('--version', value => { flags.version = value; })) {
180
+ } else if (valueFlag('--hardware', value => { flags.hardware = value; })) {
181
+ } else if (a.startsWith('-')) {
182
+ throw new CliArgumentError(`Unknown option "${a}"`);
183
+ } else {
184
+ flags.positionals.push(a);
114
185
  }
115
186
  }
116
187
 
117
188
  return flags;
118
189
  }
119
190
 
120
- async function main() {
121
- const flags = parseFlags(args.slice(1));
191
+ function validatePositionals(command, positionals) {
192
+ if (['install', 'update', 'uninstall', 'status', 'doctor'].includes(command) && positionals.length > 0) {
193
+ throw new CliArgumentError(`${command} does not accept positional arguments`);
194
+ }
195
+ if (command === 'agent') {
196
+ const sub = positionals[0] || 'list';
197
+ if (sub !== 'list') {
198
+ throw new CliArgumentError(`Unknown agent action "${sub}". Supported: list`);
199
+ }
200
+ }
201
+ }
122
202
 
123
- if (command === 'help' || command === '--help' || command === '-h') {
203
+ function validateCommandFlags(command, flags) {
204
+ const allowed = {
205
+ install: ['--agent', '--skills-dir', '--yes', '--dry-run', '--skip-content', '--force', '--engine-path'],
206
+ update: ['--agent', '--skills-dir', '--yes', '--dry-run', '--skip-content', '--force', '--engine-path'],
207
+ uninstall: ['--agent', '--dry-run', '--force'],
208
+ status: ['--json'],
209
+ doctor: ['--json'],
210
+ agent: ['--json'],
211
+ hub: ['--json', '--compact', '--locale', '--limit', '--page', '--type', '--section', '--sections', '--preset'],
212
+ product: ['--json', '--compact', '--locale', '--sku', '--host', '--accessory', '--category'],
213
+ gotchas: ['--json', '--chip'],
214
+ lockfile: ['--json', '--solution', '--version', '--hardware'],
215
+ help: [],
216
+ };
217
+ let supported = allowed[command];
218
+ if (command === 'hub') {
219
+ const sub = flags.positionals[0] || 'search';
220
+ const perSubcommand = {
221
+ search: ['--json', '--compact', '--locale', '--limit'],
222
+ list: ['--json', '--compact', '--locale', '--limit', '--page', '--type'],
223
+ get: ['--json', '--compact', '--locale', '--type', '--section', '--sections', '--preset'],
224
+ };
225
+ if (!perSubcommand[sub]) throw new CliArgumentError(`Unknown hub action "${sub}". Supported: search, list, get`);
226
+ supported = perSubcommand[sub];
227
+ }
228
+ if (!supported) return;
229
+ for (const flag of flags.usedFlags) {
230
+ if (!supported.includes(flag)) {
231
+ throw new CliArgumentError(`${flag} is not supported for ${command}`);
232
+ }
233
+ }
234
+ }
235
+
236
+ function renderSectionOutput(r, section) {
237
+ if (section === 'links') {
238
+ const landingEntries = Object.entries(r.links?.landing || {});
239
+ if (landingEntries.length) landingEntries.forEach(([locale, url]) => console.log(` Landing (${locale}): ${url}`));
240
+ else console.log(' Landing: unavailable');
241
+ console.log(` Wiki: ${r.links?.wiki || 'unavailable'}`);
242
+ console.log(` Repo: ${r.links?.repo || 'unavailable'}`);
243
+ console.log(` Declared documents: ${r.links?.declared_documents?.length ? r.links.declared_documents.join(', ') : 'unavailable'}`);
244
+ } else if (section === 'description') {
245
+ console.log(` Content locale: ${r.description?.content_locale || 'unknown'}`);
246
+ console.log(` Source role: ${r.description?.source_role || 'unavailable'}`);
247
+ console.log(` Source: ${r.description?.source || 'unavailable'}`);
248
+ if (r.description?.body) console.log(`\n${r.description.body}`);
249
+ else console.log(' Content: unavailable (the selected section has no body)');
250
+ } else if (section === 'architecture') {
251
+ console.log(` Content locale: ${r.architecture?.content_locale || 'unknown'}`);
252
+ console.log(` Fallback: ${r.architecture?.fallback ? 'yes' : 'no'}`);
253
+ console.log(` Source role: ${r.architecture?.source_role || 'unavailable'}`);
254
+ if (r.architecture?.source) console.log(` Source: ${r.architecture.source}`);
255
+ if (r.architecture?.intro) console.log(`\n${r.architecture.intro}`);
256
+ (r.architecture?.modules || []).forEach((m) => {
257
+ console.log(` ${m.label || m.id || 'Architecture'}:`);
258
+ if (m.card_image) console.log(` Image: ${m.card_image}`);
259
+ if (m.description) console.log(` ${m.description}`);
260
+ if (m.body) console.log(`\n${m.body}`);
261
+ });
262
+ (r.architecture?.gallery || []).forEach((g) => console.log(` ${g.role || 'Gallery'}: ${g.src}`));
263
+ if (!(r.architecture?.modules?.length || r.architecture?.gallery?.length)) console.log(' Content: unavailable (no architecture modules or gallery items)');
264
+ } else if (section === 'products') {
265
+ console.log(` Content locale: ${r.products_content_locale || 'unknown'}`);
266
+ console.log(` Source role: ${r.products_source_role || 'unavailable'}`);
267
+ if (!Object.keys(r.products || {}).length && !Object.keys(r.bom || {}).length) console.log(' Content: unavailable (no product or BOM entries)');
268
+ console.log(JSON.stringify({ products: r.products || {}, bom: r.bom || {} }, null, 2));
269
+ } else if (section === 'deployment') {
270
+ console.log(` Content locale: ${r.deployment?.content_locale || 'unknown'}`);
271
+ console.log(` Source role: ${r.deployment?.source_role || 'unavailable'}`);
272
+ console.log(` Deployable: ${r.deployment?.deployable ? 'yes' : 'no'}`);
273
+ console.log(` Solution: ${r.deployment?.solution_id || 'unavailable'}`);
274
+ if (r.deployment?.filtered_by_preset) {
275
+ console.log(` \x1b[32mFiltered by Preset:\x1b[0m ${r.deployment.filtered_by_preset}`);
276
+ }
277
+ console.log(` Presets: ${r.deployment?.presets?.length ? r.deployment.presets.map((p) => p.id || p.name || p).join(', ') : 'unavailable'}`);
278
+ console.log(` Guides: ${Object.keys(r.deployment?.guides || {}).length ? JSON.stringify(r.deployment.guides) : 'unavailable'}`);
279
+ console.log(` Source files: ${r.deployment?.source_files?.length ? r.deployment.source_files.join(', ') : 'unavailable'}`);
280
+ if (r.deployment?.guide?.body) {
281
+ console.log(` Guide locale: ${r.deployment.guide.content_locale}`);
282
+ console.log(`\n${r.deployment.guide.body}`);
283
+ } else console.log(' Content: unavailable (no deployment guide body returned)');
284
+ } else if (section === 'playbook') {
285
+ console.log(` \x1b[36m[现场工况诊断手册 (Diagnostic Playbook)]\x1b[0m`);
286
+ console.log(` 领域: ${r.playbook?.domain || r.title}`);
287
+ console.log(` 核心决断维度: ${(r.playbook?.core_dimensions || []).join(' | ')}`);
288
+ console.log('\n \x1b[33m[专家工况诊断追问 (2~3问)]\x1b[0m');
289
+ (r.playbook?.questions || []).forEach((q, qIdx) => {
290
+ console.log(` ${qIdx + 1}. \x1b[1m${q.question}\x1b[0m`);
291
+ if (q.rationale) console.log(` 💡 技术/商务决断理由: ${q.rationale}`);
292
+ });
293
+ } else if (section === 'integration') {
294
+ console.log(` \x1b[36m[系统集成与对接规范 (Integration Contract)]\x1b[0m`);
295
+ console.log(` 支持协议: ${(r.integration?.protocols || []).join(', ').toUpperCase()}`);
296
+ console.log('\n \x1b[33m[输入流与环境要求 (Inputs)]\x1b[0m');
297
+ if ((r.integration?.input_requirements || []).length === 0) {
298
+ console.log(' (根据具体选型接入标准 RTSP/传感器数据流)');
299
+ } else {
300
+ r.integration.input_requirements.forEach((inp) => console.log(` - [${inp.type?.toUpperCase()}] ${inp.description}`));
301
+ }
302
+ console.log('\n \x1b[32m[输出与数据总线 (Outputs)]\x1b[0m');
303
+ (r.integration?.interfaces || []).forEach((iface) => {
304
+ console.log(` - \x1b[1m[${iface.protocol?.toUpperCase()}]\x1b[0m ${iface.topic || iface.endpoint || ''} ${iface.port ? `(Port: ${iface.port})` : ''}`);
305
+ if (iface.description) console.log(` 说明: ${iface.description}`);
306
+ if (iface.payload_example) console.log(` 载荷示例: ${typeof iface.payload_example === 'string' ? iface.payload_example : JSON.stringify(iface.payload_example)}`);
307
+ });
308
+ if (r.integration?.system_ingestion_guide?.length) {
309
+ console.log('\n \x1b[35m[系统快速对接指引]\x1b[0m');
310
+ r.integration.system_ingestion_guide.forEach((g) => console.log(` ${g}`));
311
+ }
312
+ }
313
+ }
314
+
315
+ async function main() {
316
+ if (args.some(a => a === '--help' || a === '-h')) {
124
317
  printHelp();
125
- process.exit(0);
318
+ return;
126
319
  }
127
320
 
128
321
  if (command === '--version' || command === '-v') {
129
322
  console.log(pkg.version);
130
- process.exit(0);
323
+ return;
131
324
  }
132
325
 
326
+ let flags = { json: args.includes('--json') };
133
327
  try {
328
+ flags = parseFlags(args.slice(1));
329
+ validatePositionals(command, flags.positionals);
330
+ validateCommandFlags(command, flags);
331
+
332
+ if (command === 'help') {
333
+ printHelp();
334
+ return;
335
+ }
336
+
134
337
  switch (command) {
135
338
  case 'install':
136
339
  await runInstall(flags);
137
340
  break;
138
341
 
342
+ case 'update':
343
+ await runUpdate({ ...flags, yes: flags.usedFlags.has('--yes') ? flags.yes : true });
344
+ break;
345
+
346
+ case 'agent': {
347
+ const list = listAgents();
348
+ if (flags.json) {
349
+ console.log(JSON.stringify({ agents: list }, null, 2));
350
+ } else {
351
+ console.log('\n🤖 Supported AI Agent Targets:\n');
352
+ list.forEach((a, idx) => {
353
+ const detectedTag = a.detected ? '\x1b[32m[Detected]\x1b[0m' : '\x1b[90m[Not Detected]\x1b[0m';
354
+ const installTag = a.installed ? '\x1b[36m(Installed)\x1b[0m' : '\x1b[90m(Not Installed)\x1b[0m';
355
+ console.log(` ${idx + 1}. \x1b[1m${a.name}\x1b[0m (ID: ${a.id}) ${detectedTag} ${installTag}`);
356
+ console.log(` Skills Dir: ${a.skillsDir}`);
357
+ });
358
+ console.log('\n💡 Tip: To install into any custom or proprietary Agent, use:');
359
+ console.log(' solution-cli install --skills-dir <path> -y\n');
360
+ }
361
+ break;
362
+ }
363
+
139
364
  case 'status':
140
365
  runStatus(flags);
141
366
  break;
@@ -145,7 +370,7 @@ async function main() {
145
370
  if (flags.json) {
146
371
  console.log(JSON.stringify(report, null, 2));
147
372
  } else {
148
- console.log('\n🩺 SenseCraft Doctor Environment Report:\n');
373
+ console.log('\n🩺 Solution CLI Doctor Environment Report:\n');
149
374
  report.checks.forEach(c => {
150
375
  const symbol = c.status === 'ok' ? '\x1b[32m✔\x1b[0m' : c.status === 'warn' ? '\x1b[33m⚠️\x1b[0m' : '\x1b[31m✖\x1b[0m';
151
376
  console.log(` ${symbol} ${c.title}`);
@@ -161,33 +386,95 @@ async function main() {
161
386
  break;
162
387
 
163
388
  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>"`);
389
+ const sub = flags.positionals[0] || 'search';
390
+ if (!['search', 'list', 'get'].includes(sub)) {
391
+ console.error(`Unknown hub action "${sub}". Supported: search, list, get`);
167
392
  process.exit(1);
168
393
  }
169
- const query = args.slice(2).filter((a) => !a.startsWith('-')).join(' ');
170
- const dto = await searchHub({ query, locale: flags.locale, limit: flags.limit });
394
+ if (sub === 'list' && flags.positionals.length > 1) {
395
+ throw new CliArgumentError('hub list does not accept positional IDs; use --type, --page, and --limit');
396
+ }
397
+ if (sub === 'get' && (flags.positionals.length !== 2 || !flags.positionals[1])) {
398
+ throw new CliArgumentError('hub get requires exactly one record ID');
399
+ }
400
+ let dto;
401
+ if (sub === 'list') {
402
+ const listLimit = flags.usedFlags.has('--limit') ? flags.limit : 50;
403
+ dto = listHub({ type: flags.type, locale: flags.locale, page: flags.page, limit: listLimit });
404
+ } else if (sub === 'get') {
405
+ const row = getHubRecord(flags.positionals[1], {
406
+ type: flags.type,
407
+ locale: flags.locale,
408
+ section: flags.section,
409
+ sections: flags.sections,
410
+ preset: flags.preset,
411
+ });
412
+ if (!row) {
413
+ console.error(`Hub record "${flags.positionals[1]}" not found in the bundled catalog.`);
414
+ process.exit(1);
415
+ }
416
+ const core = loadKnowledgeCore();
417
+ dto = {
418
+ records: [row],
419
+ source: 'bundled_manifest',
420
+ freshness: 'UNKNOWN',
421
+ dataset_version: core.dataset_version || null,
422
+ build_time: core.build_time || null,
423
+ provenance: core.provenance || null,
424
+ scope: 'bundled_catalog_snapshot',
425
+ locale: flags.locale,
426
+ warnings: [],
427
+ };
428
+ } else {
429
+ const query = flags.positionals.slice(1).join(' ');
430
+ dto = await searchHub({ query, locale: flags.locale, limit: flags.limit });
431
+ }
171
432
  if (flags.json) {
172
433
  console.log(JSON.stringify(dto, null, 2));
173
434
  } else {
174
- console.log(`\n🔍 Solutions Hub Search Results for "${query}" (${dto.source}):\n`);
435
+ console.log(`\n🔍 Solutions Hub ${sub} (${dto.source}):\n`);
175
436
  if (dto.records.length === 0) {
176
437
  console.log(' No matching scenarios or reference designs found.');
177
438
  } else {
178
439
  dto.records.forEach((r, idx) => {
179
- const tag = r.type === 'scenario' ? '\x1b[36m[Scenario]\x1b[0m' : '\x1b[35m[Reference Design]\x1b[0m';
440
+ const tag = r.type === 'tech' ? '\x1b[34m[Tech]\x1b[0m' : r.type === 'scenario' ? '\x1b[36m[Scenario]\x1b[0m' : '\x1b[35m[Reference Design]\x1b[0m';
180
441
  console.log(` ${idx + 1}. ${tag} \x1b[1m${r.title}\x1b[0m (${r.id})`);
442
+ if (sub === 'get') {
443
+ const activeSections = r.sections || (r.section ? [r.section] : ['overview']);
444
+ activeSections.forEach((sec) => {
445
+ renderSectionOutput(r, sec);
446
+ });
447
+ if (activeSections.includes('overview') && r.available_sections?.length) {
448
+ console.log('\n \x1b[36m[Available Sections & Status 渐进式目录]:\x1b[0m');
449
+ r.available_sections.forEach((sec) => {
450
+ const status = r.section_status?.[sec] || 'documented';
451
+ const isCurrent = sec === 'overview' ? ' \x1b[32m[Current]\x1b[0m' : '';
452
+ const cmd = sec === 'overview' ? `hub get ${r.id}` : `hub get ${r.id} --section ${sec}${sec === 'deployment' ? ' [--preset <id>]' : ''}`;
453
+ console.log(` - \x1b[1m${sec}\x1b[0m (${status}): ${cmd}${isCurrent}`);
454
+ });
455
+ console.log(' 💡 批量拉取: hub get ' + r.id + ' --sections architecture,integration');
456
+ }
457
+ }
181
458
  if (r.summary) console.log(` ${r.summary}`);
182
- if (r.reference_designs?.length) console.log(` 对应参考设计: ${r.reference_designs.join(', ')}`);
459
+ if (r.architecture_preview?.image_url) {
460
+ console.log(` 官方架构图: ${r.architecture_preview.image_url}`);
461
+ if (r.architecture_preview.summary) console.log(` 架构拓扑: ${r.architecture_preview.summary}`);
462
+ }
463
+ if (r.landing_url) console.log(` Landing: ${r.landing_url}`);
464
+ console.log(` Scenario IDs: ${r.scenario_ids?.length ? r.scenario_ids.join(', ') : 'none'}`);
465
+ console.log(` Tech IDs: ${r.technology_ids?.length ? r.technology_ids.join(', ') : 'none'}`);
466
+ console.log(` Reference Design IDs: ${r.reference_designs?.length ? r.reference_designs.join(', ') : 'none'}`);
467
+ if (r.solution_id) console.log(` 可部署内容: ${r.solution_id}`);
183
468
  if (r.family_ids?.length) console.log(` 推荐硬件族: ${r.family_ids.join(', ')}`);
184
469
  if (r.presets?.length) console.log(` 预设模式: ${r.presets.join(', ')}`);
185
470
  if (r.wiki_url) console.log(` 开发 Wiki: ${r.wiki_url}`);
186
- if (r.diagnostic_playbook?.questions?.length) {
187
- console.log(' 现场工况诊断提问指引:');
188
- r.diagnostic_playbook.questions.forEach((q) => {
189
- console.log(` • ${q.question} \x1b[90m(决断考量: ${q.rationale})\x1b[0m`);
190
- });
471
+ if (r.has_playbook) console.log(' \x1b[32m现场工况诊断:\x1b[0m 可用 (使用 hub get ' + r.id + ' --section playbook 查看专业追问)');
472
+ if (r.available_sections?.length) {
473
+ const sectionSummary = r.available_sections.map((s) => {
474
+ const st = r.section_status?.[s];
475
+ return st && st !== 'documented' ? `${s}(${st})` : s;
476
+ }).join(', ');
477
+ console.log(` Available Sections: ${sectionSummary}`);
191
478
  }
192
479
  console.log('');
193
480
  });
@@ -195,13 +482,21 @@ async function main() {
195
482
  if (dto.warnings.length) {
196
483
  dto.warnings.forEach((w) => console.log(`\x1b[33mNote:\x1b[0m ${w}`));
197
484
  }
485
+ if (sub === 'list' && dto.pagination) {
486
+ const next = dto.pagination.next_page;
487
+ console.log(`\nCatalog snapshot: total=${dto.pagination.total} returned=${dto.pagination.returned} has_more=${dto.pagination.has_more} next_page=${next ?? 'none'}`);
488
+ if (dto.pagination.has_more && next) {
489
+ const typeArg = flags.type ? ` --type ${flags.type}` : '';
490
+ console.log(`Next page: solution-cli hub list --locale ${flags.locale} --page ${next} --limit ${dto.pagination.limit}${typeArg}`);
491
+ }
492
+ }
198
493
  }
199
494
  break;
200
495
  }
201
496
 
202
497
  case 'product': {
203
- const sub = args[1] || 'query';
204
- const familyId = args.slice(2).find((a) => !a.startsWith('-')) || flags.host;
498
+ const sub = flags.positionals[0] || 'query';
499
+ const familyId = flags.positionals[1] || flags.host;
205
500
 
206
501
  if (sub === 'query') {
207
502
  if (!familyId) {
@@ -233,7 +528,12 @@ async function main() {
233
528
  console.error('Please specify a product family ID (e.g. "solution-cli product accessories recomputer_j40")');
234
529
  process.exit(1);
235
530
  }
236
- const dto = queryAccessories(familyId, { sku: flags.sku, locale: flags.locale });
531
+ const dto = queryAccessories(familyId, {
532
+ sku: flags.sku,
533
+ locale: flags.locale,
534
+ compact: flags.compact,
535
+ category: flags.category,
536
+ });
237
537
  if (flags.json) {
238
538
  console.log(JSON.stringify(dto, null, 2));
239
539
  } else {
@@ -242,6 +542,9 @@ async function main() {
242
542
  console.log(`Product family "${familyId}" not found.`);
243
543
  } else {
244
544
  console.log(`\n📦 Verified Accessories for \x1b[1m${record.host_title}\x1b[0m (${record.family_id}):`);
545
+ if (record.filtered_by_category) {
546
+ console.log(` \x1b[32m[Filtered by category]:\x1b[0m ${record.filtered_by_category}`);
547
+ }
245
548
  console.log(` Host Default SKU: ${record.host_sku}`);
246
549
  if (record.physical_spec?.power_input) {
247
550
  const p = record.physical_spec.power_input;
@@ -250,7 +553,7 @@ async function main() {
250
553
 
251
554
  console.log('\n \x1b[33m[REQUIRED (必需配件)]\x1b[0m');
252
555
  if (record.accessories.required.length === 0) {
253
- console.log(' (无强制单独采购的配件)');
556
+ console.log(' (无符合条件的必需配件)');
254
557
  } else {
255
558
  record.accessories.required.forEach((acc) => {
256
559
  const stat = acc.compatibility.is_compatible ? '\x1b[32m✔\x1b[0m' : '\x1b[31m✖\x1b[0m';
@@ -262,7 +565,7 @@ async function main() {
262
565
 
263
566
  console.log('\n \x1b[36m[OPTIONAL (推荐选配)]\x1b[0m');
264
567
  if (record.accessories.optional.length === 0) {
265
- console.log(' (无选配项)');
568
+ console.log(' (无符合条件的选配项)');
266
569
  } else {
267
570
  record.accessories.optional.slice(0, 10).forEach((acc) => {
268
571
  const stat = acc.compatibility.is_compatible ? '\x1b[32m✔\x1b[0m' : '\x1b[31m✖\x1b[0m';
@@ -270,6 +573,16 @@ async function main() {
270
573
  if (acc.url) console.log(` 购买链接: ${acc.url}`);
271
574
  });
272
575
  }
576
+
577
+ if (record.bundle_cart_url) {
578
+ console.log('\n \x1b[35m[一键批量加购 (Bundle Cart)]\x1b[0m');
579
+ console.log(` 主机与必需配件已打包,点击直达购物车一键结算:`);
580
+ console.log(` \x1b[4m${record.bundle_cart_url}\x1b[0m`);
581
+ } else if (record.domestic_channel_note) {
582
+ console.log('\n \x1b[35m[国内采购渠道说明]\x1b[0m');
583
+ console.log(` ${record.domestic_channel_note}`);
584
+ }
585
+
273
586
  console.log('');
274
587
  }
275
588
  }
@@ -295,8 +608,37 @@ async function main() {
295
608
  break;
296
609
  }
297
610
 
611
+ case 'gotchas': {
612
+ const query = flags.positionals.join(' ');
613
+ const results = searchGotchas({ query, chip: flags.chip });
614
+ if (flags.json) {
615
+ console.log(JSON.stringify({
616
+ total: results.length,
617
+ query: query || null,
618
+ chip: flags.chip || null,
619
+ records: results,
620
+ }, null, 2));
621
+ } else {
622
+ console.log(`\n🩺 Edge Hardware Gotchas & Troubleshooting (${results.length} found):\n`);
623
+ if (results.length === 0) {
624
+ console.log(' No matching troubleshooting cards found.');
625
+ } else {
626
+ results.forEach((rec, idx) => {
627
+ console.log(` ${idx + 1}. \x1b[1m[${rec.chip.toUpperCase()}] ${rec.title}\x1b[0m`);
628
+ console.log(` 现象 (Symptom): ${rec.symptoms}`);
629
+ console.log(` 根因 (Root Cause): ${rec.root_cause}`);
630
+ console.log(' \x1b[32m解决方案:\x1b[0m');
631
+ rec.solution.forEach((s) => console.log(` ${s}`));
632
+ if (rec.forbidden) console.log(` \x1b[31m⚠️ 严禁行为:\x1b[0m ${rec.forbidden}`);
633
+ console.log('');
634
+ });
635
+ }
636
+ }
637
+ break;
638
+ }
639
+
298
640
  case 'lockfile': {
299
- const sub = args[1] || 'create';
641
+ const sub = flags.positionals[0] || 'create';
300
642
  if (sub !== 'create') {
301
643
  console.error(`Unknown lockfile action "${sub}". Supported: "solution-cli lockfile create"`);
302
644
  process.exit(1);
@@ -325,8 +667,18 @@ async function main() {
325
667
  process.exit(1);
326
668
  }
327
669
  } catch (err) {
328
- console.error(`\n\x1b[31mError:\x1b[0m ${err.message}\n`);
329
- process.exit(1);
670
+ const isSectionError = err?.name === 'HubSectionError';
671
+ const isArgError = err instanceof CliArgumentError || isSectionError;
672
+ if (flags?.json) {
673
+ console.log(JSON.stringify({
674
+ error: err.message,
675
+ ...(err.suggestion ? { suggestion: err.suggestion } : {}),
676
+ ...(err.allowed_sections ? { allowed_sections: err.allowed_sections } : {}),
677
+ }, null, 2));
678
+ } else {
679
+ console.error(`\n\x1b[31mError:\x1b[0m ${err.message}\n`);
680
+ }
681
+ process.exitCode = isArgError ? 2 : 1;
330
682
  }
331
683
  }
332
684