@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.
- package/.github/workflows/publish.yml +79 -0
- package/CHANGELOG.md +16 -0
- package/LICENSE +22 -0
- package/README.md +206 -0
- package/bin/thinr.js +78 -0
- package/commands/device.js +272 -0
- package/commands/logout.js +45 -0
- package/commands/product.js +198 -0
- package/lib/api.js +60 -0
- package/lib/auth.js +463 -0
- package/lib/config.js +88 -0
- package/lib/console.js +172 -0
- package/lib/device.js +76 -0
- package/lib/property.js +134 -0
- package/lib/proxy.js +310 -0
- package/lib/resource.js +124 -0
- package/lib/status.js +96 -0
- package/package.json +39 -0
|
@@ -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
|
+

|
|
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 © [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
|
+
}
|