@thinremote/thinr-cli 1.0.0-alpha.1

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,124 @@
1
+ import chalk from 'chalk';
2
+ import { readConfig } from './config.js';
3
+ import api from './api.js';
4
+
5
+ /**
6
+ * Get device resource value
7
+ * @param {string} deviceId - Device ID to query
8
+ * @return {Promise<Object>} Resource value
9
+ */
10
+ export async function getDeviceResources(deviceId) {
11
+ // Get configuration
12
+ const config = readConfig();
13
+
14
+ if (!config.token || !config.server || !config.username) {
15
+ throw new Error('Not configured. Run thinr without parameters to set up.');
16
+ }
17
+
18
+ try {
19
+ // Fetch resource value
20
+ const response = await api.get(
21
+ `/v3/users/${config.username}/devices/${deviceId}/resources`,
22
+ );
23
+
24
+ // Remove hidden resources for which the keys starting with '_'
25
+ const filtered = Object.fromEntries(
26
+ Object.entries(response.data).filter(([key]) => !key.startsWith('$'))
27
+ );
28
+
29
+ return filtered;
30
+ } catch (error) {
31
+ if (error.response) {
32
+ // The request was made and the server responded with a status code
33
+ if (error.response.status === 404) {
34
+ throw new Error(`Device not found: ${deviceId}`);
35
+ } else if (error.response.status === 401) {
36
+ throw new Error('Unauthorized. Your token may have expired. Please reconfigure.');
37
+ } else {
38
+ throw new Error(`Server error: ${error.response.status} ${error.response.statusText}`);
39
+ }
40
+ } else if (error.request) {
41
+ // The request was made but no response was received
42
+ throw new Error('No response from server. Please check your connection.');
43
+ } else {
44
+ // Something happened in setting up the request that triggered an Error
45
+ throw new Error(`Error: ${error.message}`);
46
+ }
47
+ }
48
+
49
+ }
50
+
51
+ /**
52
+ * Execute a resource on a device
53
+ */
54
+ export async function executeDeviceResource(deviceId, resourceId, inputs) {
55
+ // Get configuration
56
+ const config = readConfig();
57
+
58
+ if (!config.token || !config.server || !config.username) {
59
+ throw new Error('Not configured. Run thinr without parameters to set up.');
60
+ }
61
+
62
+ try {
63
+ // Execute resource
64
+ const response = await api.post(
65
+ `/v3/users/${config.username}/devices/${deviceId}/resources/${resourceId}`,
66
+ inputs,
67
+ );
68
+
69
+ return response.data;
70
+ } catch (error) {
71
+ if (error.response) {
72
+ // The request was made and the server responded with a status code
73
+ if (error.response.status === 404) {
74
+ throw new Error(`Resource not found: ${resourceId} on device ${deviceId}`);
75
+ } else if (error.response.status === 401) {
76
+ throw new Error('Unauthorized. Your token may have expired. Please reconfigure.');
77
+ } else {
78
+ throw new Error(`Server error: ${error.response.status} ${error.response.statusText}`);
79
+ }
80
+ } else if (error.request) {
81
+ // The request was made but no response was received
82
+ throw new Error('No response from server. Please check your connection.');
83
+ } else {
84
+ // Something happened in setting up the request that triggered an Error
85
+ throw new Error(`Error: ${error.message}`);
86
+ }
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Format device resources for display
92
+ * @param {string} deviceId - Device ID
93
+ * @param {Object} resources - Resources data from API
94
+ * @return {string} Formatted resources
95
+ */
96
+ export function formatDeviceResources(deviceId, resources) {
97
+ let formattedResources = `\nResources for device ${chalk.cyan(deviceId)}:\n`;
98
+
99
+ //
100
+ if (resources && Object.keys(resources).length > 0) {
101
+ Object.keys(resources).forEach(key => {
102
+ formattedResources += `- ${chalk.green(key)}: ${chalk.blue(getResourceType(resources[key]))}\n`;
103
+ });
104
+ } else {
105
+ formattedResources += chalk.yellow('No resources found for this device.\n');
106
+ }
107
+
108
+ return formattedResources;
109
+ }
110
+
111
+ function getResourceType(value) {
112
+ switch (value.fn) {
113
+ case 1:
114
+ return "no parameters";
115
+ case 2:
116
+ return "input";
117
+ case 3:
118
+ return "output";
119
+ case 4:
120
+ return "input/output";
121
+ default:
122
+ return 'unknown';
123
+ }
124
+ }
package/lib/status.js ADDED
@@ -0,0 +1,96 @@
1
+ import chalk from 'chalk';
2
+ import { readConfig } from './config.js';
3
+ import api from './api.js';
4
+
5
+ /**
6
+ * Get device status
7
+ * @param {string} deviceId - Device ID to check
8
+ * @returns {Promise<Object>} Status information
9
+ */
10
+ export async function getDeviceStatus(deviceId) {
11
+ // Get configuration
12
+ const config = readConfig();
13
+
14
+ if (!config.token || !config.server || !config.username) {
15
+ throw new Error('Not configured. Run thinr without parameters to set up.');
16
+ }
17
+
18
+ try {
19
+ // Fetch device status
20
+ const response = await api.get(
21
+ `/v1/users/${config.username}/devices/${deviceId}/stats`,
22
+ );
23
+
24
+ return response.data;
25
+ } catch (error) {
26
+ if (error.response) {
27
+ // The request was made and the server responded with a status code
28
+ // that falls out of the range of 2xx
29
+ if (error.response.status === 404) {
30
+ throw new Error(`Device not found: ${deviceId}`);
31
+ } else if (error.response.status === 401) {
32
+ throw new Error('Unauthorized. Your token may have expired. Please reconfigure.');
33
+ } else {
34
+ throw new Error(`Server error: ${error.response.status} ${error.response.statusText}`);
35
+ }
36
+ } else if (error.request) {
37
+ // The request was made but no response was received
38
+ throw new Error('No response from server. Please check your connection.');
39
+ } else {
40
+ // Something happened in setting up the request that triggered an Error
41
+ throw new Error(`Error: ${error.message}`);
42
+ }
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Format device status data for display
48
+ * @param {string} deviceId - Device ID
49
+ * @param {Object} status - Status data from API
50
+ * @returns {string} Formatted status
51
+ */
52
+ export function formatDeviceStatus(deviceId, status) {
53
+ const connectedStatus = status.connected ?
54
+ chalk.green('● Online') :
55
+ chalk.red('○ Offline');
56
+
57
+ // Format connected time
58
+ let connectedTime = '';
59
+ if (status.connected && status.connected_ts) {
60
+ const now = new Date().getTime();
61
+ const connectedSince = new Date(status.connected_ts).getTime();
62
+ const diffSeconds = Math.floor((now - connectedSince) / 1000);
63
+
64
+ if (diffSeconds < 60) {
65
+ connectedTime = `for ${diffSeconds} seconds`;
66
+ } else if (diffSeconds < 3600) {
67
+ connectedTime = `for ${Math.floor(diffSeconds / 60)} minutes`;
68
+ } else if (diffSeconds < 86400) {
69
+ connectedTime = `for ${Math.floor(diffSeconds / 3600)} hours`;
70
+ } else {
71
+ connectedTime = `for ${Math.floor(diffSeconds / 86400)} days`;
72
+ }
73
+ }
74
+
75
+ // Format data transfer
76
+ const rxMB = status.rx_bytes ? (status.rx_bytes / (1024 * 1024)).toFixed(2) : '0.00';
77
+ const txMB = status.tx_bytes ? (status.tx_bytes / (1024 * 1024)).toFixed(2) : '0.00';
78
+
79
+ // Build the output
80
+ let output = `
81
+ ${chalk.bold(`Device: ${deviceId}`)}
82
+ Status: ${connectedStatus} ${connectedTime}
83
+ `;
84
+
85
+ if (status.ip_address) {
86
+ output += `IP Address: ${status.ip_address}\n`;
87
+ }
88
+
89
+ output += `
90
+ Data Transfer:
91
+ ↓ Received: ${rxMB} MB
92
+ ↑ Sent: ${txMB} MB
93
+ `;
94
+
95
+ return output;
96
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@thinremote/thinr-cli",
3
+ "version": "1.0.0-alpha.1",
4
+ "description": "CLI for ThinRemote - Remote management for IoT devices",
5
+ "main": "bin/thinr.js",
6
+ "bin": {
7
+ "thinr": "bin/thinr.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node bin/thinr.js",
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "keywords": [
14
+ "cli",
15
+ "iot",
16
+ "remote",
17
+ "management",
18
+ "thing",
19
+ "thinger"
20
+ ],
21
+ "author": "ThinRemote",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "axios": "^1.10.0",
25
+ "chalk": "^5.4.1",
26
+ "commander": "^14.0.0",
27
+ "conf": "^14.0.0",
28
+ "figlet": "^1.8.1",
29
+ "@inquirer/prompts": "^7.5.3",
30
+ "jsonwebtoken": "^9.0.2",
31
+ "open": "^10.1.2",
32
+ "ora": "^8.2.0",
33
+ "ws": "^8.18.2"
34
+ },
35
+ "type": "module",
36
+ "engines": {
37
+ "node": ">=14.16"
38
+ }
39
+ }