@slush-openclaw/garmin-messenger 1.2.5

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.
@@ -0,0 +1,28 @@
1
+ {
2
+ "id": "garmin-messenger",
3
+ "name": "Garmin Messenger",
4
+ "description": "Send and receive messages via Garmin Messenger (Hermes protocol)",
5
+ "channels": ["garmin-messenger"],
6
+ "configSchema": {
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "enabled": { "type": "boolean", "description": "Enable the Garmin Messenger channel" },
11
+ "binaryPath": { "type": "string", "description": "Path to garmin-messenger binary (auto-detected if omitted)" },
12
+ "sessionDir": { "type": "string", "description": "Directory for saved credentials" },
13
+ "verbose": { "type": "boolean", "description": "Enable debug logging for the MCP binary" },
14
+ "dmPolicy": { "type": "string", "enum": ["open", "pairing", "allowlist"], "description": "DM policy: open, pairing, or allowlist" },
15
+ "allowFrom": { "type": "array", "items": { "type": "string" }, "description": "Phone numbers allowed to send messages (for allowlist policy)" }
16
+ }
17
+ },
18
+ "uiHints": {
19
+ "binaryPath": {
20
+ "label": "Binary Path",
21
+ "advanced": true
22
+ },
23
+ "sessionDir": {
24
+ "label": "Session Directory",
25
+ "advanced": true
26
+ }
27
+ }
28
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@slush-openclaw/garmin-messenger",
3
+ "version": "1.2.5",
4
+ "description": "OpenClaw channel plugin for Garmin Messenger (Hermes)",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/slush-dev/garmin-messenger.git",
8
+ "directory": "apps/openclaw-plugin"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "type": "module",
14
+ "main": "dist/index.cjs",
15
+ "scripts": {
16
+ "build": "node build.mjs",
17
+ "postinstall": "node scripts/postinstall.mjs",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest"
20
+ },
21
+ "files": [
22
+ "dist/",
23
+ "scripts/postinstall.mjs",
24
+ "openclaw.plugin.json",
25
+ "checksums.json"
26
+ ],
27
+ "devDependencies": {
28
+ "@modelcontextprotocol/sdk": "^1.26.0",
29
+ "@sinclair/typebox": "^0.34.48",
30
+ "@types/node": "^25.2.2",
31
+ "esbuild": "^0.27.0",
32
+ "openclaw": "^2026.2.9",
33
+ "typescript": "^5.9.0",
34
+ "vitest": "^4.0.0"
35
+ },
36
+ "overrides": {
37
+ "cmake-js": "^8.0.0",
38
+ "rimraf": "^6.0.0"
39
+ },
40
+ "openclaw": {
41
+ "extensions": [
42
+ "./dist/index.cjs"
43
+ ],
44
+ "channel": {
45
+ "id": "garmin-messenger",
46
+ "label": "Garmin Messenger",
47
+ "selectionLabel": "Garmin Messenger",
48
+ "docsPath": "/channels/garmin-messenger",
49
+ "blurb": "Send and receive messages via Garmin Messenger (Hermes protocol)"
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,132 @@
1
+ // Plain-JS postinstall script — runs under bare `node` (no jiti / tsx).
2
+ // Inlines platform constants so there are no .ts imports.
3
+
4
+ import { createWriteStream, chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
5
+ import { join, dirname } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { createHash } from "node:crypto";
8
+ import { get as httpGet } from "node:http";
9
+ import { get as httpsGet } from "node:https";
10
+
11
+ const BINARY_NAME = "garmin-messenger";
12
+ const GITHUB_REPO = "slush-dev/garmin-messenger";
13
+ const MAX_REDIRECTS = 5;
14
+
15
+ function platformSuffix() {
16
+ const os = process.platform === "win32" ? "windows" : process.platform;
17
+ const archMap = { x64: "amd64", arm64: "arm64" };
18
+ const arch = archMap[process.arch] ?? process.arch;
19
+ const ext = process.platform === "win32" ? ".exe" : "";
20
+ return `${os}-${arch}${ext}`;
21
+ }
22
+
23
+ export function buildDownloadURL(version, suffix) {
24
+ return `https://github.com/${GITHUB_REPO}/releases/download/v${version}/${BINARY_NAME}-${suffix}`;
25
+ }
26
+
27
+ export function verifyChecksum(filePath, expectedDigest) {
28
+ const data = readFileSync(filePath);
29
+ const actual = createHash("sha256").update(data).digest("hex");
30
+ const expected = expectedDigest.replace(/^sha256:/, "");
31
+ if (actual !== expected) {
32
+ unlinkSync(filePath);
33
+ throw new Error(`Checksum mismatch: expected ${expected}, got ${actual}`);
34
+ }
35
+ }
36
+
37
+ export function downloadBinary(url, destPath) {
38
+ return new Promise((resolve, reject) => {
39
+ let redirects = 0;
40
+
41
+ function follow(currentUrl) {
42
+ if (redirects > MAX_REDIRECTS) {
43
+ reject(new Error("Too many redirects"));
44
+ return;
45
+ }
46
+
47
+ const getter = currentUrl.startsWith("https://") ? httpsGet : httpGet;
48
+
49
+ getter(currentUrl, (res) => {
50
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
51
+ const location = res.headers.location;
52
+ if (currentUrl.startsWith("https://") && location.startsWith("http://")) {
53
+ res.resume();
54
+ reject(new Error("Refusing HTTPS to HTTP redirect downgrade"));
55
+ return;
56
+ }
57
+ redirects++;
58
+ follow(location);
59
+ return;
60
+ }
61
+
62
+ if (res.statusCode !== 200) {
63
+ res.resume();
64
+ reject(new Error(`Download failed: HTTP ${res.statusCode}`));
65
+ return;
66
+ }
67
+
68
+ mkdirSync(dirname(destPath), { recursive: true });
69
+ const file = createWriteStream(destPath);
70
+ res.pipe(file);
71
+ file.on("finish", () => {
72
+ file.close(() => {
73
+ chmodSync(destPath, 0o755);
74
+ resolve();
75
+ });
76
+ });
77
+ file.on("error", (err) => {
78
+ reject(err);
79
+ });
80
+ }).on("error", (err) => {
81
+ reject(err);
82
+ });
83
+ }
84
+
85
+ follow(url);
86
+ });
87
+ }
88
+
89
+ async function main() {
90
+ const __dirname = dirname(fileURLToPath(import.meta.url));
91
+ const pkgPath = join(__dirname, "..", "package.json");
92
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
93
+ const version = pkg.version;
94
+
95
+ const suffix = platformSuffix();
96
+ const binDir = join(__dirname, "..", "bin");
97
+ const destPath = join(binDir, `${BINARY_NAME}-${suffix}`);
98
+
99
+ if (existsSync(destPath)) {
100
+ console.log(`[garmin-messenger] Binary already exists: ${destPath}`);
101
+ return;
102
+ }
103
+
104
+ const url = buildDownloadURL(version, suffix);
105
+ console.log(`[garmin-messenger] Downloading ${url}`);
106
+
107
+ try {
108
+ await downloadBinary(url, destPath);
109
+
110
+ const checksumsPath = join(__dirname, "..", "checksums.json");
111
+ if (existsSync(checksumsPath)) {
112
+ const checksums = JSON.parse(readFileSync(checksumsPath, "utf-8"));
113
+ const assetName = `${BINARY_NAME}-${suffix}`;
114
+ const expectedDigest = checksums[assetName];
115
+ if (expectedDigest) {
116
+ verifyChecksum(destPath, expectedDigest);
117
+ console.log(`[garmin-messenger] Checksum verified`);
118
+ } else {
119
+ console.warn(`[garmin-messenger] No checksum found for ${assetName}, skipping verification`);
120
+ }
121
+ }
122
+
123
+ console.log(`[garmin-messenger] Installed to ${destPath}`);
124
+ } catch (err) {
125
+ console.warn(
126
+ `[garmin-messenger] Failed to download binary: ${err instanceof Error ? err.message : err}\n` +
127
+ ` The plugin will try to find '${BINARY_NAME}' on your PATH instead.`,
128
+ );
129
+ }
130
+ }
131
+
132
+ main();