@seeed-studio/solution-cli 0.3.0 → 0.3.2

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 CHANGED
@@ -10,26 +10,46 @@ Compatible with **Claude Code, Codex, Google Antigravity (AGY), Work Buddy, and
10
10
 
11
11
  ## 1. Quick Start & Agent Installation
12
12
 
13
- Install the underlying engine and configure the AI Agent Skill with a single command:
13
+ Install the underlying engine and configure the AI Agent Skill with one command:
14
14
 
15
15
  ```bash
16
- # Interactive mode (auto-scans local agent environments and prompts for selection)
17
- npx @seeed-studio/solution-cli install
16
+ npx -y @seeed-studio/solution-cli@latest install --agent workbuddy -y
17
+ ```
18
18
 
19
- # Non-interactive mode (e.g. install to Claude Code and Antigravity)
20
- npx @seeed-studio/solution-cli install --agent claude,antigravity -y
19
+ This example targets WorkBuddy explicitly. Replace `workbuddy` with the current
20
+ Agent ID for another host. Use `npx -y @seeed-studio/solution-cli@latest install`
21
+ for the interactive form: when one or more Agents are detected, pressing Enter
22
+ selects all detected Agents, while entering the corresponding number selects one
23
+ host. In non-interactive mode, `install -y` without `--agent` also selects all
24
+ detected Agents when any are detected. If none are detected, these defaults still
25
+ use the CLI's default target set; pass `--agent <agent-id>` to control the scope
26
+ explicitly.
27
+
28
+ The same command is safe to repeat. It checks the signed stable Engine release,
29
+ reuses an equal or newer local Engine, and upgrades an older one. Existing
30
+ modified or unregistered skills and protected symlinks are preserved. Use
31
+ `--agent claude,antigravity` to select agents, or `--engine-path <path>` to pin
32
+ a valid Engine binary. `--force` re-downloads an equal or older Engine and
33
+ overwrites existing skill files, including user modifications; use it only when
34
+ that overwrite is intended. Protected external symlinks are not followed, and
35
+ it never downgrades a newer discovered Engine.
36
+
37
+ Automatic reuse/upgrade rules start with publicly published solution-cli 0.3.1.
38
+ A latest release or signature failure stops before changing the
39
+ Engine or skills; an offline host must use a valid explicit local Engine path.
40
+
41
+ The interactive form remains available when prompts are wanted:
21
42
 
22
- # Install globally
23
- npm install -g @seeed-studio/solution-cli
24
- solution-cli install -y
43
+ ```bash
44
+ npx -y @seeed-studio/solution-cli@latest install
25
45
  ```
26
46
 
27
47
  ### Supported AI Agent Harnesses
28
- - **Claude Code**: `~/.claude/skills/sensecraft`
29
- - **Codex / OpenAI**: `~/.agents/skills/sensecraft` or `~/.codex/skills/sensecraft`
30
- - **Google Antigravity (AGY)**: `~/.gemini/config/skills/sensecraft`
31
- - **Work Buddy / Workbody**: `~/.workbuddy/skills/sensecraft`
32
- - **CodeBuddy**: `~/.codebuddy/skills/sensecraft`
48
+ - **Claude Code**: `~/.claude/skills/solution-cli`
49
+ - **Codex / OpenAI**: `~/.agents/skills/solution-cli` or `~/.codex/skills/solution-cli`
50
+ - **Google Antigravity (AGY)**: `~/.gemini/config/skills/solution-cli`
51
+ - **Work Buddy / Workbody**: `~/.workbuddy/skills/solution-cli`
52
+ - **CodeBuddy**: `~/.codebuddy/skills/solution-cli`
33
53
 
34
54
  ---
35
55
 
@@ -45,7 +65,7 @@ The `solution-cli` provides unified commands for discovery, BOM calculation, dev
45
65
  | `solution-cli install [--agent <agents>] [-y]` | Inspect host environment, download/verify engine, and install Agent skills. |
46
66
  | `solution-cli status` | Inspect installed engine binary, content cache, and configured agent skills. |
47
67
  | `solution-cli doctor` | Run pre-flight diagnostics (OS, Docker daemon, serial permissions, PATH, ledger). |
48
- | `solution-cli uninstall [--agent <agents>]` | Cleanly remove SenseCraft skills using the installation ledger. |
68
+ | `solution-cli uninstall [--agent <agents>]` | Cleanly remove Solution CLI skills using the installation ledger. |
49
69
  | `sensecraft-engine <subcommand>` | Direct proxy to the underlying high-performance Rust execution engine. |
50
70
 
51
71
  ---
@@ -110,7 +130,7 @@ Once installed, your AI Agent gains deep domain knowledge as an Edge IoT Solutio
110
130
  > 1. 提供方案对应的官方 GitHub 开源仓库地址;
111
131
  > 2. 指导用户克隆到本地工作区;
112
132
  > 3. 指导通过 TensorRT `trtexec` 完成端侧引擎转换,并在 `docker-compose.override.yml` 中挂载自定义模型;
113
- > 4. 使用 `sensecraft-engine deploy --solutions-dir ./solutions` 进行本地快速调测。
133
+ > 4. 使用 `sensecraft-engine deploy <id> --solutions-dir ./solutions` 进行本地快速调测。
114
134
 
115
135
  ---
116
136
 
package/bin/cli.js CHANGED
@@ -15,18 +15,17 @@ const command = args[0] || 'install';
15
15
 
16
16
  function printHelp() {
17
17
  console.log(`
18
- SenseCraft Solution CLI & Skill Toolset (${pkg.name})
18
+ Solution CLI & Agent Skill Toolset (${pkg.name})
19
19
 
20
20
  Usage:
21
21
  npx @seeed-studio/solution-cli [command] [options]
22
22
  solution-cli [command] [options]
23
- sensecraft-solution-cli [command] [options]
24
23
 
25
24
  Commands:
26
25
  install Inspect environment, verify/download engine, and configure AI Agent skills (default)
27
26
  status Display current engine, solution content, and agent skills status
28
27
  doctor Diagnose environment (platform, permissions, docker, PATH, ledger)
29
- uninstall Cleanly remove SenseCraft skills from specified agent(s)
28
+ uninstall Cleanly remove Solution CLI skills from specified agent(s)
30
29
  hub Search scenarios and reference designs from Seeed Solution Hub
31
30
  Usage: solution-cli hub search <keyword> [--limit 5] [--locale zh|en] [--json]
32
31
  product Query product families, SKUs, and verified accessories
@@ -42,7 +41,7 @@ Options:
42
41
  -y, --yes Accept default detected selections non-interactively
43
42
  --dry-run Simulate operations without making changes to filesystem
44
43
  --skip-content Skip pulling IoT solution packages from CDN
45
- --force Force re-download or re-link even if already installed
44
+ --force Re-download equal/older stable Engine and overwrite existing skill files, including local modifications; never downgrade a newer local Engine
46
45
  --json Output machine-readable JSON (supported by all query/diagnostic commands)
47
46
  --locale <zh|en> Language for hub and product results (default: zh)
48
47
  --limit <num> Maximum results for search (default: 5)
@@ -58,7 +57,15 @@ Examples:
58
57
  `);
59
58
  }
60
59
 
60
+ class CliArgumentError extends Error {
61
+ constructor(message) {
62
+ super(message);
63
+ this.name = 'CliArgumentError';
64
+ }
65
+ }
66
+
61
67
  function parseFlags(rawArgs) {
68
+ const usedFlags = new Set();
62
69
  const flags = {
63
70
  agent: null,
64
71
  yes: false,
@@ -75,62 +82,121 @@ function parseFlags(rawArgs) {
75
82
  solution: null,
76
83
  version: '1.0.0',
77
84
  hardware: null,
85
+ positionals: [],
86
+ usedFlags,
78
87
  };
79
88
 
80
89
  for (let i = 0; i < rawArgs.length; i++) {
81
90
  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];
91
+ const valueFlag = (name, setter) => {
92
+ const inlinePrefix = `${name}=`;
93
+ if (a.startsWith(inlinePrefix)) {
94
+ const value = a.slice(inlinePrefix.length);
95
+ if (!value) throw new CliArgumentError(`${name} requires a value`);
96
+ usedFlags.add(name);
97
+ setter(value);
98
+ return true;
99
+ }
100
+ if (a === name) {
101
+ const value = rawArgs[i + 1];
102
+ if (!value || value.startsWith('-')) {
103
+ throw new CliArgumentError(`${name} requires a value`);
104
+ }
105
+ usedFlags.add(name);
106
+ setter(value);
107
+ i += 1;
108
+ return true;
109
+ }
110
+ return false;
111
+ };
112
+
113
+ if (valueFlag('--agent', value => { flags.agent = value; })) {
86
114
  } else if (a === '-y' || a === '--yes') {
115
+ usedFlags.add('--yes');
87
116
  flags.yes = true;
88
117
  } else if (a === '--dry-run') {
118
+ usedFlags.add('--dry-run');
89
119
  flags.dryRun = true;
90
120
  } else if (a === '--skip-content') {
121
+ usedFlags.add('--skip-content');
91
122
  flags.skipContent = true;
92
123
  } else if (a === '--force') {
124
+ usedFlags.add('--force');
93
125
  flags.force = true;
94
126
  } else if (a === '--json') {
127
+ usedFlags.add('--json');
95
128
  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];
129
+ } else if (valueFlag('--engine-path', value => { flags.enginePath = value; })) {
130
+ } else if (valueFlag('--limit', value => {
131
+ if (!/^[1-9]\d*$/.test(value)) throw new CliArgumentError('--limit requires a positive integer');
132
+ const limit = Number(value);
133
+ if (!Number.isSafeInteger(limit)) throw new CliArgumentError('--limit is too large');
134
+ flags.limit = limit;
135
+ })) {
136
+ } else if (valueFlag('--locale', value => { flags.locale = value; })) {
137
+ } else if (valueFlag('--sku', value => { flags.sku = value; })) {
138
+ } else if (valueFlag('--host', value => { flags.host = value; })) {
139
+ } else if (valueFlag('--accessory', value => { flags.accessory = value; })) {
140
+ } else if (valueFlag('--solution', value => { flags.solution = value; })) {
141
+ } else if (valueFlag('--version', value => { flags.version = value; })) {
142
+ } else if (valueFlag('--hardware', value => { flags.hardware = value; })) {
143
+ } else if (a.startsWith('-')) {
144
+ throw new CliArgumentError(`Unknown option "${a}"`);
145
+ } else {
146
+ flags.positionals.push(a);
114
147
  }
115
148
  }
116
149
 
117
150
  return flags;
118
151
  }
119
152
 
120
- async function main() {
121
- const flags = parseFlags(args.slice(1));
153
+ function validatePositionals(command, positionals) {
154
+ if (['install', 'uninstall', 'status', 'doctor'].includes(command) && positionals.length > 0) {
155
+ throw new CliArgumentError(`${command} does not accept positional arguments`);
156
+ }
157
+ }
122
158
 
123
- if (command === 'help' || command === '--help' || command === '-h') {
159
+ function validateCommandFlags(command, flags) {
160
+ const allowed = {
161
+ install: ['--agent', '--yes', '--dry-run', '--skip-content', '--force', '--engine-path'],
162
+ uninstall: ['--agent', '--dry-run', '--force'],
163
+ status: ['--json'],
164
+ doctor: ['--json'],
165
+ hub: ['--json', '--locale', '--limit'],
166
+ product: ['--json', '--locale', '--sku', '--host', '--accessory'],
167
+ lockfile: ['--json', '--solution', '--version', '--hardware'],
168
+ help: [],
169
+ };
170
+ const supported = allowed[command];
171
+ if (!supported) return;
172
+ for (const flag of flags.usedFlags) {
173
+ if (!supported.includes(flag)) {
174
+ throw new CliArgumentError(`${flag} is not supported for ${command}`);
175
+ }
176
+ }
177
+ }
178
+
179
+ async function main() {
180
+ if (args.some(a => a === '--help' || a === '-h')) {
124
181
  printHelp();
125
- process.exit(0);
182
+ return;
126
183
  }
127
184
 
128
185
  if (command === '--version' || command === '-v') {
129
186
  console.log(pkg.version);
130
- process.exit(0);
187
+ return;
131
188
  }
132
189
 
133
190
  try {
191
+ const flags = parseFlags(args.slice(1));
192
+ validatePositionals(command, flags.positionals);
193
+ validateCommandFlags(command, flags);
194
+
195
+ if (command === 'help') {
196
+ printHelp();
197
+ return;
198
+ }
199
+
134
200
  switch (command) {
135
201
  case 'install':
136
202
  await runInstall(flags);
@@ -145,7 +211,7 @@ async function main() {
145
211
  if (flags.json) {
146
212
  console.log(JSON.stringify(report, null, 2));
147
213
  } else {
148
- console.log('\n🩺 SenseCraft Doctor Environment Report:\n');
214
+ console.log('\n🩺 Solution CLI Doctor Environment Report:\n');
149
215
  report.checks.forEach(c => {
150
216
  const symbol = c.status === 'ok' ? '\x1b[32m✔\x1b[0m' : c.status === 'warn' ? '\x1b[33m⚠️\x1b[0m' : '\x1b[31m✖\x1b[0m';
151
217
  console.log(` ${symbol} ${c.title}`);
@@ -161,12 +227,12 @@ async function main() {
161
227
  break;
162
228
 
163
229
  case 'hub': {
164
- const sub = args[1] || 'search';
230
+ const sub = flags.positionals[0] || 'search';
165
231
  if (sub !== 'search') {
166
232
  console.error(`Unknown hub action "${sub}". Supported: "solution-cli hub search <keyword>"`);
167
233
  process.exit(1);
168
234
  }
169
- const query = args.slice(2).filter((a) => !a.startsWith('-')).join(' ');
235
+ const query = flags.positionals.slice(1).join(' ');
170
236
  const dto = await searchHub({ query, locale: flags.locale, limit: flags.limit });
171
237
  if (flags.json) {
172
238
  console.log(JSON.stringify(dto, null, 2));
@@ -200,8 +266,8 @@ async function main() {
200
266
  }
201
267
 
202
268
  case 'product': {
203
- const sub = args[1] || 'query';
204
- const familyId = args.slice(2).find((a) => !a.startsWith('-')) || flags.host;
269
+ const sub = flags.positionals[0] || 'query';
270
+ const familyId = flags.positionals[1] || flags.host;
205
271
 
206
272
  if (sub === 'query') {
207
273
  if (!familyId) {
@@ -296,7 +362,7 @@ async function main() {
296
362
  }
297
363
 
298
364
  case 'lockfile': {
299
- const sub = args[1] || 'create';
365
+ const sub = flags.positionals[0] || 'create';
300
366
  if (sub !== 'create') {
301
367
  console.error(`Unknown lockfile action "${sub}". Supported: "solution-cli lockfile create"`);
302
368
  process.exit(1);
@@ -326,7 +392,7 @@ async function main() {
326
392
  }
327
393
  } catch (err) {
328
394
  console.error(`\n\x1b[31mError:\x1b[0m ${err.message}\n`);
329
- process.exit(1);
395
+ process.exitCode = err instanceof CliArgumentError ? 2 : 1;
330
396
  }
331
397
  }
332
398
 
package/bin/runner.js CHANGED
@@ -8,7 +8,7 @@ const binPath = getConfiguredEnginePath();
8
8
 
9
9
  if (!fs.existsSync(binPath) || !isExecutable(binPath)) {
10
10
  console.error(`\x1b[31mError:\x1b[0m SenseCraft Engine binary not found at "${binPath}".`);
11
- console.error('Please run "npx @sensecraft/cli install" first to provision the engine.\n');
11
+ console.error('Please run "npx -y --package=@seeed-studio/solution-cli@latest solution-cli install --agent <agent-id>" first to provision the engine.\n');
12
12
  process.exit(1);
13
13
  }
14
14