yeelight-home 0.1.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/CONFIG.md +63 -0
- package/DISTRIBUTION.md +38 -0
- package/INSTALL.md +80 -0
- package/README.md +75 -0
- package/npm/bin/yeelight-home.js +24 -0
- package/npm/install.js +17 -0
- package/npm/lib/runtime-installer.js +206 -0
- package/package.json +43 -0
package/CONFIG.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
## Precedence
|
|
4
|
+
|
|
5
|
+
Runtime configuration is resolved in this order:
|
|
6
|
+
|
|
7
|
+
1. Command flags: `--profile`, `--region`, `--client-id`, `--house-id`
|
|
8
|
+
2. Environment variables: `YEELIGHT_HOME_PROFILE`, `YEELIGHT_CLOUD_REGION`, `YEELIGHT_HOME_CLIENT_ID`, `YEELIGHT_HOME_HOUSE_ID`, `YEELIGHT_HOME_ACCESS_TOKEN`
|
|
9
|
+
3. Active profile, profile metadata, and credential store
|
|
10
|
+
4. Defaults: profile `default`, region `dev`
|
|
11
|
+
|
|
12
|
+
`YEELIGHT_API_BASE_URL` is a developer override for local testing. Do not expose it in Skill responses or user-facing automation.
|
|
13
|
+
|
|
14
|
+
## Profiles
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
yeelight-home profile list --json
|
|
18
|
+
yeelight-home profile show --profile default --json
|
|
19
|
+
yeelight-home profile use --profile family --region cn --client-id <client-id> --house-id <house-id>
|
|
20
|
+
yeelight-home profile delete --profile family
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`profile use` saves metadata and sets the active local profile. Profile metadata is stored in `~/.yeelight-home/config/profiles.json` by default. It contains active profile, region, client id, house id, and QR device metadata. It must not contain access tokens.
|
|
24
|
+
|
|
25
|
+
## Credentials
|
|
26
|
+
|
|
27
|
+
Preferred login:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
yeelight-home auth login --qr --region cn --profile default
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Manual token import:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
yeelight-home auth token set --profile default --token <access-token> --region cn --client-id <client-id> --house-id <house-id>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Status:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
yeelight-home auth status --json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Tokens are loaded from `YEELIGHT_HOME_ACCESS_TOKEN` first, then from the local credential store. Status and doctor output only report whether a token is present.
|
|
46
|
+
|
|
47
|
+
## Home Selection
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
yeelight-home home list --json
|
|
51
|
+
yeelight-home home select --house-id <house-id> --profile default
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`home select` changes only local profile metadata. It does not modify Yeelight cloud home data.
|
|
55
|
+
|
|
56
|
+
## Diagnostics
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
yeelight-home doctor --json
|
|
60
|
+
yeelight-home api smoke --json --region cn --profile default
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`doctor` checks local installation, paths, profile, region, token presence, and Runtime storage status. `api smoke` performs read-only cloud checks when credentials are configured.
|
package/DISTRIBUTION.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Distribution
|
|
2
|
+
|
|
3
|
+
`yeelight-home` is distributed as a standalone CLI. The public source and release repository is `Yeelight/yeelight-home`; Skill packages should depend on the installed CLI through `YEELIGHT_HOME_BIN` or `PATH`.
|
|
4
|
+
|
|
5
|
+
## Channels
|
|
6
|
+
|
|
7
|
+
| Channel | Status | User install path |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| GitHub Releases | Published | `curl -fsSL https://github.com/yeelight/yeelight-home/releases/latest/download/install.sh \| sh` |
|
|
10
|
+
| Homebrew | Published | `brew install Yeelight/tap/yeelight-home` |
|
|
11
|
+
| Scoop | Published | `scoop bucket add yeelight https://github.com/Yeelight/scoop-bucket && scoop install yeelight-home` |
|
|
12
|
+
| Debian package | Published | Download `yeelight-home_0.1.0_amd64.deb` or `yeelight-home_0.1.0_arm64.deb` from GitHub Releases |
|
|
13
|
+
| Winget | Submitted | `winget install Yeelight.yeelight-home` after microsoft/winget-pkgs PR 392555 is merged |
|
|
14
|
+
| npm | Published | `npm install -g yeelight-home` |
|
|
15
|
+
|
|
16
|
+
## Repository Layout Policy
|
|
17
|
+
|
|
18
|
+
- Keep `Yeelight/yeelight-home` as the public Runtime source and release repository.
|
|
19
|
+
- Keep `Yeelight/homebrew-tap` as a shared Homebrew tap for all Yeelight formulas. This is conventional because GitHub taps use the `homebrew-` prefix for the short `brew tap Yeelight/tap` form.
|
|
20
|
+
- Scoop does not require a Yeelight organization repository or a dedicated repository. It only needs a Git bucket repository containing JSON manifests. `Yeelight/scoop-bucket` is valid and already published; if repository count becomes a problem, future Scoop manifests can move into a consolidated distribution repository, but the existing bucket should remain as a compatibility pointer.
|
|
21
|
+
- Winget does not require a Yeelight repository. Official publication happens through `microsoft/winget-pkgs`; any personal fork is only a PR workspace.
|
|
22
|
+
- npm does not require a GitHub repository. It requires an npm account or automation token with permission to publish `yeelight-home` or the chosen scoped package.
|
|
23
|
+
|
|
24
|
+
## npm Package Model
|
|
25
|
+
|
|
26
|
+
The npm package is a thin installer and launcher:
|
|
27
|
+
|
|
28
|
+
1. `postinstall` downloads the matching GitHub Release asset for the user's OS and CPU.
|
|
29
|
+
2. The asset checksum is verified against `checksums.txt`.
|
|
30
|
+
3. The binary is cached under the user's local cache directory.
|
|
31
|
+
4. The npm `yeelight-home` binary delegates all arguments to the cached Go Runtime binary.
|
|
32
|
+
|
|
33
|
+
Environment overrides:
|
|
34
|
+
|
|
35
|
+
- `YEELIGHT_HOME_REPO=owner/repo`
|
|
36
|
+
- `YEELIGHT_HOME_VERSION=yeelight-home-v0.1.0` or `latest`
|
|
37
|
+
- `YEELIGHT_HOME_NPM_CACHE_DIR=/custom/cache`
|
|
38
|
+
- `YEELIGHT_HOME_NPM_SKIP_INSTALL=1`
|
package/INSTALL.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
## GitHub Releases
|
|
4
|
+
|
|
5
|
+
macOS and Linux:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
curl -fsSL https://github.com/yeelight/yeelight-home/releases/latest/download/install.sh | sh
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Windows PowerShell:
|
|
12
|
+
|
|
13
|
+
```powershell
|
|
14
|
+
iwr https://github.com/yeelight/yeelight-home/releases/latest/download/install.ps1 -UseB | iex
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Override the install source when testing a fork:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
YEELIGHT_HOME_REPO=owner/repo YEELIGHT_HOME_VERSION=yeelight-home-v1.0.0 sh install.sh
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
PowerShell:
|
|
24
|
+
|
|
25
|
+
```powershell
|
|
26
|
+
$env:YEELIGHT_HOME_REPO="owner/repo"
|
|
27
|
+
$env:YEELIGHT_HOME_VERSION="yeelight-home-v1.0.0"
|
|
28
|
+
.\install.ps1
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Package Managers
|
|
32
|
+
|
|
33
|
+
Homebrew:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
brew install Yeelight/tap/yeelight-home
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Scoop:
|
|
40
|
+
|
|
41
|
+
```powershell
|
|
42
|
+
scoop bucket add yeelight https://github.com/Yeelight/scoop-bucket
|
|
43
|
+
scoop install yeelight-home
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Debian/Ubuntu:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
curl -LO https://github.com/Yeelight/yeelight-home/releases/download/yeelight-home-v0.1.0/yeelight-home_0.1.0_amd64.deb
|
|
50
|
+
sudo apt install ./yeelight-home_0.1.0_amd64.deb
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Winget:
|
|
54
|
+
|
|
55
|
+
- Submitted for review: https://github.com/microsoft/winget-pkgs/pull/392555
|
|
56
|
+
- After merge: `winget install Yeelight.yeelight-home`
|
|
57
|
+
|
|
58
|
+
npm:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
npm install -g yeelight-home
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Verify
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
yeelight-home version
|
|
68
|
+
yeelight-home doctor --json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Uninstall
|
|
72
|
+
|
|
73
|
+
Remove the binary from the install directory and delete local Runtime data only when you intend to remove credentials and local preferences:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
rm -f /usr/local/bin/yeelight-home
|
|
77
|
+
rm -rf ~/.yeelight-home
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
On Windows, remove `%LOCALAPPDATA%\Programs\YeelightHome\bin\yeelight-home.exe` and remove that directory from the user `Path` if it was added by the installer.
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# yeelight-home
|
|
2
|
+
|
|
3
|
+
`yeelight-home` is the local Runtime CLI for Yeelight smart-home Skills. It keeps credentials on the user's machine, resolves semantic Skill requests, calls Yeelight cloud APIs directly, and returns redacted structured results.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Install from the public runtime release repository:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
curl -fsSL https://github.com/yeelight/yeelight-home/releases/latest/download/install.sh | sh
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Windows PowerShell:
|
|
14
|
+
|
|
15
|
+
```powershell
|
|
16
|
+
iwr https://github.com/yeelight/yeelight-home/releases/latest/download/install.ps1 -UseB | iex
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Homebrew:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
brew install Yeelight/tap/yeelight-home
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Scoop:
|
|
26
|
+
|
|
27
|
+
```powershell
|
|
28
|
+
scoop bucket add yeelight https://github.com/Yeelight/scoop-bucket
|
|
29
|
+
scoop install yeelight-home
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Debian/Ubuntu users can download the `yeelight-home_0.1.0_amd64.deb` or `yeelight-home_0.1.0_arm64.deb` asset from the GitHub Release and install it with `apt` or `dpkg`.
|
|
33
|
+
|
|
34
|
+
Npm:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npm install -g yeelight-home
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Winget publication is submitted for review at https://github.com/microsoft/winget-pkgs/pull/392555. Until it is merged, use GitHub Releases, Homebrew, Scoop, or set `YEELIGHT_HOME_BIN` to an absolute `yeelight-home` executable path.
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
yeelight-home version
|
|
46
|
+
yeelight-home auth status --json
|
|
47
|
+
yeelight-home auth login --qr --region dev
|
|
48
|
+
yeelight-home home list --json
|
|
49
|
+
yeelight-home home select --house-id <house-id>
|
|
50
|
+
yeelight-home doctor --json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For non-interactive local setup, import a token outside chat:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
yeelight-home auth token set --token <access-token> --region cn --client-id <client-id> --house-id <house-id>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Do not paste tokens into AI chat. The CLI stores tokens in the system credential store when available, and otherwise in a protected local credential fallback under the Runtime config directory.
|
|
60
|
+
|
|
61
|
+
## Skill Integration
|
|
62
|
+
|
|
63
|
+
Skills should call only:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
yeelight-home invoke --stdin
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The Yeelight Smart Home Skill wrapper resolves the Runtime in this order:
|
|
70
|
+
|
|
71
|
+
1. `YEELIGHT_HOME_BIN`
|
|
72
|
+
2. development-only bundled binary when present in a source checkout
|
|
73
|
+
3. `yeelight-home` on `PATH`
|
|
74
|
+
|
|
75
|
+
No Skill should call raw URLs, headers, curl, MCP compatibility services, or token-bearing commands.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const { spawnSync } = require("node:child_process");
|
|
6
|
+
const { ensureRuntimeBinary } = require("../lib/runtime-installer");
|
|
7
|
+
|
|
8
|
+
const result = ensureRuntimeBinary();
|
|
9
|
+
if (!result.ok) {
|
|
10
|
+
console.error(result.message);
|
|
11
|
+
process.exit(result.code || 1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const child = spawnSync(result.binaryPath, process.argv.slice(2), {
|
|
15
|
+
stdio: "inherit",
|
|
16
|
+
windowsHide: false
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (child.error) {
|
|
20
|
+
console.error(child.error.message);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
process.exit(child.status === null ? 1 : child.status);
|
package/npm/install.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const { ensureRuntimeBinary } = require("./lib/runtime-installer");
|
|
6
|
+
|
|
7
|
+
if (process.env.YEELIGHT_HOME_NPM_SKIP_INSTALL === "1") {
|
|
8
|
+
process.exit(0);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const result = ensureRuntimeBinary();
|
|
12
|
+
if (!result.ok) {
|
|
13
|
+
console.error(result.message);
|
|
14
|
+
process.exit(result.code || 1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
console.log(`yeelight-home runtime installed at ${result.binaryPath}`);
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const os = require("node:os");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const crypto = require("node:crypto");
|
|
7
|
+
const { spawnSync } = require("node:child_process");
|
|
8
|
+
|
|
9
|
+
const packageRoot = path.resolve(__dirname, "..", "..");
|
|
10
|
+
const packageInfo = require(path.join(packageRoot, "package.json"));
|
|
11
|
+
|
|
12
|
+
function ensureRuntimeBinary() {
|
|
13
|
+
let target;
|
|
14
|
+
try {
|
|
15
|
+
target = resolveTarget();
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return { ok: false, code: 2, message: error.message };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const binaryPath = resolveBinaryPath(target);
|
|
21
|
+
if (fs.existsSync(binaryPath)) {
|
|
22
|
+
return { ok: true, binaryPath };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
installRuntime(target, binaryPath);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
code: 1,
|
|
31
|
+
message: `failed to install yeelight-home runtime: ${error.message}`
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { ok: true, binaryPath };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resolveTarget() {
|
|
39
|
+
const platformMap = {
|
|
40
|
+
darwin: "darwin",
|
|
41
|
+
linux: "linux",
|
|
42
|
+
win32: "windows"
|
|
43
|
+
};
|
|
44
|
+
const archMap = {
|
|
45
|
+
x64: "amd64",
|
|
46
|
+
arm64: "arm64"
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const goos = platformMap[process.platform];
|
|
50
|
+
const goarch = archMap[process.arch];
|
|
51
|
+
if (!goos || !goarch) {
|
|
52
|
+
throw new Error(`unsupported platform: ${process.platform}/${process.arch}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const repo = process.env.YEELIGHT_HOME_REPO || "Yeelight/yeelight-home";
|
|
56
|
+
const version = process.env.YEELIGHT_HOME_VERSION || `yeelight-home-v${packageInfo.version}`;
|
|
57
|
+
const extension = goos === "windows" ? "zip" : "tar.gz";
|
|
58
|
+
const assetName = `yeelight-home-${goos}-${goarch}.${extension}`;
|
|
59
|
+
const releasePath = version === "latest" ? "latest/download" : `download/${version}`;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
repo,
|
|
63
|
+
version,
|
|
64
|
+
goos,
|
|
65
|
+
goarch,
|
|
66
|
+
assetName,
|
|
67
|
+
binaryName: goos === "windows" ? "yeelight-home.exe" : "yeelight-home",
|
|
68
|
+
assetUrl: `https://github.com/${repo}/releases/${releasePath}/${assetName}`,
|
|
69
|
+
checksumsUrl: `https://github.com/${repo}/releases/${releasePath}/checksums.txt`
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveBinaryPath(target) {
|
|
74
|
+
const cacheRoot = process.env.YEELIGHT_HOME_NPM_CACHE_DIR || defaultCacheRoot();
|
|
75
|
+
const repoKey = target.repo.replace(/[^A-Za-z0-9_.-]+/g, "-");
|
|
76
|
+
return path.join(cacheRoot, repoKey, target.version, `${target.goos}-${target.goarch}`, target.binaryName);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function defaultCacheRoot() {
|
|
80
|
+
if (process.platform === "win32" && process.env.LOCALAPPDATA) {
|
|
81
|
+
return path.join(process.env.LOCALAPPDATA, "YeelightHome", "npm");
|
|
82
|
+
}
|
|
83
|
+
if (process.platform === "darwin") {
|
|
84
|
+
return path.join(os.homedir(), "Library", "Caches", "yeelight-home", "npm");
|
|
85
|
+
}
|
|
86
|
+
return path.join(os.homedir(), ".cache", "yeelight-home", "npm");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function installRuntime(target, binaryPath) {
|
|
90
|
+
const workDir = fs.mkdtempSync(path.join(os.tmpdir(), "yeelight-home-npm-"));
|
|
91
|
+
try {
|
|
92
|
+
const assetPath = path.join(workDir, target.assetName);
|
|
93
|
+
const checksumsPath = path.join(workDir, "checksums.txt");
|
|
94
|
+
downloadFile(target.assetUrl, assetPath);
|
|
95
|
+
downloadFile(target.checksumsUrl, checksumsPath);
|
|
96
|
+
verifyChecksum(assetPath, checksumsPath, target.assetName);
|
|
97
|
+
|
|
98
|
+
fs.mkdirSync(path.dirname(binaryPath), { recursive: true });
|
|
99
|
+
if (target.goos === "windows") {
|
|
100
|
+
extractZip(assetPath, target.binaryName, binaryPath, workDir);
|
|
101
|
+
} else {
|
|
102
|
+
extractTarGz(assetPath, target.binaryName, binaryPath, workDir);
|
|
103
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
104
|
+
}
|
|
105
|
+
} finally {
|
|
106
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function downloadFile(url, outputPath) {
|
|
111
|
+
const response = spawnSync(process.execPath, [
|
|
112
|
+
"-e",
|
|
113
|
+
`
|
|
114
|
+
const fs = require("node:fs");
|
|
115
|
+
const https = require("node:https");
|
|
116
|
+
const url = process.argv[1];
|
|
117
|
+
const out = process.argv[2];
|
|
118
|
+
function get(current, redirects) {
|
|
119
|
+
https.get(current, res => {
|
|
120
|
+
if ([301, 302, 303, 307, 308].includes(res.statusCode)) {
|
|
121
|
+
if (redirects <= 0) throw new Error("too many redirects");
|
|
122
|
+
res.resume();
|
|
123
|
+
get(new URL(res.headers.location, current).toString(), redirects - 1);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (res.statusCode !== 200) {
|
|
127
|
+
console.error("download failed: " + res.statusCode + " " + current);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
const file = fs.createWriteStream(out, { mode: 0o600 });
|
|
131
|
+
res.pipe(file);
|
|
132
|
+
file.on("finish", () => file.close(() => process.exit(0)));
|
|
133
|
+
}).on("error", err => {
|
|
134
|
+
console.error(err.message);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
get(url, 5);
|
|
139
|
+
`,
|
|
140
|
+
url,
|
|
141
|
+
outputPath
|
|
142
|
+
], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
|
143
|
+
|
|
144
|
+
if (response.status !== 0) {
|
|
145
|
+
throw new Error((response.stderr || "").trim() || `download failed: ${url}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function verifyChecksum(assetPath, checksumsPath, assetName) {
|
|
150
|
+
const checksums = fs.readFileSync(checksumsPath, "utf8").split(/\r?\n/);
|
|
151
|
+
let expected = "";
|
|
152
|
+
for (const line of checksums) {
|
|
153
|
+
const parts = line.trim().split(/\s+/);
|
|
154
|
+
if (parts.length >= 2 && parts[1] === assetName) {
|
|
155
|
+
expected = parts[0];
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (!expected) {
|
|
160
|
+
throw new Error(`checksum not found for ${assetName}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const actual = crypto.createHash("sha256").update(fs.readFileSync(assetPath)).digest("hex");
|
|
164
|
+
if (actual !== expected) {
|
|
165
|
+
throw new Error(`checksum mismatch for ${assetName}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function extractTarGz(assetPath, binaryName, binaryPath, workDir) {
|
|
170
|
+
const extractDir = path.join(workDir, "tar");
|
|
171
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
172
|
+
const result = spawnSync("tar", ["-xzf", assetPath, "-C", extractDir], {
|
|
173
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
174
|
+
encoding: "utf8"
|
|
175
|
+
});
|
|
176
|
+
if (result.status !== 0) {
|
|
177
|
+
throw new Error((result.stderr || "").trim() || "tar extraction failed");
|
|
178
|
+
}
|
|
179
|
+
const extractedBinary = path.join(extractDir, binaryName);
|
|
180
|
+
if (!fs.existsSync(extractedBinary)) {
|
|
181
|
+
throw new Error(`${binaryName} not found in ${path.basename(assetPath)}`);
|
|
182
|
+
}
|
|
183
|
+
fs.copyFileSync(extractedBinary, binaryPath);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function extractZip(assetPath, binaryName, binaryPath, workDir) {
|
|
187
|
+
const extractDir = path.join(workDir, "zip");
|
|
188
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
189
|
+
const script = `Expand-Archive -LiteralPath '${assetPath.replace(/'/g, "''")}' -DestinationPath '${extractDir.replace(/'/g, "''")}' -Force`;
|
|
190
|
+
const result = spawnSync("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script], {
|
|
191
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
192
|
+
encoding: "utf8"
|
|
193
|
+
});
|
|
194
|
+
if (result.status !== 0) {
|
|
195
|
+
throw new Error((result.stderr || "").trim() || "PowerShell Expand-Archive failed");
|
|
196
|
+
}
|
|
197
|
+
const extractedBinary = path.join(extractDir, binaryName);
|
|
198
|
+
if (!fs.existsSync(extractedBinary)) {
|
|
199
|
+
throw new Error(`${binaryName} not found in ${path.basename(assetPath)}`);
|
|
200
|
+
}
|
|
201
|
+
fs.copyFileSync(extractedBinary, binaryPath);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
ensureRuntimeBinary
|
|
206
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yeelight-home",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local Yeelight Home Runtime CLI installer and launcher.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"homepage": "https://github.com/Yeelight/yeelight-home#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Yeelight/yeelight-home.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/Yeelight/yeelight-home/issues"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"yeelight-home": "npm/bin/yeelight-home.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"npm/",
|
|
19
|
+
"README.md",
|
|
20
|
+
"INSTALL.md",
|
|
21
|
+
"CONFIG.md",
|
|
22
|
+
"DISTRIBUTION.md"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"postinstall": "node npm/install.js",
|
|
26
|
+
"smoke": "node npm/bin/yeelight-home.js version"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"os": [
|
|
32
|
+
"darwin",
|
|
33
|
+
"linux",
|
|
34
|
+
"win32"
|
|
35
|
+
],
|
|
36
|
+
"cpu": [
|
|
37
|
+
"x64",
|
|
38
|
+
"arm64"
|
|
39
|
+
],
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
}
|
|
43
|
+
}
|