odelic 0.2.0 → 0.2.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 +93 -91
- package/bin/odelic-mcp.js +1 -1
- package/bin/odelic.js +1 -1
- package/dist/cli.d.ts +58 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +279 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +140 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +448 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +34 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +139 -0
- package/dist/config.js.map +1 -0
- package/dist/controller.d.ts +95 -0
- package/dist/controller.d.ts.map +1 -0
- package/dist/controller.js +239 -0
- package/dist/controller.js.map +1 -0
- package/dist/crypto.d.ts +34 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +111 -0
- package/dist/crypto.js.map +1 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +17 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/{src → dist}/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +18 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +21 -0
- package/dist/log.js.map +1 -0
- package/dist/mcp.d.ts +23 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +74 -0
- package/dist/mcp.js.map +1 -0
- package/dist/protocol.d.ts +90 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +178 -0
- package/dist/protocol.js.map +1 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +187 -0
- package/dist/server.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +19 -7
- package/src/{cli.js → cli.ts} +64 -50
- package/src/client.ts +497 -0
- package/src/{config.js → config.ts} +37 -23
- package/src/controller.ts +294 -0
- package/src/{crypto.js → crypto.ts} +46 -28
- package/src/{errors.js → errors.ts} +2 -2
- package/src/index.ts +17 -0
- package/src/log.ts +24 -0
- package/src/{mcp.js → mcp.ts} +31 -20
- package/src/{protocol.js → protocol.ts} +56 -30
- package/src/{server.js → server.ts} +57 -38
- package/src/version.ts +1 -0
- package/src/client.js +0 -362
- package/src/controller.js +0 -219
- package/src/log.js +0 -20
- package/src/version.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IncomingMessage, Server, ServerResponse } from 'node:http';
|
|
2
|
+
import type { Controller } from './controller.ts';
|
|
3
|
+
export declare const LOOPBACK: readonly string[];
|
|
4
|
+
/** The part of Controller the HTTP API uses. */
|
|
5
|
+
export type HttpController = Pick<Controller, 'info' | 'lights' | 'status' | 'on' | 'off' | 'setLight' | 'nightLight' | 'group' | 'scene'>;
|
|
6
|
+
type Params = Record<string, unknown>;
|
|
7
|
+
export declare class HttpError extends Error {
|
|
8
|
+
readonly status: number;
|
|
9
|
+
constructor(status: number, message: string);
|
|
10
|
+
}
|
|
11
|
+
/** Route one request to the controller. Returns the JSON-able result. */
|
|
12
|
+
export declare function dispatch(ctl: HttpController, method: string, path: string, params: Params): Promise<unknown>;
|
|
13
|
+
/** A `(req, res)` handler for node:http. */
|
|
14
|
+
export declare function makeHandler(ctl: HttpController, token?: string | null): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
15
|
+
export interface ServeOptions {
|
|
16
|
+
host?: string;
|
|
17
|
+
port?: number;
|
|
18
|
+
/** bearer token; required when listening on a non-loopback address */
|
|
19
|
+
token?: string | null;
|
|
20
|
+
}
|
|
21
|
+
/** Start the HTTP API. Resolves to the listening server; closing the server also closes the controller. */
|
|
22
|
+
export declare function serve(ctl: Controller, { host, port, token }?: ServeOptions): Promise<Server>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAMzE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,eAAO,MAAM,QAAQ,EAAE,SAAS,MAAM,EAAsC,CAAC;AAG7E,gDAAgD;AAChD,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,UAAU,EAC1C,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,IAAI,GAAG,KAAK,GAAG,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,OAAO,CAAC,CAAC;AAE/F,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtC,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,YAAY,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAI1C;CACF;AAQD,yEAAyE;AACzE,wBAAsB,QAAQ,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAkClH;AA2BD,4CAA4C;AAC5C,wBAAgB,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,GAAE,MAAM,GAAG,IAAW,SACvD,eAAe,OAAO,cAAc,KAAG,OAAO,CAAC,IAAI,CAAC,CA4CxE;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,2GAA2G;AAC3G,wBAAsB,KAAK,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,IAAkB,EAAE,IAAW,EAAE,KAAY,EAAE,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0BlI"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny dependency-free HTTP/JSON API on top of Controller.
|
|
3
|
+
*
|
|
4
|
+
* GET /health
|
|
5
|
+
* GET /lights list lights (ping)
|
|
6
|
+
* GET /lights/<target> state ("all" = every light)
|
|
7
|
+
* POST /lights/<target>/on | off
|
|
8
|
+
* POST /lights/<target>/light {"color": 0..100, "brightness": 1..100}
|
|
9
|
+
* POST /lights/<target>/night {"level": 0..2}
|
|
10
|
+
* POST /groups/<id>/on | off | light
|
|
11
|
+
* POST /scenes/<index>/play | save
|
|
12
|
+
*
|
|
13
|
+
* Parameters may be sent as a JSON body or as query parameters. <target> is a light name, "all" or a vaddr.
|
|
14
|
+
* When a token is configured every request needs `Authorization: Bearer <token>` (or ?token=...).
|
|
15
|
+
*/
|
|
16
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
17
|
+
import http from 'node:http';
|
|
18
|
+
import { InputError } from './errors.js';
|
|
19
|
+
import { log } from './log.js';
|
|
20
|
+
import { toInt } from './controller.js';
|
|
21
|
+
export const LOOPBACK = ['127.0.0.1', 'localhost', '::1'];
|
|
22
|
+
const MAX_BODY = 65536;
|
|
23
|
+
export class HttpError extends Error {
|
|
24
|
+
status;
|
|
25
|
+
constructor(status, message) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'HttpError';
|
|
28
|
+
this.status = status;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function need(method, expected) {
|
|
32
|
+
if (method !== expected)
|
|
33
|
+
throw new HttpError(405, `use ${expected}`);
|
|
34
|
+
}
|
|
35
|
+
const num = (v, def) => (v ?? def);
|
|
36
|
+
/** Route one request to the controller. Returns the JSON-able result. */
|
|
37
|
+
export async function dispatch(ctl, method, path, params) {
|
|
38
|
+
let parts;
|
|
39
|
+
try {
|
|
40
|
+
parts = path.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean).map(decodeURIComponent);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw new HttpError(400, 'bad URL encoding');
|
|
44
|
+
}
|
|
45
|
+
if (parts.length === 1 && parts[0] === 'health')
|
|
46
|
+
return { ok: true, ...ctl.info() };
|
|
47
|
+
if (!parts.length)
|
|
48
|
+
throw new HttpError(404, 'not found');
|
|
49
|
+
const [head] = parts;
|
|
50
|
+
if (head === 'lights') {
|
|
51
|
+
if (parts.length === 1) {
|
|
52
|
+
need(method, 'GET');
|
|
53
|
+
return ctl.lights();
|
|
54
|
+
}
|
|
55
|
+
const target = parts[1];
|
|
56
|
+
if (parts.length === 2) {
|
|
57
|
+
need(method, 'GET');
|
|
58
|
+
return ctl.status(target);
|
|
59
|
+
}
|
|
60
|
+
need(method, 'POST');
|
|
61
|
+
const act = parts[2];
|
|
62
|
+
if (act === 'on')
|
|
63
|
+
return ctl.on(target);
|
|
64
|
+
if (act === 'off')
|
|
65
|
+
return ctl.off(target);
|
|
66
|
+
if (act === 'light')
|
|
67
|
+
return ctl.setLight(target, num(params.color, 50), num(params.brightness, 100));
|
|
68
|
+
if (act === 'night')
|
|
69
|
+
return ctl.nightLight(target, num(params.level, 0));
|
|
70
|
+
}
|
|
71
|
+
else if (head === 'groups' && parts.length === 3) {
|
|
72
|
+
need(method, 'POST');
|
|
73
|
+
return ctl.group(toInt(parts[1], 'group id'), parts[2], num(params.color, 50), num(params.brightness, 100));
|
|
74
|
+
}
|
|
75
|
+
else if (head === 'scenes' && parts.length === 3 && (parts[2] === 'play' || parts[2] === 'save')) {
|
|
76
|
+
need(method, 'POST');
|
|
77
|
+
return ctl.scene(toInt(parts[1], 'scene index'), parts[2] === 'save');
|
|
78
|
+
}
|
|
79
|
+
throw new HttpError(404, 'not found');
|
|
80
|
+
}
|
|
81
|
+
function readBody(req) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const declared = Number(req.headers['content-length'] || 0);
|
|
84
|
+
if (declared > MAX_BODY) {
|
|
85
|
+
reject(new HttpError(413, 'body too large'));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const chunks = [];
|
|
89
|
+
let size = 0;
|
|
90
|
+
req.on('data', (c) => {
|
|
91
|
+
size += c.length;
|
|
92
|
+
if (size > MAX_BODY) {
|
|
93
|
+
reject(new HttpError(413, 'body too large'));
|
|
94
|
+
req.destroy();
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
chunks.push(c);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
101
|
+
req.on('error', reject);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const digest = (s) => createHash('sha256').update(String(s)).digest();
|
|
105
|
+
/** A `(req, res)` handler for node:http. */
|
|
106
|
+
export function makeHandler(ctl, token = null) {
|
|
107
|
+
return async (req, res) => {
|
|
108
|
+
let status = 200;
|
|
109
|
+
let payload;
|
|
110
|
+
try {
|
|
111
|
+
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
112
|
+
const params = Object.fromEntries(url.searchParams);
|
|
113
|
+
const body = await readBody(req);
|
|
114
|
+
if (body.length) {
|
|
115
|
+
let data;
|
|
116
|
+
try {
|
|
117
|
+
data = JSON.parse(body.toString('utf8'));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
throw new HttpError(400, 'body must be JSON');
|
|
121
|
+
}
|
|
122
|
+
if (data && typeof data === 'object' && !Array.isArray(data))
|
|
123
|
+
Object.assign(params, data);
|
|
124
|
+
}
|
|
125
|
+
if (token) {
|
|
126
|
+
const auth = req.headers.authorization ?? '';
|
|
127
|
+
const given = auth.toLowerCase().startsWith('bearer ') ? auth.slice(7) : (params.token ?? '');
|
|
128
|
+
if (!timingSafeEqual(digest(given), digest(token)))
|
|
129
|
+
throw new HttpError(401, 'bad or missing token');
|
|
130
|
+
}
|
|
131
|
+
delete params.token;
|
|
132
|
+
payload = await dispatch(ctl, (req.method ?? 'GET').toUpperCase(), url.pathname, params);
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
const err = e;
|
|
136
|
+
if (err instanceof HttpError) {
|
|
137
|
+
status = err.status;
|
|
138
|
+
}
|
|
139
|
+
else if (err instanceof InputError) {
|
|
140
|
+
status = 400;
|
|
141
|
+
}
|
|
142
|
+
else { // BLE / login failures
|
|
143
|
+
status = 502;
|
|
144
|
+
log.error('request failed:', err);
|
|
145
|
+
}
|
|
146
|
+
payload = { ok: false, error: err instanceof HttpError || err instanceof InputError ? err.message : `${err.name}: ${err.message}` };
|
|
147
|
+
}
|
|
148
|
+
const data = Buffer.from(JSON.stringify(payload ?? null), 'utf8');
|
|
149
|
+
if (res.headersSent || res.destroyed)
|
|
150
|
+
return;
|
|
151
|
+
res.writeHead(status, {
|
|
152
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
153
|
+
'Content-Length': data.length,
|
|
154
|
+
Connection: 'close',
|
|
155
|
+
});
|
|
156
|
+
res.end(data);
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/** Start the HTTP API. Resolves to the listening server; closing the server also closes the controller. */
|
|
160
|
+
export async function serve(ctl, { host = '127.0.0.1', port = 8765, token = null } = {}) {
|
|
161
|
+
if (!LOOPBACK.includes(host) && !token) {
|
|
162
|
+
throw new InputError(`refusing to listen on ${host} without a token (use --token or ODELIC_HTTP_TOKEN)`);
|
|
163
|
+
}
|
|
164
|
+
const server = http.createServer(makeHandler(ctl, token));
|
|
165
|
+
server.headersTimeout = 10_000;
|
|
166
|
+
server.requestTimeout = 10_000 + 60_000; // BLE operations (scan + login) can take a while
|
|
167
|
+
await new Promise((resolve, reject) => {
|
|
168
|
+
server.once('error', reject);
|
|
169
|
+
server.listen(port, host, () => {
|
|
170
|
+
server.removeListener('error', reject);
|
|
171
|
+
resolve();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
server.on('close', () => {
|
|
175
|
+
ctl.close().catch(() => { });
|
|
176
|
+
});
|
|
177
|
+
const addr = server.address();
|
|
178
|
+
log.warning(`HTTP API on http://${host}:${addr.port} (${ctl.keepConnected
|
|
179
|
+
? 'keeping the BLE link open' : `idle disconnect after ${ctl.idleTimeout}s`})`);
|
|
180
|
+
if (ctl.keepConnected) {
|
|
181
|
+
ctl.connect()
|
|
182
|
+
.then(() => log.warning(`connected via ${ctl.mesh.nodeName}`))
|
|
183
|
+
.catch((e) => log.warning(`initial connect failed: ${e.message}`)); // the next request retries
|
|
184
|
+
}
|
|
185
|
+
return server;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odelic",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Unofficial Node.js library for ODELIC Bluetooth lights (Pairlink connected-mesh)",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Unofficial TypeScript/Node.js library for ODELIC Bluetooth lights (Pairlink connected-mesh)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "rbstxt",
|
|
@@ -20,14 +20,22 @@
|
|
|
20
20
|
"mesh",
|
|
21
21
|
"lighting",
|
|
22
22
|
"smart-home",
|
|
23
|
-
"mcp"
|
|
23
|
+
"mcp",
|
|
24
|
+
"typescript"
|
|
24
25
|
],
|
|
25
26
|
"engines": {
|
|
26
|
-
"node": ">=
|
|
27
|
+
"node": ">=22"
|
|
27
28
|
},
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
28
30
|
"exports": {
|
|
29
|
-
".":
|
|
30
|
-
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./mcp": {
|
|
36
|
+
"types": "./dist/mcp.d.ts",
|
|
37
|
+
"default": "./dist/mcp.js"
|
|
38
|
+
},
|
|
31
39
|
"./package.json": "./package.json"
|
|
32
40
|
},
|
|
33
41
|
"bin": {
|
|
@@ -36,12 +44,16 @@
|
|
|
36
44
|
},
|
|
37
45
|
"files": [
|
|
38
46
|
"bin",
|
|
47
|
+
"dist",
|
|
39
48
|
"src",
|
|
40
49
|
"LICENSE",
|
|
41
50
|
"README.md"
|
|
42
51
|
],
|
|
43
52
|
"scripts": {
|
|
44
|
-
"
|
|
53
|
+
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
|
|
54
|
+
"typecheck": "tsc -p tsconfig.test.json",
|
|
55
|
+
"test": "node --test \"test/*.test.ts\"",
|
|
56
|
+
"prepack": "npm run build"
|
|
45
57
|
},
|
|
46
58
|
"dependencies": {
|
|
47
59
|
"@stoprocent/noble": "^2.8.0"
|
package/src/{cli.js → cli.ts}
RENAMED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
3
3
|
import { parseArgs } from 'node:util';
|
|
4
4
|
|
|
5
|
-
import { InputError, OdelicError } from './errors.
|
|
6
|
-
import { log, setLogLevel } from './log.
|
|
7
|
-
import { findNodes } from './client.
|
|
8
|
-
import { Config, configPath, parseIdle } from './config.
|
|
9
|
-
import { Controller } from './controller.
|
|
10
|
-
import { parseAppId } from './protocol.
|
|
11
|
-
import { VERSION } from './version.
|
|
5
|
+
import { InputError, OdelicError } from './errors.ts';
|
|
6
|
+
import { log, setLogLevel } from './log.ts';
|
|
7
|
+
import { findNodes } from './client.ts';
|
|
8
|
+
import { Config, configPath, parseIdle } from './config.ts';
|
|
9
|
+
import { Controller } from './controller.ts';
|
|
10
|
+
import { parseAppId } from './protocol.ts';
|
|
11
|
+
import { VERSION } from './version.ts';
|
|
12
12
|
|
|
13
13
|
const HELP = `usage: odelic [--id ID] [--node NODE] [-v] <command> [...]
|
|
14
14
|
|
|
@@ -59,10 +59,15 @@ const OPTIONS = {
|
|
|
59
59
|
host: { type: 'string' },
|
|
60
60
|
port: { type: 'string' },
|
|
61
61
|
token: { type: 'string' },
|
|
62
|
+
} as const;
|
|
63
|
+
|
|
64
|
+
type OptionName = keyof typeof OPTIONS;
|
|
65
|
+
export type CliOptions = {
|
|
66
|
+
[K in OptionName]?: (typeof OPTIONS)[K]['type'] extends 'boolean' ? boolean : string;
|
|
62
67
|
};
|
|
63
68
|
|
|
64
69
|
/** Which options each command accepts (besides the global ones). */
|
|
65
|
-
const COMMAND_OPTIONS = {
|
|
70
|
+
const COMMAND_OPTIONS: Record<string, readonly OptionName[]> = {
|
|
66
71
|
setup: ['idle', 'keep-connected'],
|
|
67
72
|
scan: [],
|
|
68
73
|
lights: [],
|
|
@@ -78,53 +83,62 @@ const COMMAND_OPTIONS = {
|
|
|
78
83
|
serve: ['host', 'port', 'token', 'idle', 'keep-connected'],
|
|
79
84
|
mcp: [],
|
|
80
85
|
};
|
|
81
|
-
const GLOBAL = ['id', 'node', 'verbose', 'help', 'version'];
|
|
86
|
+
const GLOBAL: readonly OptionName[] = ['id', 'node', 'verbose', 'help', 'version'];
|
|
87
|
+
const MAX_ARGS: Record<string, number> = { on: 1, off: 1, light: 1, night: 1, status: 1, group: 2, scene: 2, name: 3 };
|
|
82
88
|
|
|
83
|
-
function intArg(v, name, { min = -Infinity, max = Infinity
|
|
84
|
-
if (v === undefined) return
|
|
89
|
+
function intArg(v: string | undefined, name: string, { min = -Infinity, max = Infinity } = {}): number | undefined {
|
|
90
|
+
if (v === undefined) return undefined;
|
|
85
91
|
if (!/^-?\d+$/.test(v)) throw new InputError(`--${name}: invalid int value: '${v}'`);
|
|
86
92
|
const n = Number(v);
|
|
87
93
|
if (n < min || n > max) throw new InputError(`--${name}: must be between ${min} and ${max}`);
|
|
88
94
|
return n;
|
|
89
95
|
}
|
|
90
96
|
|
|
91
|
-
function choice(v, name, choices) {
|
|
92
|
-
if (!choices.includes(v
|
|
93
|
-
|
|
97
|
+
function choice<T extends string>(v: string | undefined, name: string, choices: readonly T[]): T {
|
|
98
|
+
if (!choices.includes(v as T)) {
|
|
99
|
+
throw new InputError(`${name}: expected one of ${choices.join(', ')} (got ${v === undefined ? 'nothing' : `'${v}'`})`);
|
|
100
|
+
}
|
|
101
|
+
return v as T;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface ParsedCli {
|
|
105
|
+
cmd: string;
|
|
106
|
+
args: string[];
|
|
107
|
+
opts: CliOptions;
|
|
94
108
|
}
|
|
95
109
|
|
|
96
|
-
/** Parse argv
|
|
97
|
-
export function parseCli(argv) {
|
|
110
|
+
/** Parse argv; throws InputError on bad usage. */
|
|
111
|
+
export function parseCli(argv: string[]): ParsedCli {
|
|
98
112
|
let parsed;
|
|
99
113
|
try {
|
|
100
114
|
parsed = parseArgs({ args: argv, options: OPTIONS, allowPositionals: true, strict: true });
|
|
101
115
|
} catch (e) {
|
|
102
|
-
throw new InputError(e.message);
|
|
116
|
+
throw new InputError((e as Error).message);
|
|
103
117
|
}
|
|
104
|
-
const
|
|
118
|
+
const o = parsed.values as CliOptions;
|
|
105
119
|
if (o.help || o.version) return { cmd: o.help ? 'help' : 'version', args: [], opts: o };
|
|
106
|
-
const [cmd, ...args] = positionals;
|
|
120
|
+
const [cmd, ...args] = parsed.positionals;
|
|
107
121
|
if (!cmd) throw new InputError('missing command (see odelic --help)');
|
|
108
|
-
if (!(cmd
|
|
109
|
-
for (const k of Object.keys(o)) {
|
|
122
|
+
if (!Object.hasOwn(COMMAND_OPTIONS, cmd)) throw new InputError(`unknown command '${cmd}' (see odelic --help)`);
|
|
123
|
+
for (const k of Object.keys(o) as OptionName[]) {
|
|
110
124
|
if (!GLOBAL.includes(k) && !COMMAND_OPTIONS[cmd].includes(k)) throw new InputError(`${cmd}: unrecognized option --${k}`);
|
|
111
125
|
}
|
|
112
126
|
if (o.idle !== undefined && o['keep-connected']) throw new InputError('--idle and --keep-connected are mutually exclusive');
|
|
113
127
|
if (o.idle !== undefined) parseIdle(o.idle); // validate early
|
|
114
|
-
const maxArgs =
|
|
128
|
+
const maxArgs = MAX_ARGS[cmd] ?? 0;
|
|
115
129
|
if (args.length > maxArgs) throw new InputError(`${cmd}: unexpected argument '${args[maxArgs]}'`);
|
|
116
130
|
return { cmd, args, opts: o };
|
|
117
131
|
}
|
|
118
132
|
|
|
119
|
-
function idleValue(o) {
|
|
133
|
+
function idleValue(o: CliOptions): number | null {
|
|
120
134
|
return o['keep-connected'] ? 0 : parseIdle(o.idle);
|
|
121
135
|
}
|
|
122
136
|
|
|
123
|
-
function show(obj) {
|
|
137
|
+
function show(obj: unknown): void {
|
|
124
138
|
console.log(typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2));
|
|
125
139
|
}
|
|
126
140
|
|
|
127
|
-
async function runBle(cmd, args, o, cfg) {
|
|
141
|
+
async function runBle(cmd: string, args: string[], o: CliOptions, cfg: Config): Promise<void> {
|
|
128
142
|
if (cmd === 'scan') {
|
|
129
143
|
const aid = cfg.appId(o.id);
|
|
130
144
|
const homeid = aid ? parseAppId(aid).homeid : null;
|
|
@@ -135,15 +149,15 @@ async function runBle(cmd, args, o, cfg) {
|
|
|
135
149
|
}
|
|
136
150
|
|
|
137
151
|
if (cmd === 'serve') {
|
|
138
|
-
const { serve } = await import('./server.
|
|
152
|
+
const { serve } = await import('./server.ts');
|
|
139
153
|
const ctl = new Controller(o.id, { node: o.node, config: cfg, idleTimeout: idleValue(o) });
|
|
140
154
|
const server = await serve(ctl, {
|
|
141
155
|
host: o.host ?? '127.0.0.1',
|
|
142
|
-
port: intArg(o.port, 'port', { min: 0, max: 65535
|
|
156
|
+
port: intArg(o.port, 'port', { min: 0, max: 65535 }) ?? 8765,
|
|
143
157
|
token: o.token ?? process.env.ODELIC_HTTP_TOKEN ?? null,
|
|
144
158
|
});
|
|
145
|
-
await new Promise((resolve) => {
|
|
146
|
-
const stop = () => server.close(resolve);
|
|
159
|
+
await new Promise<void>((resolve) => {
|
|
160
|
+
const stop = () => server.close(() => resolve());
|
|
147
161
|
process.once('SIGINT', stop);
|
|
148
162
|
process.once('SIGTERM', stop);
|
|
149
163
|
});
|
|
@@ -152,28 +166,28 @@ async function runBle(cmd, args, o, cfg) {
|
|
|
152
166
|
}
|
|
153
167
|
|
|
154
168
|
const target = args[0] ?? 'all';
|
|
169
|
+
const color = () => intArg(o.color, 'color') ?? 50;
|
|
170
|
+
const brightness = () => intArg(o.brightness, 'brightness') ?? 100;
|
|
155
171
|
const ctl = new Controller(o.id, { node: o.node, config: cfg, idleTimeout: 0 });
|
|
156
172
|
try {
|
|
157
173
|
if (cmd === 'lights' || cmd === 'devices') {
|
|
158
174
|
for (const d of await ctl.lights()) {
|
|
159
175
|
if (d.online) console.log(`${(d.name ?? '-').padEnd(12)} vaddr=${d.vaddr} mac=${d.mac} product=${d.product} ver=${d.version}`);
|
|
160
|
-
else console.log(`${d.name.padEnd(12)} vaddr=${d.vaddr} (offline)`);
|
|
176
|
+
else console.log(`${String(d.name).padEnd(12)} vaddr=${d.vaddr} (offline)`);
|
|
161
177
|
}
|
|
162
178
|
return;
|
|
163
179
|
}
|
|
164
180
|
if (cmd === 'on') show(await ctl.on(target));
|
|
165
181
|
else if (cmd === 'off') show(await ctl.off(target));
|
|
166
|
-
else if (cmd === 'light')
|
|
167
|
-
|
|
168
|
-
} else if (cmd === 'night') show(await ctl.nightLight(target, intArg(o.level, 'level', { min: 0, max: 2, def: 0 })));
|
|
182
|
+
else if (cmd === 'light') show(await ctl.setLight(target, color(), brightness()));
|
|
183
|
+
else if (cmd === 'night') show(await ctl.nightLight(target, intArg(o.level, 'level', { min: 0, max: 2 }) ?? 0));
|
|
169
184
|
else if (cmd === 'status') show(await ctl.status(target));
|
|
170
185
|
else if (cmd === 'group') {
|
|
171
186
|
const id = intArg(args[0], 'group_id');
|
|
172
187
|
if (id === undefined) throw new InputError('group: missing group id');
|
|
173
|
-
show(await ctl.group(id, choice(args[1], 'group action', ['on', 'off', 'light']),
|
|
174
|
-
intArg(o.color, 'color', { def: 50 }), intArg(o.brightness, 'brightness', { def: 100 })));
|
|
188
|
+
show(await ctl.group(id, choice(args[1], 'group action', ['on', 'off', 'light'] as const), color(), brightness()));
|
|
175
189
|
} else if (cmd === 'scene') {
|
|
176
|
-
const action = choice(args[0], 'scene action', ['save', 'play']);
|
|
190
|
+
const action = choice(args[0], 'scene action', ['save', 'play'] as const);
|
|
177
191
|
const index = intArg(args[1], 'index');
|
|
178
192
|
if (index === undefined) throw new InputError('scene: missing index');
|
|
179
193
|
show(await ctl.scene(index, action === 'save'));
|
|
@@ -184,17 +198,16 @@ async function runBle(cmd, args, o, cfg) {
|
|
|
184
198
|
}
|
|
185
199
|
}
|
|
186
200
|
|
|
187
|
-
/** CLI entry point. Resolves to the process exit code. */
|
|
188
|
-
export async function main(argv = process.argv.slice(2)) {
|
|
189
|
-
let
|
|
190
|
-
let args;
|
|
191
|
-
let o;
|
|
201
|
+
/** CLI entry point. Resolves to the process exit code, or null for long-running commands (`mcp`). */
|
|
202
|
+
export async function main(argv: string[] = process.argv.slice(2)): Promise<number | null> {
|
|
203
|
+
let parsed: ParsedCli;
|
|
192
204
|
try {
|
|
193
|
-
|
|
205
|
+
parsed = parseCli(argv);
|
|
194
206
|
} catch (e) {
|
|
195
|
-
console.error(`odelic: error: ${e.message}`);
|
|
207
|
+
console.error(`odelic: error: ${(e as Error).message}`);
|
|
196
208
|
return 2;
|
|
197
209
|
}
|
|
210
|
+
const { cmd, args, opts: o } = parsed;
|
|
198
211
|
if (cmd === 'help') {
|
|
199
212
|
console.log(HELP);
|
|
200
213
|
return 0;
|
|
@@ -220,7 +233,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
220
233
|
console.log(`saved to ${configPath()} (id: ${cfg.id ? 'set' : 'not set'}, connection: ${
|
|
221
234
|
mode ? `disconnect after ${mode}s idle` : 'keep connected'})`);
|
|
222
235
|
} else if (cmd === 'name') {
|
|
223
|
-
const sub = choice(args[0], 'name command', ['add', 'rm', 'list']);
|
|
236
|
+
const sub = choice(args[0], 'name command', ['add', 'rm', 'list'] as const);
|
|
224
237
|
if (sub === 'add') {
|
|
225
238
|
if (args.length !== 3) throw new InputError('usage: odelic name add NAME VADDR');
|
|
226
239
|
cfg.setName(args[1], args[2]);
|
|
@@ -232,12 +245,12 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
232
245
|
}
|
|
233
246
|
for (const [name, v] of Object.entries(cfg.lights)) console.log(`${name.padEnd(12)} ${v}`);
|
|
234
247
|
} else if (cmd === 'mcp') {
|
|
235
|
-
let mcp;
|
|
248
|
+
let mcp: typeof import('./mcp.ts');
|
|
236
249
|
try {
|
|
237
|
-
mcp = await import('./mcp.
|
|
250
|
+
mcp = await import('./mcp.ts');
|
|
238
251
|
} catch (e) {
|
|
239
|
-
if (e.code !== 'ERR_MODULE_NOT_FOUND') throw e;
|
|
240
|
-
throw new OdelicError(`${e.message} (install with: npm install @modelcontextprotocol/sdk zod)`);
|
|
252
|
+
if ((e as NodeJS.ErrnoException).code !== 'ERR_MODULE_NOT_FOUND') throw e;
|
|
253
|
+
throw new OdelicError(`${(e as Error).message} (install with: npm install @modelcontextprotocol/sdk zod)`);
|
|
241
254
|
}
|
|
242
255
|
await mcp.main();
|
|
243
256
|
return null; // keep running until stdin closes
|
|
@@ -246,8 +259,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
246
259
|
}
|
|
247
260
|
return 0;
|
|
248
261
|
} catch (e) {
|
|
249
|
-
|
|
250
|
-
|
|
262
|
+
const err = e as Error;
|
|
263
|
+
if (o.verbose || !(err instanceof OdelicError)) log.debug(err.stack);
|
|
264
|
+
console.error(`error: ${err.message}`);
|
|
251
265
|
return 1;
|
|
252
266
|
}
|
|
253
267
|
}
|