@involvex/syncstuff-cli 0.0.6 → 0.0.9
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/Agends.md +110 -0
- package/dist/cli.js +4561 -677
- package/package.json +5 -2
- package/src/cli/commands/debug.ts +1 -2
- package/src/cli/commands/device.ts +12 -13
- package/src/cli/commands/devices.ts +12 -13
- package/src/cli/commands/help.ts +15 -0
- package/src/cli/commands/listen.ts +142 -0
- package/src/cli/commands/login.ts +1 -1
- package/src/cli/commands/logout.ts +1 -1
- package/src/cli/commands/scan.ts +4 -4
- package/src/cli/commands/transfer.ts +1 -1
- package/src/cli/commands/version.ts +4 -4
- package/src/cli/commands/whoami.ts +1 -1
- package/src/cli/index.ts +7 -1
- package/src/utils/config.ts +1 -0
- package/src/utils/network.ts +124 -124
- package/src/utils/ui.ts +1 -1
- package/.eslintignore +0 -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.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"homepage": "https://syncstuff-web.involvex.workers.dev/",
|
|
6
6
|
"sponsor": {
|
|
@@ -29,7 +29,10 @@
|
|
|
29
29
|
"inquirer": "13.1.0",
|
|
30
30
|
"ora": "9.0.0",
|
|
31
31
|
"boxen": "8.0.1",
|
|
32
|
-
"table": "6.9.0"
|
|
32
|
+
"table": "6.9.0",
|
|
33
|
+
"bonjour-service": "^1.1.1",
|
|
34
|
+
"@syncstuff/network-types": "workspace:*",
|
|
35
|
+
"uuid": "^9.0.1"
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
35
38
|
"@eslint/css": "0.14.1",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import inquirer from "inquirer";
|
|
3
3
|
import { apiClient, type Device } from "../../utils/api-client.js";
|
|
4
|
-
import {
|
|
4
|
+
import { type CommandContext, debugLog } from "../../utils/context.js";
|
|
5
5
|
import {
|
|
6
6
|
createSpinner,
|
|
7
7
|
createTable,
|
|
@@ -77,7 +77,7 @@ async function listAvailableDevices(ctx: CommandContext): Promise<Device[]> {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
const tableData = response.data.map(device => [
|
|
80
|
-
device.id.substring(0, 8)
|
|
80
|
+
`${device.id.substring(0, 8)}...`,
|
|
81
81
|
device.name,
|
|
82
82
|
device.type,
|
|
83
83
|
device.platform,
|
|
@@ -100,19 +100,18 @@ async function listAvailableDevices(ctx: CommandContext): Promise<Device[]> {
|
|
|
100
100
|
printSeparator();
|
|
101
101
|
|
|
102
102
|
return response.data;
|
|
103
|
+
}
|
|
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!");
|
|
103
111
|
} else {
|
|
104
|
-
|
|
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 [];
|
|
112
|
+
error(response.error || "Unknown error");
|
|
115
113
|
}
|
|
114
|
+
return [];
|
|
116
115
|
} catch (err) {
|
|
117
116
|
spinner.fail("Error fetching devices");
|
|
118
117
|
error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { apiClient } from "../../utils/api-client.js";
|
|
3
|
-
import {
|
|
3
|
+
import { type CommandContext, debugLog } from "../../utils/context.js";
|
|
4
4
|
import {
|
|
5
5
|
createSpinner,
|
|
6
6
|
createTable,
|
|
@@ -60,7 +60,7 @@ async function fetchAndDisplayDevices(ctx: CommandContext): Promise<boolean> {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const tableData = response.data.map(device => [
|
|
63
|
-
device.id.substring(0, 8)
|
|
63
|
+
`${device.id.substring(0, 8)}...`,
|
|
64
64
|
device.name,
|
|
65
65
|
device.type,
|
|
66
66
|
device.platform,
|
|
@@ -82,19 +82,18 @@ async function fetchAndDisplayDevices(ctx: CommandContext): Promise<boolean> {
|
|
|
82
82
|
success(`Found ${response.data.length} device(s)`);
|
|
83
83
|
printSeparator();
|
|
84
84
|
return true;
|
|
85
|
+
}
|
|
86
|
+
spinner.fail("Failed to fetch devices");
|
|
87
|
+
if (
|
|
88
|
+
response.error?.includes("404") ||
|
|
89
|
+
response.error?.includes("Not found")
|
|
90
|
+
) {
|
|
91
|
+
info("Devices endpoint not yet implemented in API");
|
|
92
|
+
info("This feature will be available soon!");
|
|
85
93
|
} else {
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
response.error?.includes("404") ||
|
|
89
|
-
response.error?.includes("Not found")
|
|
90
|
-
) {
|
|
91
|
-
info("Devices endpoint not yet implemented in API");
|
|
92
|
-
info("This feature will be available soon!");
|
|
93
|
-
} else {
|
|
94
|
-
error(response.error || "Unknown error");
|
|
95
|
-
}
|
|
96
|
-
return false;
|
|
94
|
+
error(response.error || "Unknown error");
|
|
97
95
|
}
|
|
96
|
+
return false;
|
|
98
97
|
} catch (err) {
|
|
99
98
|
spinner.fail("Error fetching devices");
|
|
100
99
|
error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
package/src/cli/commands/help.ts
CHANGED
|
@@ -98,6 +98,21 @@ ${chalk.bold("Examples:")}
|
|
|
98
98
|
syncstuff scan Scan for 10 seconds
|
|
99
99
|
syncstuff scan --timeout 30 Scan for 30 seconds
|
|
100
100
|
syncstuff scan --watch Continuously monitor for devices`,
|
|
101
|
+
|
|
102
|
+
listen: `${chalk.cyan.bold("listen")}
|
|
103
|
+
|
|
104
|
+
Start listening for incoming file transfers.
|
|
105
|
+
|
|
106
|
+
${chalk.bold("Usage:")}
|
|
107
|
+
syncstuff listen
|
|
108
|
+
|
|
109
|
+
${chalk.bold("Description:")}
|
|
110
|
+
Starts a local HTTP server and advertises this CLI on the network.
|
|
111
|
+
Allows other SyncStuff devices (like the Android app) to discover
|
|
112
|
+
this computer and send files to it directly via local network.
|
|
113
|
+
|
|
114
|
+
${chalk.bold("Examples:")}
|
|
115
|
+
syncstuff listen`,
|
|
101
116
|
};
|
|
102
117
|
|
|
103
118
|
export async function showHelp(command?: string): Promise<void> {
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { createServer, type IncomingMessage, type ServerResponse } from "http";
|
|
3
|
+
import { networkInterfaces } from "os";
|
|
4
|
+
import { type CommandContext, debugLog } from "../../utils/context.js";
|
|
5
|
+
import { networkScanner } from "../../utils/network.js"; // Added this line
|
|
6
|
+
import {
|
|
7
|
+
createSpinner,
|
|
8
|
+
error,
|
|
9
|
+
info,
|
|
10
|
+
printHeader,
|
|
11
|
+
success,
|
|
12
|
+
} from "../../utils/ui.js";
|
|
13
|
+
|
|
14
|
+
const PORT = 54321; // Default port for SyncStuff CLI
|
|
15
|
+
|
|
16
|
+
export async function listen(
|
|
17
|
+
_args: string[],
|
|
18
|
+
ctx: CommandContext,
|
|
19
|
+
): Promise<void> {
|
|
20
|
+
printHeader();
|
|
21
|
+
debugLog(ctx, "Starting listen command");
|
|
22
|
+
|
|
23
|
+
console.log(chalk.cyan("Starting SyncStuff CLI Listener..."));
|
|
24
|
+
console.log(
|
|
25
|
+
chalk.gray(
|
|
26
|
+
"This allows other devices to discover this CLI and send files directly.\n",
|
|
27
|
+
),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const spinner = createSpinner("Initializing network listener...");
|
|
31
|
+
spinner.start();
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// 1. Start HTTP Server
|
|
35
|
+
const server = createServer(handleRequest);
|
|
36
|
+
|
|
37
|
+
server.listen(PORT, "0.0.0.0", () => {
|
|
38
|
+
spinner.succeed(`HTTP Server running on port ${PORT}`);
|
|
39
|
+
|
|
40
|
+
const ips = getLocalIPs();
|
|
41
|
+
if (ips.length > 0) {
|
|
42
|
+
info("Listening on:");
|
|
43
|
+
ips.forEach(ip => {
|
|
44
|
+
console.log(` http://${ip}:${PORT}`);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 2. Start UDP Broadcast (Advertising)
|
|
49
|
+
startAdvertising();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
server.on("error", err => {
|
|
53
|
+
spinner.fail("Failed to start server");
|
|
54
|
+
error(`Server error: ${err.message}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
57
|
+
} catch (err) {
|
|
58
|
+
spinner.fail("Failed to start listener");
|
|
59
|
+
error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Handle incoming HTTP requests
|
|
66
|
+
*/
|
|
67
|
+
function handleRequest(req: IncomingMessage, res: ServerResponse) {
|
|
68
|
+
// CORS headers
|
|
69
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
70
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
|
|
71
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
72
|
+
|
|
73
|
+
if (req.method === "OPTIONS") {
|
|
74
|
+
res.writeHead(200);
|
|
75
|
+
res.end();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (req.method === "GET" && req.url === "/status") {
|
|
80
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
81
|
+
res.end(
|
|
82
|
+
JSON.stringify({ status: "online", type: "cli", version: "0.0.6" }),
|
|
83
|
+
);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (req.method === "POST" && req.url === "/upload") {
|
|
88
|
+
// Handle file upload
|
|
89
|
+
info(`\nIncoming transfer request from ${req.socket.remoteAddress}`);
|
|
90
|
+
|
|
91
|
+
// Minimal handler for now - just acknowledge
|
|
92
|
+
// In a full implementation, we'd parse multipart/form-data or raw stream
|
|
93
|
+
// and save to disk
|
|
94
|
+
|
|
95
|
+
let dataLength = 0;
|
|
96
|
+
req.on("data", chunk => {
|
|
97
|
+
dataLength += chunk.length;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
req.on("end", () => {
|
|
101
|
+
success(`Received ${dataLength} bytes`);
|
|
102
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
103
|
+
res.end(JSON.stringify({ success: true, message: "File received" }));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
res.writeHead(404);
|
|
110
|
+
res.end("Not Found");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get local IP addresses
|
|
115
|
+
*/
|
|
116
|
+
function getLocalIPs(): string[] {
|
|
117
|
+
const nets = networkInterfaces();
|
|
118
|
+
const results: string[] = [];
|
|
119
|
+
|
|
120
|
+
for (const name of Object.keys(nets)) {
|
|
121
|
+
for (const net of nets[name] || []) {
|
|
122
|
+
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
|
123
|
+
if (net.family === "IPv4" && !net.internal) {
|
|
124
|
+
results.push(net.address);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return results;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Advertise presence via UDP
|
|
133
|
+
*/
|
|
134
|
+
function startAdvertising() {
|
|
135
|
+
info("\nAdvertising CLI on local network...");
|
|
136
|
+
|
|
137
|
+
// Start advertising
|
|
138
|
+
// We use a random ID for the CLI session for now
|
|
139
|
+
networkScanner.startAdvertising("SyncStuff CLI", PORT);
|
|
140
|
+
|
|
141
|
+
info("Ready to receive files. Press Ctrl+C to stop.");
|
|
142
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import inquirer from "inquirer";
|
|
2
2
|
import { apiClient } from "../../utils/api-client.js";
|
|
3
|
-
import {
|
|
3
|
+
import { type CommandContext, debugLog } from "../../utils/context.js";
|
|
4
4
|
import {
|
|
5
5
|
createSpinner,
|
|
6
6
|
error,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { apiClient } from "../../utils/api-client.js";
|
|
2
|
-
import {
|
|
2
|
+
import { type CommandContext, debugLog } from "../../utils/context.js";
|
|
3
3
|
import { error, printHeader, printSeparator, success } from "../../utils/ui.js";
|
|
4
4
|
|
|
5
5
|
export async function logout(ctx: CommandContext): Promise<void> {
|
package/src/cli/commands/scan.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { type CommandContext, debugLog } from "../../utils/context.js";
|
|
3
|
+
import { type LocalDevice, networkScanner } from "../../utils/network.js";
|
|
4
4
|
import {
|
|
5
5
|
createSpinner,
|
|
6
6
|
createTable,
|
|
@@ -21,7 +21,7 @@ export async function scanLocal(
|
|
|
21
21
|
ctx: CommandContext,
|
|
22
22
|
): Promise<void> {
|
|
23
23
|
const timeout = args.includes("--timeout")
|
|
24
|
-
? parseInt(args[args.indexOf("--timeout") + 1] || "10", 10)
|
|
24
|
+
? Number.parseInt(args[args.indexOf("--timeout") + 1] || "10", 10)
|
|
25
25
|
: 10;
|
|
26
26
|
const continuous = args.includes("--watch") || args.includes("-w");
|
|
27
27
|
|
|
@@ -135,7 +135,7 @@ async function continuousScan(
|
|
|
135
135
|
|
|
136
136
|
function displayDevices(devices: LocalDevice[]): void {
|
|
137
137
|
const tableData = devices.map(device => [
|
|
138
|
-
device.id.substring(0, 8)
|
|
138
|
+
`${device.id.substring(0, 8)}...`,
|
|
139
139
|
device.name,
|
|
140
140
|
device.platform,
|
|
141
141
|
`${device.ip}:${device.port}`,
|
|
@@ -3,7 +3,7 @@ import { existsSync, statSync } from "fs";
|
|
|
3
3
|
import inquirer from "inquirer";
|
|
4
4
|
import { basename, resolve } from "path";
|
|
5
5
|
import { apiClient } from "../../utils/api-client.js";
|
|
6
|
-
import {
|
|
6
|
+
import { type CommandContext, debugLog } from "../../utils/context.js";
|
|
7
7
|
import {
|
|
8
8
|
createSpinner,
|
|
9
9
|
error,
|
|
@@ -18,8 +18,8 @@ export function showversion() {
|
|
|
18
18
|
|
|
19
19
|
if (DebugMode.enabled === true) {
|
|
20
20
|
console.log(chalk.yellow("Debug mode enabled"));
|
|
21
|
-
console.log(
|
|
22
|
-
console.log(
|
|
21
|
+
console.log(`Package path: ${packagePath}`);
|
|
22
|
+
console.log(`Version: ${version}`);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const versionBox = createBox(
|
|
@@ -27,7 +27,7 @@ export function showversion() {
|
|
|
27
27
|
chalk.bold("Version:") +
|
|
28
28
|
` ${chalk.green(version)}\n` +
|
|
29
29
|
chalk.bold("Package:") +
|
|
30
|
-
|
|
30
|
+
" @involvex/syncstuff-cli",
|
|
31
31
|
{
|
|
32
32
|
title: "Version Information",
|
|
33
33
|
titleAlignment: "center",
|
|
@@ -40,7 +40,7 @@ export function showversion() {
|
|
|
40
40
|
if (DebugMode.enabled === true) {
|
|
41
41
|
const packagePath = join(__dirname, "../../../package.json");
|
|
42
42
|
console.log(chalk.yellow("Debug mode enabled"));
|
|
43
|
-
console.log(
|
|
43
|
+
console.log(`Packagepath: ${packagePath}`);
|
|
44
44
|
console.log(chalk.yellow("Version: 0.0.1 (unable to read package.json)"));
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { apiClient } from "../../utils/api-client.js";
|
|
3
|
-
import {
|
|
3
|
+
import { type CommandContext, debugLog } from "../../utils/context.js";
|
|
4
4
|
import {
|
|
5
5
|
createBox,
|
|
6
6
|
createSpinner,
|
package/src/cli/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { debugLog, parseArgs
|
|
2
|
+
import { type CommandContext, debugLog, parseArgs } from "../utils/context.js";
|
|
3
3
|
import { printHeader } from "../utils/ui.js";
|
|
4
4
|
import { checkForUpdates } from "../utils/update-checker.js";
|
|
5
5
|
|
|
@@ -72,6 +72,12 @@ export async function run() {
|
|
|
72
72
|
await scanLocal(commandArgs, ctx);
|
|
73
73
|
}
|
|
74
74
|
break;
|
|
75
|
+
case "listen":
|
|
76
|
+
{
|
|
77
|
+
const { listen } = await import("./commands/listen.js");
|
|
78
|
+
await listen(commandArgs, ctx);
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
75
81
|
case "help":
|
|
76
82
|
{
|
|
77
83
|
const { showHelp } = await import("./commands/help.js");
|