@involvex/syncstuff-cli 0.0.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/.eslintignore +1 -0
- package/.prettierrc +12 -0
- package/Readme.md +98 -0
- package/bunfig.toml +26 -0
- package/eslint.config.ts +61 -0
- package/package.json +49 -0
- package/src/cli/commands/debug.ts +9 -0
- package/src/cli/commands/device.ts +224 -0
- package/src/cli/commands/devices.ts +78 -0
- package/src/cli/commands/help.ts +159 -0
- package/src/cli/commands/index.ts +0 -0
- package/src/cli/commands/login.ts +69 -0
- package/src/cli/commands/logout.ts +23 -0
- package/src/cli/commands/transfer.ts +148 -0
- package/src/cli/commands/version.ts +35 -0
- package/src/cli/commands/whoami.ts +63 -0
- package/src/cli/index.ts +86 -0
- package/src/core.ts +13 -0
- package/src/utils/api-client.ts +174 -0
- package/src/utils/config.ts +41 -0
- package/src/utils/context.ts +68 -0
- package/src/utils/ui.ts +83 -0
- package/tsconfig.json +31 -0
package/.eslintignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
eslint.config.ts
|
package/.prettierrc
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"printWidth": 80,
|
|
3
|
+
"tabWidth": 2,
|
|
4
|
+
"useTabs": false,
|
|
5
|
+
"semi": true,
|
|
6
|
+
"singleQuote": false,
|
|
7
|
+
"trailingComma": "all",
|
|
8
|
+
"bracketSpacing": true,
|
|
9
|
+
"jsxBracketSameLine": false,
|
|
10
|
+
"arrowParens": "avoid",
|
|
11
|
+
"plugins": ["prettier-plugin-organize-imports"]
|
|
12
|
+
}
|
package/Readme.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# SyncStuff CLI
|
|
2
|
+
|
|
3
|
+
SyncStuff CLI is a command line interface for the [SyncStuff](https://syncstuff-web.involvex.workers.dev/) service.
|
|
4
|
+
|
|
5
|
+
[](https://badge.fury.io/js/@involvex%2Fsyncstuff-cli)
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @involvex/syncstuff-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
syncstuff help # Show help
|
|
17
|
+
syncstuff help <command> # Show help for a specific command
|
|
18
|
+
syncstuff --version / -v # Show CLI version
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Global Flags
|
|
22
|
+
|
|
23
|
+
All commands support the following global flags:
|
|
24
|
+
|
|
25
|
+
| Flag | Description |
|
|
26
|
+
| ------------- | ------------------------------------- |
|
|
27
|
+
| `-h, --help` | Show help for any command |
|
|
28
|
+
| `-d, --debug` | Enable debug mode with verbose output |
|
|
29
|
+
|
|
30
|
+
**Examples:**
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
syncstuff devices -d # List devices with debug output
|
|
34
|
+
syncstuff device -h # Show help for device command
|
|
35
|
+
syncstuff --debug login # Login with debug output
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Authentication
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
syncstuff login # Login to your account
|
|
42
|
+
syncstuff whoami # Display your user profile
|
|
43
|
+
syncstuff logout # Logout from your account
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Device Management
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# List all devices
|
|
50
|
+
syncstuff devices
|
|
51
|
+
|
|
52
|
+
# Device command with subcommands
|
|
53
|
+
syncstuff device --list # List available devices (alias: -l)
|
|
54
|
+
syncstuff device # Interactive device selection
|
|
55
|
+
syncstuff device <id> # Connect to a specific device by ID
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### File Transfer
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
syncstuff transfer <file> # Transfer a file to a connected device
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Debug Mode
|
|
65
|
+
|
|
66
|
+
Enable debug mode to see detailed output including API requests, config state, and command execution details:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
syncstuff -d devices # Debug flag before command
|
|
70
|
+
syncstuff devices -d # Debug flag after command
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Sponsor
|
|
74
|
+
|
|
75
|
+
[](https://github.com/sponsors/involvex)
|
|
76
|
+
|
|
77
|
+
## Author
|
|
78
|
+
|
|
79
|
+
[](https://github.com/involvex)
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
84
|
+
|
|
85
|
+
## Changelog
|
|
86
|
+
|
|
87
|
+
- 0.0.1
|
|
88
|
+
- Initial release
|
|
89
|
+
|
|
90
|
+
## Contributing
|
|
91
|
+
|
|
92
|
+
Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) for more information.
|
|
93
|
+
|
|
94
|
+
## Support
|
|
95
|
+
|
|
96
|
+
Support the development of SyncStuff CLI by sponsoring [involvex](https://github.com/sponsors/involvex).
|
|
97
|
+
or on PayPal: [involvex](https://paypal.me/involvex)
|
|
98
|
+
Buy me a coffee: [involvex](https://www.buymeacoffee.com/involvex)
|
package/bunfig.toml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build]
|
|
2
|
+
# Build output directory
|
|
3
|
+
outdir = "dist"
|
|
4
|
+
|
|
5
|
+
# Minification
|
|
6
|
+
minify = true
|
|
7
|
+
|
|
8
|
+
# Source maps
|
|
9
|
+
# sourcemap = true
|
|
10
|
+
|
|
11
|
+
# Target environment
|
|
12
|
+
target = "node"
|
|
13
|
+
|
|
14
|
+
[run]
|
|
15
|
+
# Node.js compatibility mode
|
|
16
|
+
compat = "node"
|
|
17
|
+
|
|
18
|
+
# Loader configuration for file types
|
|
19
|
+
loader = {
|
|
20
|
+
".css" = "css",
|
|
21
|
+
".js" = "jsx",
|
|
22
|
+
".ts" = "tsx",
|
|
23
|
+
".tsx" = "tsx",
|
|
24
|
+
".json" = "json",
|
|
25
|
+
".txt" = "text"
|
|
26
|
+
}
|
package/eslint.config.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
// import globals from "globals";
|
|
3
|
+
import json from "@eslint/json";
|
|
4
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
5
|
+
import tseslint from "typescript-eslint";
|
|
6
|
+
|
|
7
|
+
export default defineConfig([
|
|
8
|
+
globalIgnores([
|
|
9
|
+
"**/node_modules/**",
|
|
10
|
+
"**/dist/**",
|
|
11
|
+
"**/build/**",
|
|
12
|
+
"**/coverage/**",
|
|
13
|
+
"**/bin/**",
|
|
14
|
+
]),
|
|
15
|
+
{
|
|
16
|
+
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
|
17
|
+
ignores: [
|
|
18
|
+
"**/node_modules/**",
|
|
19
|
+
"**/dist/**",
|
|
20
|
+
"**/build/**",
|
|
21
|
+
"**/coverage/**",
|
|
22
|
+
"**/bin/**",
|
|
23
|
+
],
|
|
24
|
+
|
|
25
|
+
plugins: { js },
|
|
26
|
+
extends: ["js/recommended"],
|
|
27
|
+
languageOptions: {
|
|
28
|
+
globals: {
|
|
29
|
+
window: "readonly",
|
|
30
|
+
document: "readonly",
|
|
31
|
+
console: "readonly",
|
|
32
|
+
localStorage: "readonly",
|
|
33
|
+
module: "readonly",
|
|
34
|
+
setTimeout: "readonly",
|
|
35
|
+
clearTimeout: "readonly",
|
|
36
|
+
setInterval: "readonly",
|
|
37
|
+
clearInterval: "readonly",
|
|
38
|
+
IntersectionObserver: "readonly",
|
|
39
|
+
navigator: "readonly",
|
|
40
|
+
fetch: "readonly",
|
|
41
|
+
node: "readonly",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
tseslint.configs.recommended,
|
|
46
|
+
{
|
|
47
|
+
files: ["**/*.jsonc"],
|
|
48
|
+
plugins: { json },
|
|
49
|
+
language: "json/jsonc",
|
|
50
|
+
extends: ["json/recommended"],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
files: ["**/*.{ts,tsx}"],
|
|
54
|
+
languageOptions: {
|
|
55
|
+
parserOptions: {
|
|
56
|
+
tsconfigRootDir: import.meta.dirname,
|
|
57
|
+
project: "./tsconfig.json",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
]);
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@involvex/syncstuff-cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"homepage": "https://syncstuff-web.involvex.workers.dev/",
|
|
6
|
+
"sponsor": {
|
|
7
|
+
"url": "https://github.com/sponsors/involvex"
|
|
8
|
+
},
|
|
9
|
+
"author": {
|
|
10
|
+
"name": "involvex"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"syncstuff": "src/cli/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"dev": "bun run src/cli/index.ts",
|
|
17
|
+
"build": "bun build --target node --outfile dist/cli.js src/cli/index.ts",
|
|
18
|
+
"lint": "eslint src ",
|
|
19
|
+
"lint:fix": "eslint src --fix",
|
|
20
|
+
"format": "prettier --write .",
|
|
21
|
+
"format:check": "prettier --check .",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"prebuild": "bun run format && bun run lint:fix && bun run typecheck",
|
|
24
|
+
"prepublish": "bun run build"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@types/node": "25.0.3",
|
|
28
|
+
"chalk": "5.6.2",
|
|
29
|
+
"inquirer": "13.1.0",
|
|
30
|
+
"ora": "9.0.0",
|
|
31
|
+
"boxen": "8.0.1",
|
|
32
|
+
"table": "6.9.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@eslint/css": "0.14.1",
|
|
36
|
+
"@eslint/js": "9.39.2",
|
|
37
|
+
"@eslint/json": "0.14.0",
|
|
38
|
+
"@types/bun": "1.3.5",
|
|
39
|
+
"@types/inquirer": "9.0.9",
|
|
40
|
+
"eslint": "9.39.2",
|
|
41
|
+
"globals": "17.0.0",
|
|
42
|
+
"prettier": "3.7.4",
|
|
43
|
+
"prettier-plugin-organize-imports": "4.3.0",
|
|
44
|
+
"typescript-eslint": "8.52.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"typescript": "5.9.3"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import inquirer from "inquirer";
|
|
3
|
+
import { apiClient, type Device } from "../../utils/api-client.js";
|
|
4
|
+
import { debugLog, type CommandContext } from "../../utils/context.js";
|
|
5
|
+
import {
|
|
6
|
+
createSpinner,
|
|
7
|
+
createTable,
|
|
8
|
+
error,
|
|
9
|
+
info,
|
|
10
|
+
printHeader,
|
|
11
|
+
printSeparator,
|
|
12
|
+
success,
|
|
13
|
+
warning,
|
|
14
|
+
} from "../../utils/ui.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Device command - connect to a specific device or list available devices
|
|
18
|
+
* Usage:
|
|
19
|
+
* device --list / -l List available devices
|
|
20
|
+
* device <deviceId> Connect to a specific device
|
|
21
|
+
* device Interactive device selection
|
|
22
|
+
*/
|
|
23
|
+
export async function device(
|
|
24
|
+
args: string[],
|
|
25
|
+
ctx: CommandContext,
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
printHeader();
|
|
28
|
+
debugLog(ctx, "Device command called with args:", args);
|
|
29
|
+
|
|
30
|
+
// Check for --list flag
|
|
31
|
+
if (args.includes("--list") || args.includes("-l")) {
|
|
32
|
+
await listAvailableDevices(ctx);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check authentication
|
|
37
|
+
if (!apiClient.isAuthenticated()) {
|
|
38
|
+
error("You are not logged in. Please run 'syncstuff login' first.");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const deviceId = args.find(arg => !arg.startsWith("-"));
|
|
43
|
+
|
|
44
|
+
if (deviceId) {
|
|
45
|
+
await connectToDevice(deviceId, ctx);
|
|
46
|
+
} else {
|
|
47
|
+
await interactiveDeviceSelect(ctx);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* List all available devices
|
|
53
|
+
*/
|
|
54
|
+
async function listAvailableDevices(ctx: CommandContext): Promise<Device[]> {
|
|
55
|
+
debugLog(ctx, "Fetching available devices...");
|
|
56
|
+
|
|
57
|
+
if (!apiClient.isAuthenticated()) {
|
|
58
|
+
error("You are not logged in. Please run 'syncstuff login' first.");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const spinner = createSpinner("Fetching devices...");
|
|
63
|
+
spinner.start();
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const response = await apiClient.getDevices();
|
|
67
|
+
debugLog(ctx, "API response:", response);
|
|
68
|
+
|
|
69
|
+
if (response.success && response.data) {
|
|
70
|
+
spinner.succeed("Devices loaded");
|
|
71
|
+
printSeparator();
|
|
72
|
+
|
|
73
|
+
if (response.data.length === 0) {
|
|
74
|
+
info("No devices found. Connect a device to get started.");
|
|
75
|
+
printSeparator();
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const tableData = response.data.map(device => [
|
|
80
|
+
device.id.substring(0, 8) + "...",
|
|
81
|
+
device.name,
|
|
82
|
+
device.type,
|
|
83
|
+
device.platform,
|
|
84
|
+
device.is_online ? chalk.green("Online") : chalk.red("Offline"),
|
|
85
|
+
new Date(device.last_seen).toLocaleString(),
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
const table = createTable(tableData, [
|
|
89
|
+
"ID",
|
|
90
|
+
"Name",
|
|
91
|
+
"Type",
|
|
92
|
+
"Platform",
|
|
93
|
+
"Status",
|
|
94
|
+
"Last Seen",
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
console.log(table);
|
|
98
|
+
printSeparator();
|
|
99
|
+
success(`Found ${response.data.length} device(s)`);
|
|
100
|
+
printSeparator();
|
|
101
|
+
|
|
102
|
+
return response.data;
|
|
103
|
+
} else {
|
|
104
|
+
spinner.fail("Failed to fetch devices");
|
|
105
|
+
if (
|
|
106
|
+
response.error?.includes("404") ||
|
|
107
|
+
response.error?.includes("Not found")
|
|
108
|
+
) {
|
|
109
|
+
info("Devices endpoint not yet implemented in API");
|
|
110
|
+
info("This feature will be available soon!");
|
|
111
|
+
} else {
|
|
112
|
+
error(response.error || "Unknown error");
|
|
113
|
+
}
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
} catch (err) {
|
|
117
|
+
spinner.fail("Error fetching devices");
|
|
118
|
+
error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
119
|
+
debugLog(ctx, "Full error:", err);
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Connect to a specific device by ID
|
|
126
|
+
*/
|
|
127
|
+
async function connectToDevice(
|
|
128
|
+
deviceId: string,
|
|
129
|
+
ctx: CommandContext,
|
|
130
|
+
): Promise<void> {
|
|
131
|
+
debugLog(ctx, "Connecting to device:", deviceId);
|
|
132
|
+
|
|
133
|
+
const spinner = createSpinner(`Connecting to device ${deviceId}...`);
|
|
134
|
+
spinner.start();
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
// First, verify the device exists
|
|
138
|
+
const response = await apiClient.getDevices();
|
|
139
|
+
debugLog(ctx, "Devices response:", response);
|
|
140
|
+
|
|
141
|
+
if (!response.success || !response.data) {
|
|
142
|
+
spinner.fail("Failed to fetch devices");
|
|
143
|
+
error(response.error || "Could not retrieve device list");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const targetDevice = response.data.find(
|
|
148
|
+
d => d.id === deviceId || d.id.startsWith(deviceId),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
if (!targetDevice) {
|
|
152
|
+
spinner.fail("Device not found");
|
|
153
|
+
error(`No device found with ID: ${deviceId}`);
|
|
154
|
+
info("Run 'syncstuff device --list' to see available devices");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!targetDevice.is_online) {
|
|
159
|
+
spinner.warn("Device is offline");
|
|
160
|
+
warning(`Device "${targetDevice.name}" is currently offline`);
|
|
161
|
+
info("The device must be running SyncStuff to connect");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// TODO: Implement actual WebRTC/signaling connection
|
|
166
|
+
spinner.succeed(`Connected to "${targetDevice.name}"`);
|
|
167
|
+
printSeparator();
|
|
168
|
+
|
|
169
|
+
console.log(chalk.cyan("Device Details:"));
|
|
170
|
+
console.log(` Name: ${targetDevice.name}`);
|
|
171
|
+
console.log(` ID: ${targetDevice.id}`);
|
|
172
|
+
console.log(` Platform: ${targetDevice.platform}`);
|
|
173
|
+
console.log(` Type: ${targetDevice.type}`);
|
|
174
|
+
console.log(
|
|
175
|
+
` Status: ${targetDevice.is_online ? chalk.green("Online") : chalk.red("Offline")}`,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
printSeparator();
|
|
179
|
+
info("Connection established. Ready for file transfer.");
|
|
180
|
+
info("Use 'syncstuff transfer <file>' to send files to this device.");
|
|
181
|
+
printSeparator();
|
|
182
|
+
} catch (err) {
|
|
183
|
+
spinner.fail("Connection failed");
|
|
184
|
+
error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
185
|
+
debugLog(ctx, "Full error:", err);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Interactive device selection
|
|
191
|
+
*/
|
|
192
|
+
async function interactiveDeviceSelect(ctx: CommandContext): Promise<void> {
|
|
193
|
+
debugLog(ctx, "Starting interactive device selection");
|
|
194
|
+
|
|
195
|
+
const devices = await listAvailableDevices(ctx);
|
|
196
|
+
|
|
197
|
+
if (devices.length === 0) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const onlineDevices = devices.filter(d => d.is_online);
|
|
202
|
+
|
|
203
|
+
if (onlineDevices.length === 0) {
|
|
204
|
+
warning("No online devices available to connect to");
|
|
205
|
+
info("Make sure SyncStuff is running on your other devices");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
printSeparator();
|
|
210
|
+
|
|
211
|
+
const { selectedDevice } = await inquirer.prompt<{ selectedDevice: string }>([
|
|
212
|
+
{
|
|
213
|
+
type: "list",
|
|
214
|
+
name: "selectedDevice",
|
|
215
|
+
message: "Select a device to connect to:",
|
|
216
|
+
choices: onlineDevices.map(d => ({
|
|
217
|
+
name: `${d.name} (${d.platform}) - ${d.type}`,
|
|
218
|
+
value: d.id,
|
|
219
|
+
})),
|
|
220
|
+
},
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
await connectToDevice(selectedDevice, ctx);
|
|
224
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { apiClient } from "../../utils/api-client.js";
|
|
3
|
+
import { debugLog, type CommandContext } from "../../utils/context.js";
|
|
4
|
+
import {
|
|
5
|
+
createSpinner,
|
|
6
|
+
createTable,
|
|
7
|
+
error,
|
|
8
|
+
info,
|
|
9
|
+
printHeader,
|
|
10
|
+
printSeparator,
|
|
11
|
+
success,
|
|
12
|
+
} from "../../utils/ui.js";
|
|
13
|
+
|
|
14
|
+
export async function listDevices(ctx: CommandContext): Promise<void> {
|
|
15
|
+
printHeader();
|
|
16
|
+
debugLog(ctx, "Fetching devices list");
|
|
17
|
+
|
|
18
|
+
if (!apiClient.isAuthenticated()) {
|
|
19
|
+
error("You are not logged in. Please run 'syncstuff login' first.");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const spinner = createSpinner("Fetching devices...");
|
|
24
|
+
spinner.start();
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const response = await apiClient.getDevices();
|
|
28
|
+
|
|
29
|
+
if (response.success && response.data) {
|
|
30
|
+
spinner.succeed("Devices loaded");
|
|
31
|
+
printSeparator();
|
|
32
|
+
|
|
33
|
+
if (response.data.length === 0) {
|
|
34
|
+
info("No devices found. Connect a device to get started.");
|
|
35
|
+
printSeparator();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const tableData = response.data.map(device => [
|
|
40
|
+
device.id.substring(0, 8) + "...",
|
|
41
|
+
device.name,
|
|
42
|
+
device.type,
|
|
43
|
+
device.platform,
|
|
44
|
+
device.is_online ? chalk.green("Online") : chalk.red("Offline"),
|
|
45
|
+
new Date(device.last_seen).toLocaleString(),
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
const table = createTable(tableData, [
|
|
49
|
+
"ID",
|
|
50
|
+
"Name",
|
|
51
|
+
"Type",
|
|
52
|
+
"Platform",
|
|
53
|
+
"Status",
|
|
54
|
+
"Last Seen",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
console.log(table);
|
|
58
|
+
printSeparator();
|
|
59
|
+
success(`Found ${response.data.length} device(s)`);
|
|
60
|
+
printSeparator();
|
|
61
|
+
} else {
|
|
62
|
+
spinner.fail("Failed to fetch devices");
|
|
63
|
+
if (
|
|
64
|
+
response.error?.includes("404") ||
|
|
65
|
+
response.error?.includes("Not found")
|
|
66
|
+
) {
|
|
67
|
+
info("Devices endpoint not yet implemented in API");
|
|
68
|
+
info("This feature will be available soon!");
|
|
69
|
+
} else {
|
|
70
|
+
error(response.error || "Unknown error");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
spinner.fail("Error fetching devices");
|
|
75
|
+
error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|