edilkamin 1.6.1 → 1.7.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.
@@ -10,7 +10,7 @@ jobs:
10
10
  timeout-minutes: 5
11
11
  strategy:
12
12
  matrix:
13
- node-version: [20.x, 22.x]
13
+ node-version: [20.x, 22.x, 24.x]
14
14
  steps:
15
15
  - uses: actions/checkout@v6
16
16
  - uses: actions/setup-node@v6
@@ -12,7 +12,7 @@ jobs:
12
12
  - uses: actions/checkout@v6
13
13
  - uses: actions/setup-node@v6
14
14
  with:
15
- node-version: "22.x"
15
+ node-version: "24.x"
16
16
  - name: git config
17
17
  run: |
18
18
  git config user.name documentation-deploy-action
@@ -4,6 +4,10 @@ on:
4
4
  push:
5
5
  pull_request:
6
6
 
7
+ permissions:
8
+ id-token: write
9
+ contents: read
10
+
7
11
  jobs:
8
12
  build:
9
13
  runs-on: ubuntu-latest
@@ -12,12 +16,10 @@ jobs:
12
16
  - uses: actions/checkout@v6
13
17
  - uses: actions/setup-node@v6
14
18
  with:
15
- node-version: "22.x"
19
+ node-version: "24.x"
16
20
  registry-url: "https://registry.npmjs.org"
17
21
  - run: yarn install
18
22
  - run: yarn build
19
23
  - run: npm publish --dry-run
20
24
  - run: npm publish
21
25
  if: startsWith(github.ref, 'refs/tags/')
22
- env:
23
- NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
@@ -10,7 +10,7 @@ jobs:
10
10
  timeout-minutes: 5
11
11
  strategy:
12
12
  matrix:
13
- node-version: [20.x, 22.x]
13
+ node-version: [20.x, 22.x, 24.x]
14
14
  steps:
15
15
  - uses: actions/checkout@v6
16
16
  - uses: actions/setup-node@v6
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Edilkamin.js
2
2
 
3
- [![Tests](https://github.com/AndreMiras/edilkamin.js/workflows/Tests/badge.svg)](https://github.com/AndreMiras/edilkamin.js/actions/workflows/tests.yml)
3
+ [![Tests](htts://github.com/AndreMiras/edilkamin.js/actions/workflows/tests.yml/badge.svg)](https://github.com/AndreMiras/edilkamin.js/actions/workflows/tests.yml)
4
4
  [![CLI Tests](https://github.com/AndreMiras/edilkamin.js/actions/workflows/cli-tests.yml/badge.svg)](https://github.com/AndreMiras/edilkamin.js/actions/workflows/cli-tests.yml)
5
5
  [![codecov](https://codecov.io/gh/AndreMiras/edilkamin.js/graph/badge.svg?token=YG3LKXNZWU)](https://app.codecov.io/gh/AndreMiras/edilkamin.js/tree/main)
6
- [![Documentation](https://github.com/AndreMiras/edilkamin.js/workflows/Documentation/badge.svg)](https://github.com/AndreMiras/edilkamin.js/actions/workflows/documentation.yml)
6
+ [![Documentation](https://github.com/AndreMiras/edilkamin.js/actions/workflows/documentation.yml/badge.svg)](https://github.com/AndreMiras/edilkamin.js/actions/workflows/documentation.yml)
7
7
  [![npm version](https://badge.fury.io/js/edilkamin.svg)](https://badge.fury.io/js/edilkamin)
8
8
 
9
9
  This is a library for the [Reverse Engineered](docs/ReverseEngineering.md) "The Mind" Edilkamin API.
@@ -31,11 +31,26 @@ Basic usage:
31
31
  import { signIn, deviceInfo, setPowerOff } from "edilkamin";
32
32
 
33
33
  const macAddress = "aabbccddeeff";
34
- const token = signIn(username, password);
35
- deviceInfo(token, macAddress).then(console.log);
36
- setPowerOff(token, macAddress).then(console.log);
34
+ const token = await signIn(username, password);
35
+ console.log(await deviceInfo(token, macAddress));
36
+ console.log(await setPowerOff(token, macAddress));
37
37
  ```
38
38
 
39
+ For long-running applications, use `getSession()` to automatically refresh tokens:
40
+
41
+ ```js
42
+ import { signIn, getSession, deviceInfo } from "edilkamin";
43
+
44
+ // Authenticate once
45
+ await signIn(username, password);
46
+
47
+ // Get current session (auto-refreshes if expired)
48
+ const token = await getSession();
49
+ console.log(await deviceInfo(token, macAddress));
50
+ ```
51
+
52
+ Sessions persist for ~30 days without re-authentication. Call `getSession()` to retrieve fresh tokens as needed.
53
+
39
54
  It's also possible to change the default backend URL:
40
55
 
41
56
  ```js
@@ -43,8 +58,8 @@ import { signIn, configure } from "edilkamin";
43
58
 
44
59
  const baseUrl = "https://my-proxy.com/";
45
60
  const { deviceInfo, setPower } = configure(baseUrl);
46
- deviceInfo(token, macAddress).then(console.log);
47
- setPower(token, macAddress, 0).then(console.log);
61
+ console.log(await deviceInfo(token, macAddress));
62
+ console.log(await setPower(token, macAddress, 0));
48
63
  ```
49
64
 
50
65
  ## CLI
@@ -52,7 +67,14 @@ setPower(token, macAddress, 0).then(console.log);
52
67
  The library includes a CLI tool that is useful for debugging.
53
68
 
54
69
  ```sh
70
+ # First time: provide credentials to authenticate
55
71
  yarn cli deviceInfo --mac $MAC --username $USERNAME --password $PASSWORD
72
+
73
+ # Subsequent calls: session persists, credentials optional
74
+ yarn cli deviceInfo --mac $MAC
75
+
76
+ # Clear stored session
77
+ yarn cli logout
56
78
  ```
57
79
 
58
80
  Or with `npx` once the library is installed:
@@ -61,6 +83,38 @@ Or with `npx` once the library is installed:
61
83
  npx edilkamin deviceInfo --mac $MAC --username $USERNAME --password $PASSWORD
62
84
  ```
63
85
 
86
+ The CLI stores session data in `~/.edilkamin/session.json` and automatically refreshes tokens when needed. Sessions remain valid for ~30 days.
87
+
88
+ ## API Versions
89
+
90
+ This library supports both the new and legacy Edilkamin API endpoints.
91
+
92
+ ### CLI Usage
93
+
94
+ ```sh
95
+ # New API (default)
96
+ yarn cli deviceInfo --mac $MAC --username $USERNAME --password $PASSWORD
97
+
98
+ # Legacy API
99
+ yarn cli deviceInfo --mac $MAC --username $USERNAME --password $PASSWORD --legacy
100
+ ```
101
+
102
+ ### Library Usage
103
+
104
+ ```js
105
+ import { configure, signIn, OLD_API_URL, NEW_API_URL } from "edilkamin";
106
+
107
+ // New API (default)
108
+ const token = await signIn(username, password);
109
+ const api = configure();
110
+
111
+ // Legacy API
112
+ const legacyToken = await signIn(username, password, true);
113
+ const legacyApi = configure(OLD_API_URL);
114
+ ```
115
+
116
+ > **Note**: The legacy API uses AWS API Gateway and may be deprecated in the future.
117
+
64
118
  ## Motivations
65
119
 
66
120
  - providing an open source web alternative
@@ -0,0 +1,25 @@
1
+ import { BufferEncodedType } from "./types";
2
+ /**
3
+ * Type guard to check if a value is a serialized Node.js Buffer.
4
+ * Node.js Buffers serialize to JSON as: {type: "Buffer", data: [...]}
5
+ *
6
+ * @param value - The value to check
7
+ * @returns True if the value is a Buffer-encoded object
8
+ */
9
+ declare const isBuffer: (value: unknown) => value is BufferEncodedType;
10
+ /**
11
+ * Decompresses a Buffer-encoded gzip object and parses the resulting JSON.
12
+ *
13
+ * @param bufferObj - A serialized Buffer object containing gzip data
14
+ * @returns The decompressed and parsed JSON data, or the original object on failure
15
+ */
16
+ declare const decompressBuffer: (bufferObj: BufferEncodedType) => unknown;
17
+ /**
18
+ * Recursively processes an API response to decompress any Buffer-encoded fields.
19
+ * Handles nested objects and arrays, preserving structure while decompressing.
20
+ *
21
+ * @param data - The API response data to process
22
+ * @returns The processed data with all Buffer fields decompressed
23
+ */
24
+ declare const processResponse: <T>(data: T) => T;
25
+ export { decompressBuffer, isBuffer, processResponse };
@@ -0,0 +1,70 @@
1
+ import pako from "pako";
2
+ /**
3
+ * Type guard to check if a value is a serialized Node.js Buffer.
4
+ * Node.js Buffers serialize to JSON as: {type: "Buffer", data: [...]}
5
+ *
6
+ * @param value - The value to check
7
+ * @returns True if the value is a Buffer-encoded object
8
+ */
9
+ const isBuffer = (value) => {
10
+ return (typeof value === "object" &&
11
+ value !== null &&
12
+ "type" in value &&
13
+ value.type === "Buffer" &&
14
+ "data" in value &&
15
+ Array.isArray(value.data));
16
+ };
17
+ /**
18
+ * Decompresses a Buffer-encoded gzip object and parses the resulting JSON.
19
+ *
20
+ * @param bufferObj - A serialized Buffer object containing gzip data
21
+ * @returns The decompressed and parsed JSON data, or the original object on failure
22
+ */
23
+ const decompressBuffer = (bufferObj) => {
24
+ try {
25
+ // Convert data array to Uint8Array for pako
26
+ const compressed = new Uint8Array(bufferObj.data);
27
+ // Decompress with gzip
28
+ const decompressed = pako.ungzip(compressed, { to: "string" });
29
+ // Parse JSON
30
+ return JSON.parse(decompressed);
31
+ }
32
+ catch (error) {
33
+ // Log warning but return original to maintain backward compatibility
34
+ console.warn("Failed to decompress buffer:", error);
35
+ return bufferObj;
36
+ }
37
+ };
38
+ /**
39
+ * Recursively processes an API response to decompress any Buffer-encoded fields.
40
+ * Handles nested objects and arrays, preserving structure while decompressing.
41
+ *
42
+ * @param data - The API response data to process
43
+ * @returns The processed data with all Buffer fields decompressed
44
+ */
45
+ const processResponse = (data) => {
46
+ if (data === null || data === undefined) {
47
+ return data;
48
+ }
49
+ // Check if this is a Buffer object
50
+ if (isBuffer(data)) {
51
+ const decompressed = decompressBuffer(data);
52
+ // Recursively process the decompressed result (may contain nested buffers)
53
+ return processResponse(decompressed);
54
+ }
55
+ // Recursively process arrays
56
+ if (Array.isArray(data)) {
57
+ return data.map((item) => processResponse(item));
58
+ }
59
+ // Recursively process objects
60
+ if (typeof data === "object") {
61
+ const processed = {};
62
+ for (const [key, value] of Object.entries(data)) {
63
+ processed[key] = processResponse(value);
64
+ }
65
+ return processed;
66
+ }
67
+ // Primitive value, return as-is
68
+ return data;
69
+ };
70
+ export { decompressBuffer, isBuffer, processResponse };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,181 @@
1
+ import { strict as assert } from "assert";
2
+ import pako from "pako";
3
+ import sinon from "sinon";
4
+ import { decompressBuffer, isBuffer, processResponse } from "./buffer-utils";
5
+ /**
6
+ * Helper to create a gzip-compressed Buffer object for testing.
7
+ */
8
+ const createGzippedBuffer = (data) => {
9
+ const json = JSON.stringify(data);
10
+ const compressed = pako.gzip(json);
11
+ return {
12
+ type: "Buffer",
13
+ data: Array.from(compressed),
14
+ };
15
+ };
16
+ describe("buffer-utils", () => {
17
+ afterEach(() => {
18
+ sinon.restore();
19
+ });
20
+ describe("isBuffer", () => {
21
+ it("should detect valid Buffer objects", () => {
22
+ const buffer = { type: "Buffer", data: [31, 139, 8, 0] };
23
+ assert.ok(isBuffer(buffer));
24
+ });
25
+ it("should detect empty Buffer objects", () => {
26
+ const buffer = { type: "Buffer", data: [] };
27
+ assert.ok(isBuffer(buffer));
28
+ });
29
+ it("should reject non-Buffer objects with wrong type", () => {
30
+ assert.ok(!isBuffer({ type: "NotBuffer", data: [] }));
31
+ });
32
+ it("should reject objects without type field", () => {
33
+ assert.ok(!isBuffer({ data: [1, 2, 3] }));
34
+ });
35
+ it("should reject objects without data field", () => {
36
+ assert.ok(!isBuffer({ type: "Buffer" }));
37
+ });
38
+ it("should reject objects with non-array data", () => {
39
+ assert.ok(!isBuffer({ type: "Buffer", data: "not an array" }));
40
+ });
41
+ it("should reject null", () => {
42
+ assert.ok(!isBuffer(null));
43
+ });
44
+ it("should reject undefined", () => {
45
+ assert.ok(!isBuffer(undefined));
46
+ });
47
+ it("should reject primitives", () => {
48
+ assert.ok(!isBuffer("string"));
49
+ assert.ok(!isBuffer(123));
50
+ assert.ok(!isBuffer(true));
51
+ });
52
+ });
53
+ describe("decompressBuffer", () => {
54
+ it("should decompress gzipped JSON buffer", () => {
55
+ const originalData = { test: "value", nested: { key: 123 } };
56
+ const bufferObj = createGzippedBuffer(originalData);
57
+ const result = decompressBuffer(bufferObj);
58
+ assert.deepEqual(result, originalData);
59
+ });
60
+ it("should handle gzipped arrays", () => {
61
+ const originalData = [1, 2, 3, "test"];
62
+ const bufferObj = createGzippedBuffer(originalData);
63
+ const result = decompressBuffer(bufferObj);
64
+ assert.deepEqual(result, originalData);
65
+ });
66
+ it("should handle gzipped strings", () => {
67
+ const originalData = "test string";
68
+ const bufferObj = createGzippedBuffer(originalData);
69
+ const result = decompressBuffer(bufferObj);
70
+ assert.equal(result, originalData);
71
+ });
72
+ it("should return original value if decompression fails", () => {
73
+ const consoleWarnStub = sinon.stub(console, "warn");
74
+ const invalidBuffer = { type: "Buffer", data: [1, 2, 3] };
75
+ const result = decompressBuffer(invalidBuffer);
76
+ assert.deepEqual(result, invalidBuffer);
77
+ assert.ok(consoleWarnStub.calledOnce);
78
+ });
79
+ it("should return original value if JSON parsing fails", () => {
80
+ const consoleWarnStub = sinon.stub(console, "warn");
81
+ // Create valid gzip but invalid JSON
82
+ const invalidJson = "not valid json {";
83
+ const compressed = pako.gzip(invalidJson);
84
+ const bufferObj = {
85
+ type: "Buffer",
86
+ data: Array.from(compressed),
87
+ };
88
+ const result = decompressBuffer(bufferObj);
89
+ assert.deepEqual(result, bufferObj);
90
+ assert.ok(consoleWarnStub.calledOnce);
91
+ });
92
+ });
93
+ describe("processResponse", () => {
94
+ it("should pass through null", () => {
95
+ assert.equal(processResponse(null), null);
96
+ });
97
+ it("should pass through undefined", () => {
98
+ assert.equal(processResponse(undefined), undefined);
99
+ });
100
+ it("should pass through primitives", () => {
101
+ assert.equal(processResponse("string"), "string");
102
+ assert.equal(processResponse(123), 123);
103
+ assert.equal(processResponse(true), true);
104
+ });
105
+ it("should pass through plain objects", () => {
106
+ const obj = { key: "value", nested: { num: 42 } };
107
+ assert.deepEqual(processResponse(obj), obj);
108
+ });
109
+ it("should pass through plain arrays", () => {
110
+ const arr = [1, "two", { three: 3 }];
111
+ assert.deepEqual(processResponse(arr), arr);
112
+ });
113
+ it("should decompress Buffer at root level", () => {
114
+ const originalData = { decompressed: true };
115
+ const buffer = createGzippedBuffer(originalData);
116
+ const result = processResponse(buffer);
117
+ assert.deepEqual(result, originalData);
118
+ });
119
+ it("should decompress nested Buffer fields", () => {
120
+ const statusData = { commands: { power: true } };
121
+ const response = {
122
+ plain: "data",
123
+ status: createGzippedBuffer(statusData),
124
+ };
125
+ const result = processResponse(response);
126
+ assert.equal(result.plain, "data");
127
+ assert.deepEqual(result.status, statusData);
128
+ });
129
+ it("should recursively decompress deeply nested Buffers", () => {
130
+ const innerData = { value: 42 };
131
+ const middleData = { inner: createGzippedBuffer(innerData) };
132
+ const response = {
133
+ outer: createGzippedBuffer(middleData),
134
+ };
135
+ const result = processResponse(response);
136
+ assert.deepEqual(result, { outer: { inner: { value: 42 } } });
137
+ });
138
+ it("should handle arrays containing Buffers", () => {
139
+ const itemData = { id: 1 };
140
+ const response = {
141
+ items: [createGzippedBuffer(itemData), { id: 2 }],
142
+ };
143
+ const result = processResponse(response);
144
+ assert.deepEqual(result.items, [{ id: 1 }, { id: 2 }]);
145
+ });
146
+ it("should handle mixed compressed and uncompressed fields", () => {
147
+ const compressedStatus = { commands: { power: true } };
148
+ const response = {
149
+ status: createGzippedBuffer(compressedStatus),
150
+ nvm: { user_parameters: { temperature: 22 } },
151
+ plain_field: "unchanged",
152
+ };
153
+ const result = processResponse(response);
154
+ assert.deepEqual(result.status, compressedStatus);
155
+ assert.deepEqual(result.nvm, { user_parameters: { temperature: 22 } });
156
+ assert.equal(result.plain_field, "unchanged");
157
+ });
158
+ it("should handle real-world DeviceInfo structure with compressed status", () => {
159
+ const statusData = {
160
+ commands: { power: true },
161
+ temperatures: { board: 25, enviroment: 20 },
162
+ };
163
+ const nvmData = {
164
+ user_parameters: {
165
+ enviroment_1_temperature: 22,
166
+ enviroment_2_temperature: 0,
167
+ enviroment_3_temperature: 0,
168
+ is_auto: false,
169
+ is_sound_active: true,
170
+ },
171
+ };
172
+ const response = {
173
+ status: createGzippedBuffer(statusData),
174
+ nvm: createGzippedBuffer(nvmData),
175
+ };
176
+ const result = processResponse(response);
177
+ assert.deepEqual(result.status, statusData);
178
+ assert.deepEqual(result.nvm, nvmData);
179
+ });
180
+ });
181
+ });
package/dist/esm/cli.js CHANGED
@@ -11,7 +11,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  import { Command } from "commander";
12
12
  import readline from "readline";
13
13
  import { version } from "../package.json";
14
- import { configure, signIn } from "./library";
14
+ import { NEW_API_URL, OLD_API_URL } from "./constants";
15
+ import { configure, configureAmplify, getSession, signIn } from "./library";
16
+ import { clearSession, createFileStorage } from "./token-storage";
15
17
  const promptPassword = () => {
16
18
  const rl = readline.createInterface({
17
19
  input: process.stdin,
@@ -36,11 +38,12 @@ const promptPassword = () => {
36
38
  };
37
39
  /**
38
40
  * Adds common options (username and password) to a command.
41
+ * Username is optional if a session already exists.
39
42
  * @param command The command to which options should be added.
40
43
  * @returns The command with options added.
41
44
  */
42
45
  const addAuthOptions = (command) => command
43
- .requiredOption("-u, --username <username>", "Username")
46
+ .option("-u, --username <username>", "Username (optional if session exists)")
44
47
  .option("-p, --password <password>", "Password");
45
48
  /**
46
49
  * Adds MAC address option to a command.
@@ -48,17 +51,39 @@ const addAuthOptions = (command) => command
48
51
  * @returns The command with the MAC address option added.
49
52
  */
50
53
  const addMacOption = (command) => command.requiredOption("-m, --mac <macAddress>", "MAC address of the device");
54
+ /**
55
+ * Adds legacy API option to a command.
56
+ * @param command The command to which the legacy option should be added.
57
+ * @returns The command with the legacy option added.
58
+ */
59
+ const addLegacyOption = (command) => command.option("--legacy", "Use legacy API endpoint (old AWS Gateway)");
51
60
  /**
52
61
  * Handles common authentication and API initialization logic.
62
+ * Tries to use existing session first, falls back to sign-in if needed.
53
63
  * @param options The options passed from the CLI command.
54
64
  * @returns An object containing the normalized MAC, JWT token, and configured API instance.
55
65
  */
56
66
  const initializeCommand = (options) => __awaiter(void 0, void 0, void 0, function* () {
57
- const { username, password, mac } = options;
67
+ const { username, password, mac, legacy = false } = options;
58
68
  const normalizedMac = mac.replace(/:/g, "");
59
- const pwd = password || (yield promptPassword());
60
- const jwtToken = yield signIn(username, pwd);
61
- const api = configure();
69
+ // Initialize file storage for session persistence
70
+ const storage = createFileStorage();
71
+ configureAmplify(storage);
72
+ let jwtToken;
73
+ try {
74
+ // Try to get existing session first
75
+ jwtToken = yield getSession(false, legacy);
76
+ }
77
+ catch (_a) {
78
+ // No session, need to sign in
79
+ if (!username) {
80
+ throw new Error("No session found. Please provide --username to sign in.");
81
+ }
82
+ const pwd = password || (yield promptPassword());
83
+ jwtToken = yield signIn(username, pwd, legacy);
84
+ }
85
+ const apiUrl = legacy ? OLD_API_URL : NEW_API_URL;
86
+ const api = configure(apiUrl);
62
87
  return { normalizedMac, jwtToken, api };
63
88
  });
64
89
  /**
@@ -88,12 +113,28 @@ const createProgram = () => {
88
113
  .description("CLI tool for interacting with the Edilkamin API")
89
114
  .version(version);
90
115
  // Command: signIn
91
- addAuthOptions(program.command("signIn").description("Sign in and retrieve a JWT token")).action((options) => __awaiter(void 0, void 0, void 0, function* () {
116
+ program
117
+ .command("signIn")
118
+ .description("Sign in and retrieve a JWT token")
119
+ .requiredOption("-u, --username <username>", "Username")
120
+ .option("-p, --password <password>", "Password")
121
+ .action((options) => __awaiter(void 0, void 0, void 0, function* () {
92
122
  const { username, password } = options;
123
+ // Initialize file storage for session persistence
124
+ const storage = createFileStorage();
125
+ configureAmplify(storage);
93
126
  const pwd = password || (yield promptPassword());
94
127
  const jwtToken = yield signIn(username, pwd);
95
128
  console.log("JWT Token:", jwtToken);
96
129
  }));
130
+ // Command: logout
131
+ program
132
+ .command("logout")
133
+ .description("Clear stored session")
134
+ .action(() => __awaiter(void 0, void 0, void 0, function* () {
135
+ yield clearSession();
136
+ console.log("Session cleared successfully");
137
+ }));
97
138
  // Generic getter commands
98
139
  [
99
140
  {
@@ -117,7 +158,7 @@ const createProgram = () => {
117
158
  getter: (api, jwtToken, mac) => api.getTargetTemperature(jwtToken, mac),
118
159
  },
119
160
  ].forEach(({ commandName, description, getter }) => {
120
- addMacOption(addAuthOptions(program.command(commandName).description(description))).action((options) => executeGetter(options, getter));
161
+ addLegacyOption(addMacOption(addAuthOptions(program.command(commandName).description(description)))).action((options) => executeGetter(options, getter));
121
162
  });
122
163
  // Generic setter commands
123
164
  [
@@ -132,8 +173,68 @@ const createProgram = () => {
132
173
  setter: (api, jwtToken, mac, value) => api.setTargetTemperature(jwtToken, mac, value),
133
174
  },
134
175
  ].forEach(({ commandName, description, setter }) => {
135
- addMacOption(addAuthOptions(program.command(commandName).description(description)).requiredOption("-v, --value <number>", "Value to set", parseFloat)).action((options) => executeSetter(options, setter));
176
+ addLegacyOption(addMacOption(addAuthOptions(program.command(commandName).description(description)).requiredOption("-v, --value <number>", "Value to set", parseFloat))).action((options) => executeSetter(options, setter));
136
177
  });
178
+ // Command: register
179
+ addLegacyOption(addAuthOptions(program
180
+ .command("register")
181
+ .description("Register a device with your account")))
182
+ .requiredOption("-m, --mac <macAddress>", "MAC address of the device")
183
+ .requiredOption("-s, --serial <serialNumber>", "Device serial number")
184
+ .requiredOption("-n, --name <deviceName>", "Device name")
185
+ .requiredOption("-r, --room <deviceRoom>", "Room name")
186
+ .action((options) => __awaiter(void 0, void 0, void 0, function* () {
187
+ const { username, password, mac, serial, name, room, legacy = false, } = options;
188
+ const normalizedMac = mac.replace(/:/g, "");
189
+ // Initialize file storage for session persistence
190
+ const storage = createFileStorage();
191
+ configureAmplify(storage);
192
+ let jwtToken;
193
+ try {
194
+ jwtToken = yield getSession(false, legacy);
195
+ }
196
+ catch (_a) {
197
+ if (!username) {
198
+ throw new Error("No session found. Please provide --username to sign in.");
199
+ }
200
+ const pwd = password || (yield promptPassword());
201
+ jwtToken = yield signIn(username, pwd, legacy);
202
+ }
203
+ const apiUrl = legacy ? OLD_API_URL : NEW_API_URL;
204
+ const api = configure(apiUrl);
205
+ const result = yield api.registerDevice(jwtToken, normalizedMac, serial, name, room);
206
+ console.log("Device registered successfully:");
207
+ console.log(JSON.stringify(result, null, 2));
208
+ }));
209
+ // Command: editDevice
210
+ addLegacyOption(addMacOption(addAuthOptions(program
211
+ .command("editDevice")
212
+ .description("Update device name and room"))))
213
+ .requiredOption("-n, --name <deviceName>", "Device name")
214
+ .requiredOption("-r, --room <deviceRoom>", "Room name")
215
+ .action((options) => __awaiter(void 0, void 0, void 0, function* () {
216
+ const { username, password, mac, name, room, legacy = false } = options;
217
+ const normalizedMac = mac.replace(/:/g, "");
218
+ // Initialize file storage for session persistence
219
+ const storage = createFileStorage();
220
+ configureAmplify(storage);
221
+ let jwtToken;
222
+ try {
223
+ jwtToken = yield getSession(false, legacy);
224
+ }
225
+ catch (_a) {
226
+ if (!username) {
227
+ throw new Error("No session found. Please provide --username to sign in.");
228
+ }
229
+ const pwd = password || (yield promptPassword());
230
+ jwtToken = yield signIn(username, pwd, legacy);
231
+ }
232
+ const apiUrl = legacy ? OLD_API_URL : NEW_API_URL;
233
+ const api = configure(apiUrl);
234
+ const result = yield api.editDevice(jwtToken, normalizedMac, name, room);
235
+ console.log("Device updated successfully:");
236
+ console.log(JSON.stringify(result, null, 2));
237
+ }));
137
238
  return program;
138
239
  };
139
240
  const main = () => {
@@ -1,2 +1,4 @@
1
+ declare const OLD_API_URL = "https://fxtj7xkgc6.execute-api.eu-central-1.amazonaws.com/prod/";
2
+ declare const NEW_API_URL = "https://the-mind-api.edilkamin.com/";
1
3
  declare const API_URL = "https://the-mind-api.edilkamin.com/";
2
- export { API_URL };
4
+ export { API_URL, NEW_API_URL, OLD_API_URL };
@@ -1,2 +1,4 @@
1
- const API_URL = "https://the-mind-api.edilkamin.com/";
2
- export { API_URL };
1
+ const OLD_API_URL = "https://fxtj7xkgc6.execute-api.eu-central-1.amazonaws.com/prod/";
2
+ const NEW_API_URL = "https://the-mind-api.edilkamin.com/";
3
+ const API_URL = NEW_API_URL;
4
+ export { API_URL, NEW_API_URL, OLD_API_URL };
@@ -1,4 +1,7 @@
1
- export { API_URL } from "./constants";
2
- export { configure, signIn } from "./library";
3
- export { CommandsType, DeviceInfoType, StatusType, TemperaturesType, UserParametersType, } from "./types";
4
- export declare const deviceInfo: (jwtToken: string, macAddress: string) => Promise<import("./types").DeviceInfoType>, setPower: (jwtToken: string, macAddress: string, value: number) => Promise<import("axios").AxiosResponse<any, any, {}>>, setPowerOff: (jwtToken: string, macAddress: string) => Promise<import("axios").AxiosResponse<any, any, {}>>, setPowerOn: (jwtToken: string, macAddress: string) => Promise<import("axios").AxiosResponse<any, any, {}>>, getPower: (jwtToken: string, macAddress: string) => Promise<boolean>, getEnvironmentTemperature: (jwtToken: string, macAddress: string) => Promise<number>, getTargetTemperature: (jwtToken: string, macAddress: string) => Promise<number>, setTargetTemperature: (jwtToken: string, macAddress: string, temperature: number) => Promise<import("axios").AxiosResponse<any, any, {}>>;
1
+ export { decompressBuffer, isBuffer, processResponse } from "./buffer-utils";
2
+ export { API_URL, NEW_API_URL, OLD_API_URL } from "./constants";
3
+ export { configure, getSession, signIn } from "./library";
4
+ export { serialNumberDisplay, serialNumberFromHex, serialNumberToHex, } from "./serial-utils";
5
+ export { clearSession } from "./token-storage";
6
+ export { BufferEncodedType, CommandsType, DeviceAssociationBody, DeviceAssociationResponse, DeviceInfoRawType, DeviceInfoType, EditDeviceAssociationBody, StatusType, TemperaturesType, UserParametersType, } from "./types";
7
+ export declare const deviceInfo: (jwtToken: string, macAddress: string) => Promise<import("./types").DeviceInfoType>, registerDevice: (jwtToken: string, macAddress: string, serialNumber: string, deviceName?: string, deviceRoom?: string) => Promise<import("./types").DeviceAssociationResponse>, editDevice: (jwtToken: string, macAddress: string, deviceName?: string, deviceRoom?: string) => Promise<import("./types").DeviceAssociationResponse>, setPower: (jwtToken: string, macAddress: string, value: number) => Promise<import("axios").AxiosResponse<any, any, {}>>, setPowerOff: (jwtToken: string, macAddress: string) => Promise<import("axios").AxiosResponse<any, any, {}>>, setPowerOn: (jwtToken: string, macAddress: string) => Promise<import("axios").AxiosResponse<any, any, {}>>, getPower: (jwtToken: string, macAddress: string) => Promise<boolean>, getEnvironmentTemperature: (jwtToken: string, macAddress: string) => Promise<number>, getTargetTemperature: (jwtToken: string, macAddress: string) => Promise<number>, setTargetTemperature: (jwtToken: string, macAddress: string, temperature: number) => Promise<import("axios").AxiosResponse<any, any, {}>>;
package/dist/esm/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  import { configure } from "./library";
2
- export { API_URL } from "./constants";
3
- export { configure, signIn } from "./library";
4
- export const { deviceInfo, setPower, setPowerOff, setPowerOn, getPower, getEnvironmentTemperature, getTargetTemperature, setTargetTemperature, } = configure();
2
+ export { decompressBuffer, isBuffer, processResponse } from "./buffer-utils";
3
+ export { API_URL, NEW_API_URL, OLD_API_URL } from "./constants";
4
+ export { configure, getSession, signIn } from "./library";
5
+ export { serialNumberDisplay, serialNumberFromHex, serialNumberToHex, } from "./serial-utils";
6
+ export { clearSession } from "./token-storage";
7
+ export const { deviceInfo, registerDevice, editDevice, setPower, setPowerOff, setPowerOn, getPower, getEnvironmentTemperature, getTargetTemperature, setTargetTemperature, } = configure();