@seeed-studio/solution-cli 0.0.0-stage → 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/README.md +77 -2
- package/bin/cli.js +327 -0
- package/bin/runner.js +39 -0
- package/catalog/knowledge-core.json +7970 -0
- package/lib/agent.js +160 -0
- package/lib/catalog-dto.js +69 -0
- package/lib/compatibility.js +312 -0
- package/lib/detector.js +321 -0
- package/lib/doctor.js +179 -0
- package/lib/downloader.js +219 -0
- package/lib/installer.js +375 -0
- package/lib/knowledge.js +327 -0
- package/lib/ledger.js +302 -0
- package/lib/platform.js +113 -0
- package/package.json +52 -4
- package/skills/sensecraft/SKILL.md +183 -0
- package/skills/sensecraft/references/app-only.md +273 -0
- package/skills/sensecraft/references/consult-and-select.md +56 -0
- package/skills/sensecraft/references/deploy.md +300 -0
- package/skills/sensecraft/references/diagnose.md +133 -0
- package/skills/sensecraft/references/error-catalog.md +33 -0
- package/skills/sensecraft/references/hardware-bom.md +83 -0
- package/skills/sensecraft/references/offline.md +78 -0
- package/skills/sensecraft/references/verify.md +136 -0
package/lib/platform.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
|
|
5
|
+
export function getPlatformInfo() {
|
|
6
|
+
const p = os.platform();
|
|
7
|
+
const a = os.arch();
|
|
8
|
+
|
|
9
|
+
let target = null;
|
|
10
|
+
let supported = true;
|
|
11
|
+
let note = '';
|
|
12
|
+
|
|
13
|
+
if (p === 'darwin') {
|
|
14
|
+
if (a === 'arm64') {
|
|
15
|
+
target = 'aarch64-apple-darwin';
|
|
16
|
+
} else {
|
|
17
|
+
supported = false;
|
|
18
|
+
note = 'macOS x86_64 (Intel) is not supported for standalone binary releases. Under Rosetta, run from a native ARM shell.';
|
|
19
|
+
}
|
|
20
|
+
} else if (p === 'linux') {
|
|
21
|
+
if (a === 'x64') {
|
|
22
|
+
target = 'x86_64-unknown-linux-musl';
|
|
23
|
+
} else if (a === 'arm64') {
|
|
24
|
+
target = 'aarch64-unknown-linux-musl';
|
|
25
|
+
} else {
|
|
26
|
+
supported = false;
|
|
27
|
+
note = `Linux architecture ${a} is not supported. Supported: x64, arm64.`;
|
|
28
|
+
}
|
|
29
|
+
} else if (p === 'win32') {
|
|
30
|
+
if (a === 'x64') {
|
|
31
|
+
target = 'x86_64-pc-windows-msvc';
|
|
32
|
+
} else {
|
|
33
|
+
supported = false;
|
|
34
|
+
note = `Windows architecture ${a} is not supported. Supported: x64.`;
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
supported = false;
|
|
38
|
+
note = `Operating system ${p} is not supported.`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const isWindows = p === 'win32';
|
|
42
|
+
const binaryName = isWindows ? 'sensecraft-engine.exe' : 'sensecraft-engine';
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
platform: p,
|
|
46
|
+
arch: a,
|
|
47
|
+
target,
|
|
48
|
+
supported,
|
|
49
|
+
note,
|
|
50
|
+
isWindows,
|
|
51
|
+
binaryName
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getScHome() {
|
|
56
|
+
return process.env.SC_HOME || path.join(os.homedir(), '.sensecraft');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getEngineBinDir() {
|
|
60
|
+
return path.join(getScHome(), 'bin');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getEngineBinaryPath() {
|
|
64
|
+
const { binaryName } = getPlatformInfo();
|
|
65
|
+
return path.join(getEngineBinDir(), binaryName);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getContentDir() {
|
|
69
|
+
return path.join(getScHome(), 'content');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getConfiguredEnginePath() {
|
|
73
|
+
const pointerFile = path.join(getScHome(), 'engine.json');
|
|
74
|
+
if (fs.existsSync(pointerFile)) {
|
|
75
|
+
try {
|
|
76
|
+
const data = JSON.parse(fs.readFileSync(pointerFile, 'utf8'));
|
|
77
|
+
if (data && data.bin && fs.existsSync(data.bin) && isExecutable(data.bin)) {
|
|
78
|
+
return path.resolve(data.bin);
|
|
79
|
+
}
|
|
80
|
+
} catch {}
|
|
81
|
+
}
|
|
82
|
+
return path.resolve(getEngineBinaryPath());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function setConfiguredEnginePath(binPath, version = null, source = null) {
|
|
86
|
+
const pointerFile = path.join(getScHome(), 'engine.json');
|
|
87
|
+
const dir = path.dirname(pointerFile);
|
|
88
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
89
|
+
const payload = {
|
|
90
|
+
schema_version: 1,
|
|
91
|
+
bin: path.resolve(binPath),
|
|
92
|
+
version: version || 'unknown',
|
|
93
|
+
source: source || 'unknown',
|
|
94
|
+
updated_at: new Date().toISOString()
|
|
95
|
+
};
|
|
96
|
+
const tempFile = path.join(dir, `.engine.tmp-${Date.now()}`);
|
|
97
|
+
fs.writeFileSync(tempFile, JSON.stringify(payload, null, 2), 'utf8');
|
|
98
|
+
fs.renameSync(tempFile, pointerFile);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function isExecutable(filePath) {
|
|
102
|
+
try {
|
|
103
|
+
const stat = fs.statSync(filePath);
|
|
104
|
+
if (!stat.isFile()) return false;
|
|
105
|
+
if (os.platform() === 'win32') {
|
|
106
|
+
return filePath.toLowerCase().endsWith('.exe') || filePath.toLowerCase().endsWith('.cmd');
|
|
107
|
+
}
|
|
108
|
+
// Unix: check execute permissions (owner/group/others)
|
|
109
|
+
return Boolean(stat.mode & 0o111);
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seeed-studio/solution-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Zero-friction installer and CLI runner for SenseCraft Engine and AI Agent Skills (Work Buddy, Claude Code, Codex, CodeBuddy)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./lib/installer.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"solution-cli": "bin/cli.js",
|
|
9
|
+
"sensecraft-solution-cli": "bin/cli.js",
|
|
10
|
+
"sensecraft": "bin/cli.js",
|
|
11
|
+
"sensecraft-engine": "bin/runner.js",
|
|
12
|
+
"sensecraft-skill": "bin/cli.js"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin",
|
|
19
|
+
"lib",
|
|
20
|
+
"catalog",
|
|
21
|
+
"skills",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "node --test tests/cli.test.mjs",
|
|
26
|
+
"install:self": "node bin/cli.js install",
|
|
27
|
+
"status": "node bin/cli.js status",
|
|
28
|
+
"doctor": "node bin/cli.js doctor"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"seeed-studio",
|
|
32
|
+
"sensecraft",
|
|
33
|
+
"sensecraft-solution",
|
|
34
|
+
"iot",
|
|
35
|
+
"workbuddy",
|
|
36
|
+
"claude-code",
|
|
37
|
+
"codex",
|
|
38
|
+
"codebuddy",
|
|
39
|
+
"skills",
|
|
40
|
+
"edge-ai",
|
|
41
|
+
"jetson",
|
|
42
|
+
"raspberry-pi"
|
|
43
|
+
],
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/Seeed-Studio/app_collaboration.git",
|
|
47
|
+
"directory": "packages/solution-cli"
|
|
48
|
+
},
|
|
49
|
+
"author": "Seeed Studio",
|
|
50
|
+
"license": "Apache-2.0",
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sensecraft
|
|
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
|
+
---
|
|
5
|
+
|
|
6
|
+
# SenseCraft Solution
|
|
7
|
+
|
|
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
|
+
| **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
|
+
|
|
22
|
+
Capabilities that need the **SenseCraft Solution desktop app** (editing a
|
|
23
|
+
solution's configuration, the app's `/api/ai/context`, diagnosing through the
|
|
24
|
+
app's logs) are in `references/app-only.md`. Use them only when the user has
|
|
25
|
+
the app running.
|
|
26
|
+
|
|
27
|
+
## Host platform boundary
|
|
28
|
+
|
|
29
|
+
The standalone engine supports four host platforms: macOS ARM
|
|
30
|
+
(`aarch64-apple-darwin`), Linux ARM (`aarch64-unknown-linux-musl`), Linux
|
|
31
|
+
x86_64 (`x86_64-unknown-linux-musl`), and Windows x86_64
|
|
32
|
+
(`x86_64-pc-windows-msvc`). macOS x86_64 is not maintained; under Rosetta,
|
|
33
|
+
run the installer from a native ARM shell.
|
|
34
|
+
|
|
35
|
+
`docker_deploy` target `local` requires a supported Docker host. Windows native
|
|
36
|
+
`docker_local` is intentionally unsupported by both the Python and Rust
|
|
37
|
+
deployers; a Windows CLI can still use a `remote` target over SSH to a Linux
|
|
38
|
+
Docker host. WSL uses the Linux engine binary and Linux host behavior; do not
|
|
39
|
+
describe a WSL run as Windows-native local Docker support. USB device steps
|
|
40
|
+
remain hardware-dependent and require the user to connect and confirm the
|
|
41
|
+
device.
|
|
42
|
+
|
|
43
|
+
On macOS, local Docker uses the literal `docker` command found by the engine's
|
|
44
|
+
process environment. If Docker Desktop's CLI is only inside its application
|
|
45
|
+
bundle, start a new engine process with a temporary `PATH` containing that
|
|
46
|
+
bundle's `bin` directory; do not change global shell configuration or assume an
|
|
47
|
+
existing engine daemon inherits the new path. Verify the new engine instance
|
|
48
|
+
with `doctor --json` before selecting a local target. A running Docker Desktop
|
|
49
|
+
daemon is a host prerequisite, not evidence that a deployment has passed.
|
|
50
|
+
|
|
51
|
+
## Rules
|
|
52
|
+
|
|
53
|
+
1. **Credentials and secrets:** never invent a host, username or password —
|
|
54
|
+
ask the user. Replace passwords, tokens, API keys and any other secret a
|
|
55
|
+
deployment prints (for example a gateway token) with `<REDACTED>` in
|
|
56
|
+
anything you show or log. Tell the user where to find the value (on the
|
|
57
|
+
device, in the solution's page, in the file that holds it) instead of
|
|
58
|
+
printing it.
|
|
59
|
+
2. **What you may do without asking, and what needs the user's OK:**
|
|
60
|
+
|
|
61
|
+
| Action | Ask first? |
|
|
62
|
+
|---|---|
|
|
63
|
+
| Read deployment status and logs (`status`, `logs`), engine log files, `doctor`, `device list/ports/scan` | No |
|
|
64
|
+
| Read-only checks on a device over SSH (`docker ps`, `docker logs`, `df -h`, `ss -ltnp`, `ping`) | No |
|
|
65
|
+
| Edit the solution's edit copy (desktop app only, see `references/app-only.md`) | No — show the diff afterwards; the user sees it in the app as "locally modified" and can restore the official version in one click |
|
|
66
|
+
| Anything a person does with their hands: plug in or swap a cable, connect a servo, sit in front of a camera, walk across a counting line, move or stand near a robot arm | **Yes** — tell the user what to do and wait for them to confirm it is done |
|
|
67
|
+
| Commands that move hardware (`robot torque`, `robot actions test`) | **Yes** — the user confirms nobody is within reach first |
|
|
68
|
+
| Change anything on a device: stop/remove containers, delete files, install packages, change system settings; `--replace-existing` | **Yes** |
|
|
69
|
+
| Redeploy | First retry: no. Every later retry: **yes** |
|
|
70
|
+
| Cancel a running deployment | **Yes** — it can leave a device half-configured |
|
|
71
|
+
| Prepare an issue report (the user submits it) or open a pull request | **Yes** |
|
|
72
|
+
|
|
73
|
+
3. **At most 3 fix-and-retry rounds** per problem for deterministic failures
|
|
74
|
+
(wrong parameter, missing file, port taken, bad credentials, a container
|
|
75
|
+
that exits). After that, stop and give the user a report (what failed, what
|
|
76
|
+
you checked, what you changed). Transient registry or network errors follow
|
|
77
|
+
the backoff rule in `references/deploy.md` §6 and do not use up a round.
|
|
78
|
+
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. **Crucial engine switch step**: Because an existing engine daemon instance retains its initial solution directory in memory, you MUST stop the running instance first: `sensecraft-engine daemon stop`. Then re-run deployment with `--solutions-dir ./.sensecraft-work/solutions` so the fresh engine instance picks up the patched files. Show the user the patch diff.
|
|
80
|
+
- If running with the Desktop App: use the app's edit copy mechanism (`references/app-only.md` §4).
|
|
81
|
+
5. **Reading output:** read the CLI's JSON output directly; it is small. If a
|
|
82
|
+
filter is needed, use `jq` only when it is already installed. Do not start
|
|
83
|
+
python, node or any other interpreter to parse output. Where a command has
|
|
84
|
+
a flag that prints just what you need (`--json`, `--preset`, `--device`,
|
|
85
|
+
`--since`), use it.
|
|
86
|
+
6. **Temp files:** keep request bodies and scratch files in the current
|
|
87
|
+
working directory under `./.sensecraft-work/`, never in `/tmp`. Remove the
|
|
88
|
+
directory when the task ends, also after a failure.
|
|
89
|
+
|
|
90
|
+
## Step 0 — Install the engine and the solutions (every task)
|
|
91
|
+
|
|
92
|
+
1. Check whether it is installed: `sensecraft-engine --version`. If the command
|
|
93
|
+
is not found, also try `~/.sensecraft/bin/sensecraft-engine --version`
|
|
94
|
+
(Windows: `%USERPROFILE%\.sensecraft\bin\sensecraft-engine.cmd`); the
|
|
95
|
+
installer puts it there and prints the `PATH` line to add.
|
|
96
|
+
2. Before running an installer, check its local prerequisites. On Unix run
|
|
97
|
+
`command -v curl || command -v wget`, `command -v sha256sum || command -v shasum`,
|
|
98
|
+
`command -v tar`, `command -v jq`, and `command -v minisign`. If wget is the
|
|
99
|
+
selected downloader, also require `wget --help | grep -- --compression` to
|
|
100
|
+
succeed; otherwise stop because the CDN may return gzip content. On Windows
|
|
101
|
+
PowerShell run `Get-Command irm`, `Get-Command Invoke-WebRequest`,
|
|
102
|
+
`Get-Command Expand-Archive`, `Get-Command ConvertFrom-Json`, and
|
|
103
|
+
`Get-Command minisign`. Windows uses PowerShell/.NET ZIP and JSON support;
|
|
104
|
+
it does not require `tar` or `jq`. If any check fails, report the missing
|
|
105
|
+
command and stop; do not install packages or start another interpreter.
|
|
106
|
+
`minisign` is required to verify the signed release manifest; Unix `jq` is
|
|
107
|
+
required by the shell installer to read it.
|
|
108
|
+
3. Not installed → install it together with the solutions (ask the user first,
|
|
109
|
+
it downloads a program):
|
|
110
|
+
|
|
111
|
+
**Recommended (Zero-friction via solution-cli, auto-detects existing app & agents)**:
|
|
112
|
+
```bash
|
|
113
|
+
npx @seeed-studio/solution-cli install -y
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Fallback (Standalone shell script when Node.js is not present)**:
|
|
117
|
+
```bash
|
|
118
|
+
curl --compressed -fsSL https://sensecraft-statics.seeed.cc/solution-app/engine/install.sh | sh -s -- --with-content
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
If `curl` is unavailable but `wget` passed the prerequisite check, use the
|
|
122
|
+
equivalent command:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
wget --compression=auto -qO- https://sensecraft-statics.seeed.cc/solution-app/engine/install.sh | sh -s -- --with-content
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Windows PowerShell:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
& ([scriptblock]::Create((irm https://sensecraft-statics.seeed.cc/solution-app/engine/install.ps1))) -WithContent
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
4. Solutions are downloaded separately from the engine:
|
|
135
|
+
|
|
136
|
+
| Command | Does |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `sensecraft-engine content status --offline --json` | Reads the local content store only; an empty store is a normal result with `installed: 0` and exit code 0 |
|
|
139
|
+
| `sensecraft-engine content status --json` | Compares local solutions with the online list |
|
|
140
|
+
| `sensecraft-engine content pull` | Downloads every solution (first run), or only `--solution <id>` |
|
|
141
|
+
| `sensecraft-engine content update` | Re-downloads installed solutions that changed online |
|
|
142
|
+
|
|
143
|
+
Run the offline status check before deciding whether content is installed.
|
|
144
|
+
If it returns exit code 0 with `installed: 0`, run `content pull`; do not
|
|
145
|
+
treat that status result as an error. A solution query such as `solution
|
|
146
|
+
list` or `deploy-info` exits with code 3 when no usable resource/solution
|
|
147
|
+
directory is available and tells you to run `content pull`. If a usable
|
|
148
|
+
directory exists but the requested solution ID is unknown, the query exits
|
|
149
|
+
with code 2. After `content pull` / `update`, if an engine instance is
|
|
150
|
+
already running it keeps the solutions it loaded at start: run
|
|
151
|
+
`sensecraft-engine daemon stop` when no deployment is running.
|
|
152
|
+
5. Device pre-checks on this computer: `sensecraft-engine doctor --json`. Each
|
|
153
|
+
check has `status` (`ok` / `warn` / `fail`) and a `fix` command. Show the
|
|
154
|
+
user any `fail` and its `fix`; running a `fix` changes this computer and
|
|
155
|
+
needs their OK.
|
|
156
|
+
|
|
157
|
+
Use `--lang zh` on commands that take `--lang` when the user writes in Chinese,
|
|
158
|
+
`--lang en` otherwise.
|
|
159
|
+
|
|
160
|
+
## Exit codes (all commands)
|
|
161
|
+
|
|
162
|
+
| Code | Meaning | Next step |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| 0 | Success / accepted | Continue |
|
|
165
|
+
| 1 | Deployment failed, cancelled or interrupted; for `counting` / `face` / `wizard` / `robot`: the device did not confirm | `references/diagnose.md` |
|
|
166
|
+
| 2 | Invalid arguments or request. A `missing_params` error includes `missing[]`; an unknown ID or malformed JSON is also code 2 and may have no `missing[]` | Ask for each field only when `missing[]` is present; otherwise inspect the ID, JSON shape and parameter spelling, then correct or ask for the specific value |
|
|
167
|
+
| 3 | Environment or conflict: a solution query has no usable resource/solution directory (`content pull`), device busy with another deployment (409), engine instance shutting down or incompatible, serial port busy | Read the message; pull content or fix the environment, then run again. An empty `content status --offline` store is exit 0, not this case |
|
|
168
|
+
| 4 | This step needs the desktop app (deployer not in the engine) | Tell the user to deploy this solution from the SenseCraft Solution app |
|
|
169
|
+
| 5 | Internal engine error | Engine bug: `references/diagnose.md` §6 |
|
|
170
|
+
|
|
171
|
+
## Pick the task
|
|
172
|
+
|
|
173
|
+
| The user wants to... | Read |
|
|
174
|
+
|---|---|
|
|
175
|
+
| Deploy a solution | `references/deploy.md` |
|
|
176
|
+
| Finish a step that reported `verify_required` (dashboards, counting lines, face enrollment, servo setup, robot arm) | `references/verify.md` |
|
|
177
|
+
| Find out why a deployment failed or a deployed solution misbehaves | `references/diagnose.md` |
|
|
178
|
+
| Deploy on a site without internet (prepare an offline package first) | `references/offline.md` |
|
|
179
|
+
| Change a solution's configuration, or use the desktop app's API | `references/app-only.md` (requires the desktop app) |
|
|
180
|
+
|
|
181
|
+
They connect: **a failed deployment goes straight to `diagnose.md`** without
|
|
182
|
+
stopping to ask; a `verify_required` event goes to `verify.md`; the retry goes
|
|
183
|
+
back to `deploy.md` for the failed device only.
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Desktop-app-only capabilities
|
|
2
|
+
|
|
3
|
+
> **Requires the SenseCraft Solution desktop app** running on this computer.
|
|
4
|
+
> Everything here talks to the app's local HTTP API with `curl`. The engine
|
|
5
|
+
> CLI path in `SKILL.md` does not need any of it. Use this file when the user
|
|
6
|
+
> has the app open and wants to change a solution's configuration, deployed
|
|
7
|
+
> from the app, or asks what the app is showing.
|
|
8
|
+
|
|
9
|
+
Sections: 1. Connect to the app · 2. Deploy through the app · 3. Diagnose
|
|
10
|
+
through the app · 4. Change a solution's configuration (edit copy).
|
|
11
|
+
|
|
12
|
+
> **Windows PowerShell:** `curl` there is an alias for `Invoke-WebRequest`.
|
|
13
|
+
> Always type `curl.exe`. Send JSON bodies from a file (`--data @body.json`)
|
|
14
|
+
> instead of inline strings to avoid quoting problems.
|
|
15
|
+
|
|
16
|
+
## 1. Connect to the app (every app task)
|
|
17
|
+
|
|
18
|
+
1. Read the runtime file:
|
|
19
|
+
- macOS / Linux: `~/.sensecraft/runtime.json`
|
|
20
|
+
- Windows: `%USERPROFILE%\.sensecraft\runtime.json`
|
|
21
|
+
|
|
22
|
+
It holds `base_url`, `contract_version`, `logs_dir`, `solutions_dir` and
|
|
23
|
+
`solutions_edits_dir`.
|
|
24
|
+
2. Check the app is really there: `curl -s <base_url>/api/health` must return
|
|
25
|
+
`"status": "healthy"`. The file can be left over after a crash.
|
|
26
|
+
3. If the file is missing or the check fails, the app is not running: ask the
|
|
27
|
+
user to open **SenseCraft Solution** and try again. (Developers running
|
|
28
|
+
`./dev.sh` from source: `base_url` is `http://127.0.0.1:3260`.)
|
|
29
|
+
4. If `contract_version` is below 6, deploying and diagnosing still work, but
|
|
30
|
+
config editing is not available: tell the user to update the app.
|
|
31
|
+
5. The `sensecraft-engine` CLI writes the same file for its own instance. A
|
|
32
|
+
file with `"schema_version": 2` and `"python_backend": null` points at an
|
|
33
|
+
engine instance started by the CLI, not at the app: the app is not running.
|
|
34
|
+
|
|
35
|
+
Below, `BASE` means the `base_url` value. Add `lang=zh` to API calls that take
|
|
36
|
+
`lang` when the user writes in Chinese, `lang=en` otherwise.
|
|
37
|
+
|
|
38
|
+
## 2. Deploy through the app
|
|
39
|
+
|
|
40
|
+
`BASE` comes from §1. Examples use `lang=en`; use `lang=zh`
|
|
41
|
+
for a Chinese-speaking user.
|
|
42
|
+
|
|
43
|
+
### 2.1 Find the solution
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
curl -s "BASE/api/solutions?lang=en"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Match the user's wording against `name` / `summary`. Confirm the `id` with the
|
|
50
|
+
user if more than one fits.
|
|
51
|
+
|
|
52
|
+
### 2.2 Read what the deployment needs
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
curl -s "BASE/api/solutions/<id>/deploy-info?lang=en"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- `presets[]` — deployment variants. If there is more than one, ask the user which
|
|
59
|
+
one, then call again with `&preset_id=<preset>`.
|
|
60
|
+
- `steps[]` — each step's `device_id`, `type`, `required`, parameters, and for
|
|
61
|
+
`docker_deploy` steps the `targets` (local = this computer's Docker, remote =
|
|
62
|
+
a device over SSH).
|
|
63
|
+
- `request_template` — a ready request body. Values shaped like
|
|
64
|
+
`<REQUIRED: ...>` must be filled in; everything else has a working default.
|
|
65
|
+
|
|
66
|
+
Never make up step or device IDs: the keys of `device_connections` and the
|
|
67
|
+
entries of `selected_devices` are exactly the ones in `request_template`.
|
|
68
|
+
|
|
69
|
+
Optional device discovery, to pre-fill hosts:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
curl -s "BASE/api/devices/detect/<id>?preset=<preset>" # USB / serial
|
|
73
|
+
curl -s "BASE/api/devices/scan-mdns" # SSH devices on the LAN
|
|
74
|
+
curl -s "BASE/api/docker-devices/local/check" # local Docker
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2.3 Fill in and confirm
|
|
78
|
+
|
|
79
|
+
Ask the user for every `<REQUIRED: ...>` value (device IP, username, password,
|
|
80
|
+
and any user inputs). For `docker_deploy` steps pick the target with
|
|
81
|
+
`"target": "<target id>"` **and set `"target_type"` to that target's own type**
|
|
82
|
+
(`local` / `remote`, from deploy-info `targets`) — or remove `target_type`. The
|
|
83
|
+
template carries the default target's type; leaving it while switching
|
|
84
|
+
`target` is rejected with a 400. Remote targets also need `host`, `username`,
|
|
85
|
+
`password`. Add `"auto_replace_containers": true` only if the user agrees to
|
|
86
|
+
replace existing containers with the same name.
|
|
87
|
+
|
|
88
|
+
Write the filled body to a file, e.g. `body.json`:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"solution_id": "<id>",
|
|
93
|
+
"preset_id": "<preset>",
|
|
94
|
+
"selected_devices": ["<step_id>"],
|
|
95
|
+
"device_connections": {
|
|
96
|
+
"<step_id>": {"target": "<target_id>", "host": "192.168.1.50",
|
|
97
|
+
"username": "<user>", "password": "<password>"}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Show the user the body with the password as `<REDACTED>` before starting.
|
|
103
|
+
|
|
104
|
+
### 2.4 Start and wait
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
curl -s -X POST "BASE/api/deployments/start" -H "Content-Type: application/json" --data @body.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Keep the returned `deployment_id`. The response also lists each device with
|
|
111
|
+
`status`: `deploying`, or `skipped` with a `reason` (manual / info-only steps
|
|
112
|
+
are always skipped — that is normal, not an error).
|
|
113
|
+
|
|
114
|
+
Poll every 3–5 seconds:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
curl -s "BASE/api/deployments/<deployment_id>/summary"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`status` is `running` while in progress and ends as `completed`, `failed` or
|
|
121
|
+
`cancelled`. Each `devices[].steps[]` entry has its own `status` (`pending`,
|
|
122
|
+
`running`, `completed`, `failed`) and a `message`. The JSON is compact
|
|
123
|
+
(`"status":"failed"`, no spaces) — parse it rather than grepping. The user sees the same
|
|
124
|
+
deployment live in the app. Delete `body.json` afterwards (it holds a password).
|
|
125
|
+
|
|
126
|
+
### 2.5 Verify
|
|
127
|
+
|
|
128
|
+
`completed` is not enough. Check the result is actually usable:
|
|
129
|
+
|
|
130
|
+
- Docker steps: on the target (`docker ps` locally, or over SSH) the
|
|
131
|
+
solution's containers are `Up` and, if they define a health check, `(healthy)`.
|
|
132
|
+
- Services with a port: `curl -s -o /dev/null -w "%{http_code}" http://<host>:<port>/`
|
|
133
|
+
returns 2xx/3xx.
|
|
134
|
+
- Tell the user the URL to open and anything the guide says to do next.
|
|
135
|
+
|
|
136
|
+
### 2.6 If anything failed
|
|
137
|
+
|
|
138
|
+
Go straight to §3 below with the `deployment_id`. Do not stop to ask.
|
|
139
|
+
|
|
140
|
+
To retry after a fix, send the same body again — same `preset_id` and
|
|
141
|
+
`device_connections` — with `selected_devices` listing only the device(s) that
|
|
142
|
+
failed; the others are already done. Leaving manual steps in is harmless (they
|
|
143
|
+
are skipped).
|
|
144
|
+
|
|
145
|
+
## 3. Diagnose through the app
|
|
146
|
+
|
|
147
|
+
The general method (classify, retry, report, issue form) is in `diagnose.md`; these are the app-specific evidence sources.
|
|
148
|
+
|
|
149
|
+
### 3.1 Find the deployment
|
|
150
|
+
|
|
151
|
+
If you started it, you already have the `deployment_id`. Otherwise:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
curl -s "BASE/api/deployments" # newest first: id, solution_id, status
|
|
155
|
+
curl -s "BASE/api/ai/context" # what is running right now, and what the user is looking at in the app
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 3.2 Collect evidence
|
|
159
|
+
|
|
160
|
+
`/summary` and `/logs` only know deployments made since the app last started;
|
|
161
|
+
an older id from the list returns 404. In that case read the engine log (below)
|
|
162
|
+
for that time, or reproduce the failure by deploying again.
|
|
163
|
+
|
|
164
|
+
Start with the summary. **The real error is usually in the failing step's
|
|
165
|
+
`message`** (`devices[].steps[]` with `status: failed`); the top-level `errors`
|
|
166
|
+
list and the error-level logs may only say "Device X deployment failed".
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
curl -s "BASE/api/deployments/<deployment_id>/summary"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
For more context around the failure, the full device log:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
curl -s "BASE/api/deployments/<deployment_id>/logs?device_id=<device_id>&limit=300"
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
If the deployment logs are empty or end abruptly, read the engine log:
|
|
179
|
+
`<logs_dir>/provisioning-station.log` (last ~200 lines) and, if present,
|
|
180
|
+
`<logs_dir>/provisioning-station-crash.log`. `logs_dir` is in `runtime.json`.
|
|
181
|
+
|
|
182
|
+
Note the failing device, step and the first error — later errors are usually
|
|
183
|
+
consequences of the first.
|
|
184
|
+
|
|
185
|
+
### 3.3 Device checks, classification, retry, report
|
|
186
|
+
|
|
187
|
+
As in `diagnose.md` §3–6, with these differences: the solution folder is
|
|
188
|
+
`<solutions_dir>/<id>/` from `runtime.json`, or the edit copy if one exists;
|
|
189
|
+
configuration fixes go through §4; the issue form's `app_version` is
|
|
190
|
+
`app_version` from `runtime.json`, and its `diff` field is the output of
|
|
191
|
+
`/api/editor/solutions/<id>/diff`.
|
|
192
|
+
|
|
193
|
+
## 4. Change a solution's configuration
|
|
194
|
+
|
|
195
|
+
Needs `contract_version` 6 or later (see §1).
|
|
196
|
+
|
|
197
|
+
The app never changes its bundled solutions. Edits go into an **edit copy** of
|
|
198
|
+
one solution; while the edit copy exists, the app uses it instead of the
|
|
199
|
+
bundled one. Deleting it ("discard") restores the original. The app marks
|
|
200
|
+
the solution as "locally modified", shows the changes, and offers "restore
|
|
201
|
+
official version" — so the user can always see and undo what you changed.
|
|
202
|
+
|
|
203
|
+
### 4.1 Open the edit copy
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
curl -s -X POST "BASE/api/editor/solutions/<id>/enter"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Returns `path` — the edit copy's folder. Calling it again is safe; it keeps
|
|
210
|
+
existing edits and returns the same `path`.
|
|
211
|
+
|
|
212
|
+
### 4.2 Edit files in `path`
|
|
213
|
+
|
|
214
|
+
| File | Holds |
|
|
215
|
+
|---|---|
|
|
216
|
+
| `solution.yaml` | Name, presets, intro metadata |
|
|
217
|
+
| `guide.md`, `guide_zh.md` | Deployment steps; each `## Step` header has `config=devices/<x>.yaml` |
|
|
218
|
+
| `devices/<x>.yaml` | One step's settings: `docker.compose_file`, `docker.environment`, `docker.services[].port` / `health_check_endpoint`, `pre_checks`, `ssh.default_user`, firmware source, ... |
|
|
219
|
+
| `assets/...` | What gets deployed: compose files, Node-RED flows, scripts, firmware |
|
|
220
|
+
|
|
221
|
+
Field definitions:
|
|
222
|
+
https://raw.githubusercontent.com/suharvest/sensecraft-solutions/main/spec/device.schema.json
|
|
223
|
+
and `.../spec/solution.schema.json`.
|
|
224
|
+
|
|
225
|
+
Keep changes minimal: change what the evidence points at, nothing else. When a
|
|
226
|
+
value appears in several places (a port in compose, in `services[].port` and in
|
|
227
|
+
`pre_checks`), change all of them.
|
|
228
|
+
|
|
229
|
+
**Device YAMLs and assets are read from disk at deploy time**, so they take
|
|
230
|
+
effect as soon as you save them. Always validate before redeploying.
|
|
231
|
+
|
|
232
|
+
### 4.3 Validate
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
curl -s -X POST "BASE/api/editor/solutions/<id>/validate"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`ok: false` → fix each entry in `errors` (`file` is relative to `path`, `line`
|
|
239
|
+
is 1-based or null) and validate again. `warnings` do not block; leave
|
|
240
|
+
existing ones (e.g. "missing Chinese translation") alone unless the user asked
|
|
241
|
+
for guide changes.
|
|
242
|
+
|
|
243
|
+
### 4.4 Apply
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
curl -s -X POST "BASE/api/editor/solutions/<id>/apply"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
- `200` with `active_path` equal to `path` → the edit copy is live, including
|
|
250
|
+
changes to `solution.yaml` and the guides.
|
|
251
|
+
- `422` → `{"detail": <the same report as validate>}`; nothing was reloaded.
|
|
252
|
+
Fix the `detail.errors` entries and retry.
|
|
253
|
+
|
|
254
|
+
Then redeploy the affected device (§2.6, or `deploy.md` §6 on the engine path).
|
|
255
|
+
|
|
256
|
+
### 4.5 Show what changed
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
curl -s "BASE/api/editor/solutions/<id>/diff"
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Returns `files[]` with `path`, `status` (`modified` / `added` / `removed`) and a
|
|
263
|
+
unified `diff` for text files. Show the user these diffs. The same changes are
|
|
264
|
+
visible in the app.
|
|
265
|
+
|
|
266
|
+
### 4.6 Undo, share
|
|
267
|
+
|
|
268
|
+
- Undo all edits: `curl -s -X POST "BASE/api/editor/solutions/<id>/discard"`
|
|
269
|
+
- Package the edit copy as a zip (returns `zip_path`) to send to the solution
|
|
270
|
+
maintainers or turn into a pull request:
|
|
271
|
+
`curl -s -X POST "BASE/api/editor/solutions/<id>/export"`
|
|
272
|
+
- An app content update does not overwrite the edit copy. If the user later
|
|
273
|
+
wants the updated official version, discard the edit copy.
|