@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,79 @@
1
+ name: "publish"
2
+
3
+ on:
4
+ push:
5
+ branches: [ master, develop ]
6
+
7
+ jobs:
8
+ publish:
9
+ name: publish
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+
14
+ - name: Extract branch name
15
+ shell: bash
16
+ run: echo "branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV
17
+
18
+ - name: checkout
19
+ uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0
22
+ ref: ${{ env.branch }}
23
+
24
+ - name: node
25
+ uses: actions/setup-node@v4
26
+ with:
27
+ node-version: 22
28
+ registry-url: https://registry.npmjs.org
29
+
30
+ - name: Get tag version
31
+ run: |
32
+ echo "tag_version=`awk '/^## \[[0-9]+/' CHANGELOG.md | awk 'NR==1 && -F"\[\]" {print $2}' 2>/dev/null | tail -c +2 | head -c -2`" >> $GITHUB_ENV
33
+
34
+ - name: Check if tag is for develop and if it already exists
35
+ run: |
36
+ git fetch --prune --tags
37
+ if [ ${{ env.branch }} == 'develop' ]; then
38
+ for i in {1..100}; do
39
+ if [ `git tag --list | grep ${{ env.tag_version }}"-alpha.${i}" | wc -l` -eq 0 ]; then
40
+ tag_version=${{ env.tag_version }}"-alpha.${i}"
41
+ echo "tag_version=${tag_version}" >> $GITHUB_ENV
42
+ echo "npm_tag_version=develop" >> $GITHUB_ENV
43
+ break;
44
+ fi
45
+ done
46
+ elif [ ${{ env.branch }} == 'master' ]; then
47
+ echo "npm_tag_version=latest" >> $GITHUB_ENV
48
+ elif [ `git tag --list | grep -q ${{ env.tag_version }}` -ne 0 ]; then exit 1; fi
49
+
50
+ - name: update version
51
+ run: |
52
+ npm version --no-git-tag-version ${{ env.tag_version }}
53
+
54
+ - name: publish
55
+ run: npm publish --tag ${{ env.npm_tag_version }} --access public
56
+ env:
57
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
58
+
59
+ - name: Get changelog body
60
+ run: |
61
+ awk '/^## \[[0-9]+/,EOF' CHANGELOG.md | awk '(NR>1)' | awk '1;/^## \[[0-9]+/{exit}' | sed '$d' > release_body.md
62
+
63
+ - name: Set pre-release boolean
64
+ run: |
65
+ prerelease=false
66
+ if [ ${{ env.branch }} == 'develop' ]; then
67
+ prerelease=true
68
+ fi
69
+ echo "prerelease=$prerelease" >> $GITHUB_ENV
70
+
71
+ - name: Create GitHub release
72
+ uses: ncipollo/release-action@v1
73
+ with:
74
+ prerelease: ${{ env.prerelease }}
75
+ commit: ${{ env.branch }}
76
+ tag: ${{ env.tag_version }}
77
+ name: ${{ env.tag_version }}
78
+ token: ${{ secrets.GITHUB_TOKEN }}
79
+ bodyFile: "release_body.md"
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ ## [1.0.0] - 2025-06-20
4
+
5
+ ### Added
6
+
7
+ - Initial release of ThinR CLI, a command-line interface for managing remote devices
8
+ - OAuth device flow authentication
9
+ - Device and product management commands
10
+ - Remote terminal access
11
+ - Proxy creation
12
+ - Device status checks
13
+ - Property listing and retrieval
14
+ - Device resource listing and execution
15
+
16
+ [1.0.0]: https://github.com/Thin-Remote/thinr-cli/tag/1.0.0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+
2
+ MIT License
3
+
4
+ Copyright (c) 2025 Thinger.io
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # ThingR - ThingRemote CLI
2
+
3
+ A modern command-line interface for managing remote devices through the ThingRemote platform.
4
+
5
+ ![ThingR CLI](https://raw.githubusercontent.com/Thin-Remote/thinr/main/assets/thinr-cli.png)
6
+
7
+ ## Features
8
+
9
+ - **Remote Terminal**: Connect to a device's terminal and interact with it directly
10
+ - **TCP/Web Proxy**: Create proxies to remote devices, either for TCP connections or web interfaces
11
+ - **Device Status**: Check connection status and statistics for a device
12
+ - **Secure Authentication**: Support for both username/password and token-based authentication
13
+ - **Modern UI**: Clean and intuitive command-line interface
14
+
15
+ ## Installation
16
+
17
+ ### From NPM (recommended)
18
+
19
+ ```bash
20
+ npm install -g thinr-cli
21
+ ```
22
+
23
+ ### From Source
24
+
25
+ ```bash
26
+ git clone https://github.com/Thin-Remote/thinr-cli.git
27
+ cd thinr-cli
28
+ npm install
29
+ npm link
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ### Initial Setup
35
+
36
+ Run the CLI without any parameters to set up:
37
+
38
+ ```bash
39
+ thinr
40
+ ```
41
+
42
+ You'll be prompted to authenticate either with username/password or with an existing token.
43
+
44
+ ### Available Commands
45
+
46
+ #### Device
47
+
48
+ ##### Console
49
+
50
+ Connect to a device's terminal:
51
+
52
+ ```bash
53
+ thinr device <deviceId> console
54
+ ```
55
+
56
+ ##### Proxy
57
+
58
+ Create a TCP or HTTP proxy to a remote device:
59
+
60
+ ```bash
61
+ thinr device <deviceId> tcp <target> [options]
62
+ thinr device <deviceId> tls <target> [options]
63
+ thinr device <deviceId> http <taget> [options]
64
+ ```
65
+
66
+ The target being the remote port on the device (defaults: tcp: 22, http: 80, tls: 443).
67
+
68
+ Options:
69
+ - `-w, --web`: Create an HTTP proxy for web interface instead of TCP
70
+ - `--no-secure`: Do not use SSL for the target connection
71
+ - `--no-open`: Do not open browser automatically (web mode only)
72
+
73
+ Examples:
74
+ ```bash
75
+ # Create a TCP proxy to SSH
76
+ thinr proxy RevPi20679
77
+
78
+ # Create a web interface proxy
79
+ thinr proxy RevPi20679 --web
80
+
81
+ # Create a proxy to a specific port with SSL
82
+ thinr proxy RevPi20679 --remote-port 8080 --ssl
83
+
84
+ # Create a proxy to a specific address
85
+ thinr proxy RevPi20679 --remote-address 192.168.1.100
86
+ ```
87
+
88
+ ##### Status
89
+
90
+ Check the connection status of a device:
91
+
92
+ ```bash
93
+ thinr status <deviceId> [options]
94
+ ```
95
+
96
+ Options:
97
+ - `-j, --json`: Output the status in JSON format
98
+
99
+ ##### Property
100
+
101
+ Retrieve the properties of a device:
102
+
103
+ ```bash
104
+ thinr device <deviceId> property [options]
105
+ ```
106
+
107
+ Retrieve a property from a device:
108
+
109
+ ```bash
110
+ thinr device <deviceId> property <propertyId> [options]
111
+ ```
112
+
113
+ Options:
114
+ - `-j, --json`: Output the status in JSON format
115
+ - `-f, --field`: Field to extract from the property or resource (e.g., -f data.value)
116
+
117
+ ##### Resource
118
+
119
+ Get the avaiable resources of a device:
120
+
121
+ ```bash
122
+ thinr device <deviceId> resource [options]
123
+ ```
124
+
125
+ Options:
126
+ - `-j, --json`: Output the status in JSON format
127
+
128
+ Execute a resource on a device:
129
+
130
+ ```bash
131
+ thinr device <deviceId> resource <resourceId> [options]
132
+ ```
133
+
134
+ Options:
135
+ - `-i, --input`: Input for the resource (e.g., -i param1=value1 -i param2=value2)
136
+ - `-j, --json`: Output the status in JSON format
137
+ - `-f, --field`: Field to extract from the property or resource (e.g., -f data.value)
138
+
139
+ ##### Product
140
+
141
+ Iterate over all devices of a specific product in order to retrieve a property or execute a resource:
142
+
143
+ ```bash
144
+ thinr product <productId> property [options]
145
+ thinr product <productId> resource [options]
146
+ ```
147
+
148
+ Options:
149
+ - `-j, --json`: Output the status in JSON format
150
+ - `-f, --field`: Field to extract from the property or resource (e.g., -f data.value)
151
+
152
+ Options for executing a resource:
153
+ - `-i, --input`: Input for the resource (e.g., -i param1=value1 -i param2=value2)
154
+
155
+
156
+ ### Help
157
+
158
+ Get help for any command:
159
+
160
+ ```bash
161
+ thinr --help
162
+ thinr device --help
163
+ thinr logout --help
164
+ ```
165
+
166
+ ## Development
167
+
168
+ The project structure is organized as follows:
169
+
170
+ ```
171
+ thinr/
172
+ ├── bin/ # Main executable
173
+ ├── lib/ # Core functionality
174
+ ├── commands/ # Command implementations
175
+ ├── package.json
176
+ └── README.md
177
+ ```
178
+
179
+ ### Building
180
+
181
+ ```bash
182
+ npm install
183
+ npm link
184
+ ```
185
+
186
+ ### Testing
187
+
188
+ ```bash
189
+ npm test
190
+ ```
191
+
192
+ ## License
193
+
194
+ <a href="http://opensource.org/">
195
+ <img style="float: right;" width="100px" height="137px" src="/assets/OSI_Standard_Logo_0.svg">
196
+ </a>
197
+
198
+ The plugin is licensed under the [MIT License](http://opensource.org/licenses/MIT):
199
+
200
+ Copyright &copy; [Thinger.io](http://thinger.io)
201
+
202
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
203
+
204
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
205
+
206
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/bin/thinr.js ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import figlet from 'figlet';
6
+ import ora from 'ora';
7
+ import { configExists } from '../lib/config.js';
8
+ import { authenticate } from '../lib/auth.js';
9
+ import { deviceCommand } from '../commands/device.js';
10
+ import { logoutCommand } from '../commands/logout.js';
11
+ import { productCommand } from "../commands/product.js";
12
+ import { setBaseURL } from '../lib/api.js';
13
+
14
+ // Function to display the banner
15
+ function displayBanner() {
16
+ console.log(
17
+ chalk.cyan(
18
+ figlet.textSync('thinr-cli', { horizontalLayout: 'full' })
19
+ )
20
+ );
21
+ console.log(chalk.blue('Thin Remote CLI - Remote management for IoT devices'));
22
+ console.log();
23
+ }
24
+
25
+ const program = new Command();
26
+
27
+ // Set up CLI information
28
+ program
29
+ .name('thinr')
30
+ .description('CLI for ThinRemote - Remote management for IoT devices')
31
+ .version('1.0.0');
32
+
33
+ // Register commands
34
+ deviceCommand(program);
35
+ productCommand(program);
36
+ logoutCommand(program);
37
+
38
+ // Handle help
39
+ const originalHelp = program.help;
40
+ program.help = function(cb) {
41
+ displayBanner();
42
+ return originalHelp.call(this, cb);
43
+ };
44
+
45
+ // initialize API base URL
46
+ if (configExists()) {
47
+ const config = readConfig();
48
+ if (config.server) {
49
+ // Initialize API with the server if configured
50
+ setBaseURL(`https://${config.server}`);
51
+ }
52
+ }
53
+
54
+ // Handle no command (just "thinr")
55
+ if (process.argv.length <= 2) {
56
+ const spinner = ora('Checking configuration...').start();
57
+
58
+ try {
59
+ if (!configExists()) {
60
+ // Display banner only if configuration doesn't exist
61
+ displayBanner();
62
+ spinner.succeed('No configuration found. Starting setup...');
63
+ await authenticate();
64
+ program.help();
65
+ } else {
66
+ spinner.succeed('Configuration found');
67
+ // Show help if already configured, but don't display banner twice
68
+ program.help();
69
+ }
70
+ } catch (error) {
71
+ spinner.fail(`Configuration error: ${error.message}`);
72
+ process.exit(1);
73
+ }
74
+ spinner.succeed('Configuration found');
75
+ } else {
76
+ // Parse arguments - don't show banner for regular commands
77
+ program.parse(process.argv);
78
+ }
@@ -0,0 +1,272 @@
1
+ import { handleProxyAction } from '../lib/proxy.js';
2
+ import { connectToDeviceConsole } from '../lib/console.js';
3
+ import { getDeviceStatus, formatDeviceStatus } from '../lib/status.js';
4
+ import { formatDeviceProperty, getDeviceProperty, getDeviceProperties, formatDeviceProperties } from '../lib/property.js';
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+ import { configExists } from '../lib/config.js';
8
+ import { getDeviceResources, executeDeviceResource, formatDeviceResources } from "../lib/resource.js";
9
+
10
+
11
+ /**
12
+ * Register the device command with all device-related subcommands
13
+ * @param {Command} program - Commander program instance
14
+ */
15
+ export function deviceCommand(program) {
16
+
17
+ const device = program.command('device').description('Device commands');
18
+
19
+ device
20
+ .argument('<deviceId>', 'ID of the device to manage')
21
+ .description('Manage device connections and proxies')
22
+ .allowUnknownOption(true)
23
+ .allowExcessArguments(true)
24
+ .action(async (deviceId, options, command) => {
25
+ // Get additional arguments manually
26
+ const args = command.args.slice(1); // Skip deviceId
27
+ const action = args[0];
28
+ const target = args[1] && !args[1].startsWith('-') ? args[1] : null; // Check if target is provided, skip if it's an option
29
+
30
+ // Check if configured
31
+ if (!configExists()) {
32
+ console.error(chalk.red('Error: Not configured. Run thinr without parameters to set up.'));
33
+ process.exit(1);
34
+ }
35
+
36
+ // If no action provided, show help
37
+ if (!action) {
38
+ printHelp(deviceId);
39
+ return;
40
+ }
41
+
42
+ // Parse options manually from process.argv
43
+ const parsedOptions = {
44
+ port: null,
45
+ json: false,
46
+ openBrowser: true,
47
+ targetSecure: true,
48
+ inputs: {},
49
+ field: "",
50
+ };
51
+
52
+ const argv = process.argv;
53
+ for (let i = 0; i < argv.length; i++) {
54
+ if (argv[i] === '-p' || argv[i] === '--port') {
55
+ parsedOptions.port = argv[i + 1];
56
+ i++; // Skip the value
57
+ } else if (argv[i] === '-j' || argv[i] === '--json') {
58
+ parsedOptions.json = true;
59
+ } else if (argv[i] === '--no-open') {
60
+ parsedOptions.open = false;
61
+ } else if (argv[i] === '-i' || argv[i] === '--input') {
62
+ let inputPair = argv[i + 1].split('=');
63
+ parsedOptions.inputs[inputPair[0]] = inputPair[1];
64
+ } else if (argv[i] === '-f' || argv[i] === '--field') {
65
+ parsedOptions.field = argv[i + 1];
66
+ } else if (argv[i] === '--no-secure') {
67
+ parsedOptions.targetSecure = false;
68
+ } else if (argv[i] === '-h' || argv[i] === '--help') {
69
+ printHelp(deviceId);
70
+ return;
71
+ }
72
+ }
73
+
74
+ switch (action.toLowerCase()) {
75
+ case 'tcp':
76
+ await handleProxyAction(deviceId, 'tcp', target, parsedOptions);
77
+ break;
78
+
79
+ case 'tls':
80
+ await handleProxyAction(deviceId, 'tls', target, parsedOptions);
81
+ break;
82
+
83
+ case 'http':
84
+ await handleProxyAction(deviceId, 'http', target, parsedOptions);
85
+ break;
86
+
87
+ case 'console':
88
+ try {
89
+ await connectToDeviceConsole(deviceId);
90
+ } catch (error) {
91
+ console.error(chalk.red(`Error: ${error.message}`));
92
+ process.exit(1);
93
+ }
94
+ break;
95
+
96
+ case 'status': {
97
+ const spinner = ora(`Checking status of ${deviceId}...`).start();
98
+ try {
99
+ const status = await getDeviceStatus(deviceId);
100
+ spinner.succeed(`Device ${deviceId} found`);
101
+
102
+ if (parsedOptions.json) {
103
+ console.log(JSON.stringify(status, null, 2));
104
+ } else {
105
+ console.log(formatDeviceStatus(deviceId, status));
106
+ }
107
+ } catch (error) {
108
+ spinner.fail(`Status check failed`);
109
+ console.error(chalk.red(`Error: ${error.message}`));
110
+ process.exit(1);
111
+ }
112
+ break;
113
+ }
114
+
115
+ case 'property': {
116
+
117
+ if ( !target ) {
118
+ const spinner = ora(`Getting properties for ${deviceId}...`).start();
119
+ try {
120
+ const result = await getDeviceProperties(deviceId);
121
+ spinner.succeed(`Properties for ${deviceId} retrieved successfully`);
122
+ if (parsedOptions.json) {
123
+ console.log(result);
124
+ } else {
125
+ console.log(chalk.green(`Resource result for device ${deviceId}:`));
126
+ console.log(formatDeviceProperties(deviceId, result));
127
+ }
128
+ } catch (error) {
129
+ spinner.fail(`Property retrieval failed`);
130
+ console.error(chalk.red(`Error: ${error.message}`));
131
+ process.exit(1);
132
+ }
133
+ } else {
134
+ const spinner = ora(`Retrieving property for ${deviceId}...`).start();
135
+ try {
136
+ const property = await getDeviceProperty(deviceId, target);
137
+ spinner.succeed(`Property ${target} for ${deviceId} found`);
138
+
139
+ if (parsedOptions.json) {
140
+ if ( parsedOptions.field ) {
141
+ console.log(parsedOptions.field.split('.').reduce((obj, key) => obj && obj[key], property));
142
+ } else {
143
+ console.log(JSON.stringify(property, null, 2));
144
+ }
145
+ } else {
146
+ console.log(formatDeviceProperty(deviceId, target, property));
147
+ }
148
+ } catch (error) {
149
+ spinner.fail(`Property retrieval failed`);
150
+ console.error(chalk.red(`Error: ${error.message}`));
151
+ process.exit(1);
152
+ }
153
+
154
+ }
155
+
156
+ break;
157
+ }
158
+
159
+ case 'resource': {
160
+
161
+ if (!target) {
162
+ const spinner = ora(`Getting resources for ${deviceId}...`).start();
163
+ try {
164
+
165
+ const result = await getDeviceResources(deviceId);
166
+ spinner.succeed(`Succesfully retrieved resources for ${deviceId}`);
167
+
168
+ if (parsedOptions.json) {
169
+ console.log(result);
170
+ } else {
171
+ //console.log(chalk.green(`Resource ${target} result for device ${deviceId}:`));
172
+ console.log(chalk.green(`Resource result for device ${deviceId}:`));
173
+ console.log(formatDeviceResources(deviceId, result));
174
+ }
175
+ } catch (error) {
176
+ spinner.fail(`Resource retrieval failed`);
177
+ console.error(chalk.red(`Error: ${error.message}`));
178
+ process.exit(1);
179
+ }
180
+
181
+ } else {
182
+ const spinner = ora(`Executing resource ${target} for ${deviceId}...`).start();
183
+ try {
184
+
185
+ const result = await executeDeviceResource(deviceId, target, parsedOptions.inputs);
186
+ spinner.succeed(`Resource ${target} executed successfully for ${deviceId}`);
187
+
188
+ if ( parsedOptions.field ) {
189
+ console.log(parsedOptions.field.split('.').reduce((obj, key) => obj && obj[key], result));
190
+ } else {
191
+ console.log(result);
192
+ }
193
+
194
+ } catch (error) {
195
+ spinner.fail(`Resource execution failed`);
196
+ console.error(chalk.red(`Error: ${error.message}`));
197
+ process.exit(1);
198
+ }
199
+ }
200
+
201
+ break;
202
+ }
203
+
204
+ default:
205
+ console.error(chalk.red(`Error: Unknown action '${action}'. Available actions: tcp, tls, http, console, status, property, resource.\n`));
206
+ printHelp(deviceId);
207
+ process.exit(1);
208
+ }
209
+ });
210
+
211
+ }
212
+ function printHelp(deviceId) {
213
+ console.log(`Usage: thinr device ${deviceId} <command> [options]
214
+
215
+ Available commands for device "${deviceId}":
216
+
217
+ tcp [target] Create a TCP proxy (no TLS)
218
+ Example: thinr device ${deviceId} tcp 22
219
+
220
+ tls [target] Create a TLS proxy
221
+ Example: thinr device ${deviceId} tls 443
222
+
223
+ http [target] Create an HTTP proxy
224
+ Example: thinr device ${deviceId} http 8080
225
+
226
+ console Open terminal console
227
+ Example: thinr device ${deviceId} console
228
+
229
+ status Check device status
230
+ Example: thinr device ${deviceId} status
231
+
232
+ property Get device properties
233
+ Example: thinr device ${deviceId} property
234
+
235
+ property [propertyId] Get a specific property of the device
236
+ Example: thinr device ${deviceId} property uptime
237
+
238
+ resource Get resources of the device
239
+ Example: thinr device ${deviceId} resource
240
+
241
+ resource [resource] Execute a specific resource of the device
242
+ Example: thinr device ${deviceId} resource reboot
243
+
244
+ Arguments:
245
+
246
+ target Target for proxy commands (address:port, port, or address)
247
+ Defaults: tcp=22, tls=443, http=80
248
+
249
+ propertyId ID of the property to retrieve (for property command)
250
+
251
+ resource Name of the resource to execute (for resource command)
252
+
253
+ Options (for proxy commands):
254
+ -p, --port <port> Local port to use (default: random)
255
+ --no-secure Do not use SSL/TLS for the target
256
+ --no-open Do not open browser (http only)
257
+
258
+ Options (for status command):
259
+ -j, --json Output as JSON
260
+
261
+ Options (for property command):
262
+ -j, --json Output as JSON
263
+ -f, --field <field> Field to extract from the property (e.g., -f data.value)
264
+
265
+ Options (for resource command):
266
+ -i, --input <input>=<value> Input for the resource (e.g., -i param1=value1 -i param2=value2)
267
+ -j, --json Output as JSON
268
+ -f, --field <field> Field to extract from the property (e.g., -f data.value)
269
+
270
+ Use "thinr device ${deviceId} <command> --help" for more details on each command.`);
271
+
272
+ }