@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 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
+ [![npm version](https://badge.fury.io/js/@involvex%2Fsyncstuff-cli.svg)](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
+ [![Sponsor](https://img.shields.io/badge/Sponsor-involvex-ff69b4)](https://github.com/sponsors/involvex)
76
+
77
+ ## Author
78
+
79
+ [![GitHub](https://img.shields.io/badge/GitHub-involvex-000000)](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
+ }
@@ -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,9 @@
1
+ import type { DebugMode } from "../../core";
2
+
3
+ export const showDebug = (debugMode: DebugMode) => {
4
+ if (!debugMode.enabled) {
5
+ return;
6
+ } else {
7
+ console.log("Debug mode enabled");
8
+ }
9
+ };
@@ -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
+ }