@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,45 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { deleteConfig, configExists } from '../lib/config.js';
4
+ import { deleteToken } from '../lib/auth.js';
5
+
6
+ /**
7
+ * Register the logout command
8
+ * @param {Command} program - Commander program instance
9
+ */
10
+ export function logoutCommand(program) {
11
+ program
12
+ .command('logout')
13
+ .description('Remove stored credentials and configuration')
14
+ .action(async () => {
15
+ // Check if configured
16
+ if (!configExists()) {
17
+ console.log(chalk.yellow('No configuration found. Already logged out.'));
18
+ return;
19
+ }
20
+
21
+ const spinner = ora('Removing configuration...').start();
22
+
23
+ try {
24
+ // Delete token from server first
25
+ spinner.text = 'Deleting token from server...';
26
+ await deleteToken();
27
+
28
+ // Delete local configuration
29
+ spinner.text = 'Removing local configuration...';
30
+ const success = deleteConfig();
31
+
32
+ if (success) {
33
+ spinner.succeed('Logged out successfully. Configuration removed.');
34
+ } else {
35
+ spinner.fail('Failed to remove configuration.');
36
+ console.error(chalk.red('Error: Unable to delete configuration file. You may need to remove it manually.'));
37
+ console.error(chalk.gray('Configuration is stored in ~/.config/thinr-cli/config.json'));
38
+ }
39
+ } catch (error) {
40
+ spinner.fail('Failed to remove configuration.');
41
+ console.error(chalk.red(`Error: ${error.message}`));
42
+ console.error(chalk.gray('Configuration is stored in ~/.config/thinr-cli/config.json'));
43
+ }
44
+ });
45
+ }
@@ -0,0 +1,198 @@
1
+ import { configExists } from "../lib/config.js";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import { formatDeviceProperty, getDeviceProperty } from "../lib/property.js";
5
+ import { executeDeviceResource } from "../lib/resource.js";
6
+ import { filterActiveDevices, getDevices } from "../lib/device.js";
7
+
8
+ /**
9
+ * Register the product command with all product related functionality.
10
+ * @param {Command} program - Commander program instance
11
+ */
12
+ export function productCommand(program) {
13
+
14
+ const product = program.command('product').description('Product commands');
15
+
16
+ product
17
+ .argument('<productId>', 'ID of the product to manage')
18
+ .description('Device commands for a specific product')
19
+ .allowUnknownOption(true)
20
+ .allowExcessArguments(true)
21
+ .action( async (productId, options, command) => {
22
+
23
+ // Get additional arguments manually
24
+ const args = command.args.slice(1); // Skip productId
25
+ const action = args[0];
26
+ const target = args[1] && !args[1].startsWith('-') ? args[1] : null;
27
+
28
+ // Check if configured
29
+ if (!configExists()) {
30
+ console.error(chalk.red('Error: Not configured. Run thinr without parameters to set up.'));
31
+ process.exit(1);
32
+ }
33
+
34
+ // If no action provided, show help
35
+ if (!action) {
36
+ printHelp(productId);
37
+ return;
38
+ }
39
+
40
+ // Parse options manually from process.argv
41
+ const parsedOptions = {
42
+ json: false,
43
+ inputs: {},
44
+ field: "",
45
+ allDevices: false,
46
+ group: null // Group option for filtering devices
47
+ };
48
+
49
+ const argv = process.argv;
50
+ for (let i = 0; i < argv.length; i++) {
51
+ if (argv[i] === '-j' || argv[i] === '--json') {
52
+ parsedOptions.json = true;
53
+ } else if (argv[i] === '-i' || argv[i] === '--input') {
54
+ let inputPair = argv[i + 1].split('=');
55
+ parsedOptions.inputs[inputPair[0]] = inputPair[1];
56
+ } else if (argv[i] === '-f' || argv[i] === '--field') {
57
+ parsedOptions.field = argv[i + 1];
58
+ } else if (argv[i] === '-h' || argv[i] === '--help') {
59
+ printHelp(productId);
60
+ return;
61
+ } else if (argv[i] === '-a' || argv[i] === '--all') {
62
+ parsedOptions.allDevices = true;
63
+ } else if (argv[i] === '-g' || argv[i] === '--group') {
64
+ parsedOptions.group = argv[i + 1];
65
+ }
66
+
67
+ }
68
+
69
+ let devices = [];
70
+ const spinner = ora(`Retrieving devices for product ${productId}...`).start();
71
+ try {
72
+ let filter = {
73
+ productId: productId,
74
+ };
75
+ if (parsedOptions.group) {
76
+ filter.asset_group = parsedOptions.group;
77
+ }
78
+ devices = await getDevices(filter);
79
+ spinner.succeed(`Devices for product ${productId} retrieved successfully`);
80
+ } catch (e) {
81
+ spinner.fail(`Failed to retrieve devices for product ${productId}`);
82
+ process.exit(1);
83
+ }
84
+
85
+ switch (action.toLowerCase()) {
86
+
87
+ case 'property': {
88
+
89
+ //const spinner = ora(`Retrieving property ${target} for devices belonging to the product ${productId}...`).start();
90
+
91
+ if ( ! parsedOptions.allDevices ) {
92
+ devices = filterActiveDevices(devices);
93
+ }
94
+
95
+ try {
96
+ for (const device of devices) {
97
+ try {
98
+ const property = await getDeviceProperty(device.device, target);
99
+ console.log('Device', chalk.blue(device.device), 'property', chalk.blue(target));
100
+
101
+ if (parsedOptions.json) {
102
+ if ( parsedOptions.field ) {
103
+ console.log(parsedOptions.field.split('.').reduce((obj, key) => obj && obj[key], property));
104
+ } else {
105
+ console.log(JSON.stringify(property, null, 2));
106
+ }
107
+ } else {
108
+ console.log(formatDeviceProperty(device.device, target, property));
109
+ }
110
+ } catch (e) {
111
+ console.error(chalk.red(`Error retrieving property ${target} for device ${device.device}: ${e.message}`));
112
+ }
113
+ }
114
+ //spinner.succeed(`Property ${target} for devices belonging to product ${productId} found`);
115
+ } catch (error) {
116
+ spinner.fail(`Property retrieval failed`);
117
+ console.error(chalk.red(`Error: ${error.message}`));
118
+ process.exit(1);
119
+ }
120
+ break;
121
+ }
122
+
123
+ case 'resource': {
124
+
125
+ //const spinner = ora(`Executing resource ${target} for devices belonging to the product ${productId}...`).start();
126
+
127
+ // Obviously, we can only execute resources on active devices
128
+ devices = filterActiveDevices(devices);
129
+
130
+ try {
131
+ for (const device of devices) {
132
+ try {
133
+ const result = await executeDeviceResource(device.device, target, parsedOptions.inputs);
134
+ console.log('Device', chalk.blue(device.device), 'resource', chalk.blue(target));
135
+ if ( parsedOptions.field ) {
136
+ console.log(parsedOptions.field.split('.').reduce((obj, key) => obj && obj[key], result));
137
+ } else {
138
+ console.log(result);
139
+ }
140
+ } catch (e) {
141
+ console.error(chalk.red(`Error retrieving property ${target} for device ${device.device}: ${e.message}`));
142
+ }
143
+ }
144
+ //spinner.succeed(`Resource ${target} executed successfully for devices belonging to product ${productId}`);
145
+
146
+
147
+ } catch (error) {
148
+ spinner.fail(`Resource execution failed`);
149
+ console.error(chalk.red(`Error: ${error.message}`));
150
+ process.exit(1);
151
+ }
152
+
153
+ break;
154
+
155
+ }
156
+
157
+ default:
158
+ console.error(chalk.red(`Error: Unknown action '${action}'. Available actions: property, resource.\n`));
159
+ printHelp(productId);
160
+ process.exit(1);
161
+
162
+ }
163
+ });
164
+ }
165
+
166
+ function printHelp(productId) {
167
+ console.log(`Usage: thinr device product ${productId} <command> [options]
168
+
169
+ Available commands for device product "${productId}":
170
+
171
+ property [propertyId] Get a specific property of all devices belonging to the product
172
+ Example: thinr device product ${productId} property uptime
173
+
174
+ resource [resource] Execute a specific resource of all devices belonging to the product
175
+ Example: thinr device product ${productId} resource reboot
176
+
177
+ Arguments:
178
+
179
+ propertyId ID of the property to retrieve (for property command)
180
+
181
+ resource Name of the resource to execute (for resource command)
182
+
183
+
184
+ Options (for all product subcommands):
185
+ -g, --group <group> Group of devices to filter (e.g., -g group1)
186
+
187
+ Options (for property command):
188
+ -j, --json Output as JSON
189
+ -f, --field <field> Field to extract from the property (e.g., -f data.value)
190
+ -a, --all Include non active devices (default: false)
191
+
192
+ Options (for resource command):
193
+ -j, --json Output as JSON
194
+ -f, --field <field> Field to extract from the property (e.g., -f data.value)
195
+
196
+ Use "thinr product device ${productId} <command> --help" for more details on each command.`);
197
+
198
+ }
package/lib/api.js ADDED
@@ -0,0 +1,60 @@
1
+ import axios from 'axios';
2
+ import { refreshToken, getAccessToken } from './auth.js'; // Adjust the import based on your project structure
3
+ import http from 'http';
4
+ import https from 'https';
5
+
6
+ const httpAgent = new http.Agent({ keepAlive: true });
7
+ const httpsAgent = new https.Agent({ keepAlive: true });
8
+
9
+ const api = axios.create({
10
+ // You can leave baseURL undefined initially
11
+ timeout: 10000,
12
+ httpAgent,
13
+ httpsAgent,
14
+ });
15
+
16
+ export function setBaseURL(url) {
17
+ api.defaults.baseURL = url;
18
+ }
19
+
20
+ api.interceptors.request.use(request => {
21
+
22
+ const token = getAccessToken();
23
+ if (token) {
24
+ request.headers['Authorization'] = `Bearer ${token}`;
25
+ }
26
+ return request;
27
+ }, error => {
28
+ return Promise.reject(error);
29
+ });
30
+
31
+ api.interceptors.response.use(
32
+ response => response,
33
+ async error => {
34
+ const originalRequest = error.config;
35
+
36
+ // Only handle 401 once
37
+ if (error.response?.status === 401 && !originalRequest._retry) {
38
+ originalRequest._retry = true;
39
+
40
+ try {
41
+ const newToken = await refreshToken();
42
+
43
+ // Set new token on headers
44
+ api.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
45
+ originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
46
+
47
+ // return the retried request
48
+ return api(originalRequest);
49
+ } catch (refreshError) {
50
+ // Token refresh failed, reject to trigger outer catch
51
+ return Promise.reject(refreshError);
52
+ }
53
+ }
54
+
55
+ // Any other errors
56
+ return Promise.reject(error);
57
+ }
58
+ );
59
+
60
+ export default api;