@involvex/syncstuff-cli 0.0.1 → 0.0.3
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/dist/cli.js +31575 -0
- package/package.json +2 -2
- package/src/cli/commands/transfer.ts +81 -14
- package/src/cli/commands/version.ts +1 -1
- package/src/utils/ui.ts +6 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@involvex/syncstuff-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"homepage": "https://syncstuff-web.involvex.workers.dev/",
|
|
6
6
|
"sponsor": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "involvex"
|
|
11
11
|
},
|
|
12
12
|
"bin": {
|
|
13
|
-
"syncstuff": "
|
|
13
|
+
"syncstuff": "dist/cli.js"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"dev": "bun run src/cli/index.ts",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { existsSync, statSync } from "fs";
|
|
3
3
|
import inquirer from "inquirer";
|
|
4
|
-
import { resolve } from "path";
|
|
4
|
+
import { basename, resolve } from "path";
|
|
5
5
|
import { apiClient } from "../../utils/api-client.js";
|
|
6
6
|
import { debugLog, type CommandContext } from "../../utils/context.js";
|
|
7
7
|
import {
|
|
@@ -11,8 +11,20 @@ import {
|
|
|
11
11
|
printHeader,
|
|
12
12
|
printSeparator,
|
|
13
13
|
success,
|
|
14
|
+
warning,
|
|
14
15
|
} from "../../utils/ui.js";
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Format file size for display
|
|
19
|
+
*/
|
|
20
|
+
function formatFileSize(bytes: number): string {
|
|
21
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
22
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
|
|
23
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
24
|
+
return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
|
|
25
|
+
return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
export async function transferFile(
|
|
17
29
|
filePath: string | undefined,
|
|
18
30
|
ctx: CommandContext,
|
|
@@ -24,11 +36,13 @@ export async function transferFile(
|
|
|
24
36
|
error("You are not logged in. Please run 'syncstuff login' first.");
|
|
25
37
|
process.exit(1);
|
|
26
38
|
}
|
|
39
|
+
debugLog(ctx, "Authentication verified");
|
|
27
40
|
|
|
28
41
|
let targetFile = filePath;
|
|
29
42
|
|
|
30
43
|
// If no file path provided, prompt for it
|
|
31
44
|
if (!targetFile) {
|
|
45
|
+
debugLog(ctx, "No file path provided, prompting user");
|
|
32
46
|
const answers = await inquirer.prompt([
|
|
33
47
|
{
|
|
34
48
|
type: "input",
|
|
@@ -58,6 +72,7 @@ export async function transferFile(
|
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
const resolvedPath = resolve(targetFile);
|
|
75
|
+
debugLog(ctx, "Resolved file path", { resolvedPath });
|
|
61
76
|
|
|
62
77
|
if (!existsSync(resolvedPath)) {
|
|
63
78
|
error(`File not found: ${resolvedPath}`);
|
|
@@ -71,16 +86,31 @@ export async function transferFile(
|
|
|
71
86
|
|
|
72
87
|
// Get file info
|
|
73
88
|
const stats = statSync(resolvedPath);
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
89
|
+
const fileName = basename(resolvedPath);
|
|
90
|
+
const fileSize = formatFileSize(stats.size);
|
|
91
|
+
|
|
92
|
+
debugLog(ctx, "File info", {
|
|
93
|
+
name: fileName,
|
|
94
|
+
size: stats.size,
|
|
95
|
+
formattedSize: fileSize,
|
|
96
|
+
modified: stats.mtime,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
printSeparator();
|
|
100
|
+
console.log(chalk.cyan("File Details:"));
|
|
101
|
+
console.log(` Name: ${chalk.bold(fileName)}`);
|
|
102
|
+
console.log(` Path: ${resolvedPath}`);
|
|
103
|
+
console.log(` Size: ${chalk.yellow(fileSize)}`);
|
|
104
|
+
printSeparator();
|
|
78
105
|
|
|
79
106
|
// Get device list
|
|
80
|
-
const devicesSpinner = createSpinner("Fetching devices...");
|
|
107
|
+
const devicesSpinner = createSpinner("Fetching available devices...");
|
|
81
108
|
devicesSpinner.start();
|
|
82
109
|
|
|
110
|
+
debugLog(ctx, "Fetching devices from API");
|
|
83
111
|
const devicesResponse = await apiClient.getDevices();
|
|
112
|
+
debugLog(ctx, "Devices response", devicesResponse);
|
|
113
|
+
|
|
84
114
|
devicesSpinner.stop();
|
|
85
115
|
|
|
86
116
|
if (
|
|
@@ -88,15 +118,32 @@ export async function transferFile(
|
|
|
88
118
|
!devicesResponse.data ||
|
|
89
119
|
devicesResponse.data.length === 0
|
|
90
120
|
) {
|
|
91
|
-
error
|
|
92
|
-
"
|
|
93
|
-
|
|
121
|
+
if (devicesResponse.error) {
|
|
122
|
+
debugLog(ctx, "Devices API error", { error: devicesResponse.error });
|
|
123
|
+
error(`Failed to fetch devices: ${devicesResponse.error}`);
|
|
124
|
+
} else {
|
|
125
|
+
warning("No devices available.");
|
|
126
|
+
info("Make sure you have at least one device connected to your account.");
|
|
127
|
+
info("Run 'syncstuff devices' to see your registered devices.");
|
|
128
|
+
}
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const onlineDevices = devicesResponse.data.filter(d => d.is_online);
|
|
133
|
+
debugLog(ctx, "Device counts", {
|
|
134
|
+
total: devicesResponse.data.length,
|
|
135
|
+
online: onlineDevices.length,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (onlineDevices.length === 0) {
|
|
139
|
+
warning("All devices are offline.");
|
|
140
|
+
info("Start Syncstuff on your target device to receive files.");
|
|
94
141
|
process.exit(1);
|
|
95
142
|
}
|
|
96
143
|
|
|
97
144
|
// Prompt for device selection
|
|
98
145
|
const deviceChoices = devicesResponse.data.map(device => ({
|
|
99
|
-
name: `${device.name} (${device.platform}) - ${device.is_online ? "Online" : "Offline"}`,
|
|
146
|
+
name: `${device.name} (${device.platform}) - ${device.is_online ? chalk.green("Online") : chalk.red("Offline")}`,
|
|
100
147
|
value: device.id,
|
|
101
148
|
disabled: !device.is_online ? "Offline" : false,
|
|
102
149
|
}));
|
|
@@ -110,22 +157,38 @@ export async function transferFile(
|
|
|
110
157
|
},
|
|
111
158
|
]);
|
|
112
159
|
|
|
160
|
+
const selectedDevice = devicesResponse.data.find(
|
|
161
|
+
d => d.id === deviceAnswer.deviceId,
|
|
162
|
+
);
|
|
163
|
+
debugLog(ctx, "Selected device", selectedDevice);
|
|
164
|
+
|
|
113
165
|
// Transfer file
|
|
166
|
+
printSeparator();
|
|
114
167
|
const transferSpinner = createSpinner(
|
|
115
|
-
`Transferring ${chalk.cyan(
|
|
168
|
+
`Transferring ${chalk.cyan(fileName)} to ${chalk.yellow(selectedDevice?.name || "device")}...`,
|
|
116
169
|
);
|
|
117
170
|
transferSpinner.start();
|
|
118
171
|
|
|
119
172
|
try {
|
|
173
|
+
debugLog(ctx, "Initiating transfer API call", {
|
|
174
|
+
deviceId: deviceAnswer.deviceId,
|
|
175
|
+
filePath: resolvedPath,
|
|
176
|
+
});
|
|
177
|
+
|
|
120
178
|
const transferResponse = await apiClient.transferFile(
|
|
121
179
|
deviceAnswer.deviceId,
|
|
122
180
|
resolvedPath,
|
|
123
181
|
);
|
|
124
182
|
|
|
183
|
+
debugLog(ctx, "Transfer response", transferResponse);
|
|
184
|
+
|
|
125
185
|
if (transferResponse.success) {
|
|
126
186
|
transferSpinner.succeed("File transfer initiated!");
|
|
127
187
|
printSeparator();
|
|
128
188
|
success(`Transfer ID: ${transferResponse.data?.transferId || "N/A"}`);
|
|
189
|
+
info(`File: ${fileName}`);
|
|
190
|
+
info(`Size: ${fileSize}`);
|
|
191
|
+
info(`Target: ${selectedDevice?.name || deviceAnswer.deviceId}`);
|
|
129
192
|
info("File is being synced to the target device");
|
|
130
193
|
printSeparator();
|
|
131
194
|
} else {
|
|
@@ -134,15 +197,19 @@ export async function transferFile(
|
|
|
134
197
|
transferResponse.error?.includes("404") ||
|
|
135
198
|
transferResponse.error?.includes("Not found")
|
|
136
199
|
) {
|
|
137
|
-
|
|
200
|
+
warning("File transfer endpoint not yet implemented in API");
|
|
138
201
|
info("This feature will be available soon!");
|
|
202
|
+
debugLog(ctx, "API endpoint not implemented");
|
|
139
203
|
} else {
|
|
140
|
-
error(transferResponse.error || "Unknown error");
|
|
204
|
+
error(transferResponse.error || "Unknown error occurred");
|
|
205
|
+
debugLog(ctx, "Transfer error", { error: transferResponse.error });
|
|
141
206
|
}
|
|
142
207
|
}
|
|
143
208
|
} catch (err) {
|
|
144
209
|
transferSpinner.fail("Transfer error");
|
|
145
|
-
|
|
210
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
211
|
+
error(`Error: ${errorMessage}`);
|
|
212
|
+
debugLog(ctx, "Exception during transfer", { error: err });
|
|
146
213
|
process.exit(1);
|
|
147
214
|
}
|
|
148
215
|
}
|
|
@@ -13,7 +13,7 @@ export function showversion() {
|
|
|
13
13
|
try {
|
|
14
14
|
const packagePath = join(__dirname, "../../../package.json");
|
|
15
15
|
const packageJson = JSON.parse(readFileSync(packagePath, "utf-8"));
|
|
16
|
-
const version = packageJson.version
|
|
16
|
+
const version = packageJson.version;
|
|
17
17
|
|
|
18
18
|
const versionBox = createBox(
|
|
19
19
|
chalk.cyan.bold("Syncstuff CLI\n\n") +
|
package/src/utils/ui.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import boxen from "boxen";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
+
import { readFileSync } from "fs";
|
|
3
4
|
import ora from "ora";
|
|
5
|
+
import { join } from "path";
|
|
4
6
|
import Table from "table";
|
|
5
7
|
|
|
6
8
|
export function success(message: string): void {
|
|
@@ -69,9 +71,12 @@ export function animateText(text: string, delay: number = 50): Promise<void> {
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
export function printHeader(): void {
|
|
74
|
+
const packagePath = join(__dirname, "../../../../package.json");
|
|
75
|
+
const packageJson = JSON.parse(readFileSync(packagePath, "utf-8"));
|
|
76
|
+
const version = packageJson.version;
|
|
72
77
|
const header = chalk.cyan.bold(`
|
|
73
78
|
╔═══════════════════════════════════════╗
|
|
74
|
-
║ Syncstuff CLI
|
|
79
|
+
║ Syncstuff CLI v${version} ║
|
|
75
80
|
║ Seamless Sync Across Devices ║
|
|
76
81
|
╚═══════════════════════════════════════╝
|
|
77
82
|
`);
|