@involvex/syncstuff-cli 0.0.6 → 0.0.7
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 +143 -1
- package/package.json +1 -1
- package/src/cli/commands/help.ts +15 -0
- package/src/cli/commands/listen.ts +142 -0
- package/src/cli/index.ts +6 -0
- package/src/utils/network.ts +45 -0
package/dist/cli.js
CHANGED
|
@@ -9807,7 +9807,21 @@ ${source_default.bold("Description:")}
|
|
|
9807
9807
|
${source_default.bold("Examples:")}
|
|
9808
9808
|
syncstuff scan Scan for 10 seconds
|
|
9809
9809
|
syncstuff scan --timeout 30 Scan for 30 seconds
|
|
9810
|
-
syncstuff scan --watch Continuously monitor for devices
|
|
9810
|
+
syncstuff scan --watch Continuously monitor for devices`,
|
|
9811
|
+
listen: `${source_default.cyan.bold("listen")}
|
|
9812
|
+
|
|
9813
|
+
Start listening for incoming file transfers.
|
|
9814
|
+
|
|
9815
|
+
${source_default.bold("Usage:")}
|
|
9816
|
+
syncstuff listen
|
|
9817
|
+
|
|
9818
|
+
${source_default.bold("Description:")}
|
|
9819
|
+
Starts a local HTTP server and advertises this CLI on the network.
|
|
9820
|
+
Allows other SyncStuff devices (like the Android app) to discover
|
|
9821
|
+
this computer and send files to it directly via local network.
|
|
9822
|
+
|
|
9823
|
+
${source_default.bold("Examples:")}
|
|
9824
|
+
syncstuff listen`
|
|
9811
9825
|
};
|
|
9812
9826
|
});
|
|
9813
9827
|
|
|
@@ -31614,6 +31628,33 @@ class NetworkScanner {
|
|
|
31614
31628
|
});
|
|
31615
31629
|
});
|
|
31616
31630
|
}
|
|
31631
|
+
startAdvertising(deviceName, port, platform = "cli") {
|
|
31632
|
+
const socket = createSocket({ type: "udp4", reuseAddr: true });
|
|
31633
|
+
socket.bind(() => {
|
|
31634
|
+
socket.setBroadcast(true);
|
|
31635
|
+
});
|
|
31636
|
+
const advertise = () => {
|
|
31637
|
+
const message = JSON.stringify({
|
|
31638
|
+
service: "syncstuff",
|
|
31639
|
+
deviceId: "cli-device-" + Math.floor(Math.random() * 1e4),
|
|
31640
|
+
deviceName,
|
|
31641
|
+
platform,
|
|
31642
|
+
port,
|
|
31643
|
+
version: "0.0.6",
|
|
31644
|
+
timestamp: Date.now()
|
|
31645
|
+
});
|
|
31646
|
+
const localIPs = this.getLocalIPs();
|
|
31647
|
+
for (const ip of localIPs) {
|
|
31648
|
+
const parts = ip.split(".");
|
|
31649
|
+
parts[3] = "255";
|
|
31650
|
+
const broadcastAddr = parts.join(".");
|
|
31651
|
+
socket.send(message, 0, message.length, SYNCSTUFF_PORT, broadcastAddr);
|
|
31652
|
+
}
|
|
31653
|
+
socket.send(message, 0, message.length, SYNCSTUFF_PORT, "224.0.0.251");
|
|
31654
|
+
};
|
|
31655
|
+
advertise();
|
|
31656
|
+
return setInterval(advertise, 3000);
|
|
31657
|
+
}
|
|
31617
31658
|
}
|
|
31618
31659
|
var SYNCSTUFF_PORT = 5353, networkScanner;
|
|
31619
31660
|
var init_network = __esm(() => {
|
|
@@ -31734,6 +31775,101 @@ var init_scan = __esm(() => {
|
|
|
31734
31775
|
init_ui();
|
|
31735
31776
|
});
|
|
31736
31777
|
|
|
31778
|
+
// src/cli/commands/listen.ts
|
|
31779
|
+
var exports_listen = {};
|
|
31780
|
+
__export(exports_listen, {
|
|
31781
|
+
listen: () => listen
|
|
31782
|
+
});
|
|
31783
|
+
import { createServer } from "http";
|
|
31784
|
+
import { networkInterfaces as networkInterfaces2 } from "os";
|
|
31785
|
+
async function listen(_args, ctx) {
|
|
31786
|
+
printHeader();
|
|
31787
|
+
debugLog(ctx, "Starting listen command");
|
|
31788
|
+
console.log(source_default.cyan("Starting SyncStuff CLI Listener..."));
|
|
31789
|
+
console.log(source_default.gray(`This allows other devices to discover this CLI and send files directly.
|
|
31790
|
+
`));
|
|
31791
|
+
const spinner = createSpinner("Initializing network listener...");
|
|
31792
|
+
spinner.start();
|
|
31793
|
+
try {
|
|
31794
|
+
const server = createServer(handleRequest);
|
|
31795
|
+
server.listen(PORT, "0.0.0.0", () => {
|
|
31796
|
+
spinner.succeed(`HTTP Server running on port ${PORT}`);
|
|
31797
|
+
const ips = getLocalIPs();
|
|
31798
|
+
if (ips.length > 0) {
|
|
31799
|
+
info2("Listening on:");
|
|
31800
|
+
ips.forEach((ip) => {
|
|
31801
|
+
console.log(` http://${ip}:${PORT}`);
|
|
31802
|
+
});
|
|
31803
|
+
}
|
|
31804
|
+
startAdvertising();
|
|
31805
|
+
});
|
|
31806
|
+
server.on("error", (err) => {
|
|
31807
|
+
spinner.fail("Failed to start server");
|
|
31808
|
+
error2(`Server error: ${err.message}`);
|
|
31809
|
+
process.exit(1);
|
|
31810
|
+
});
|
|
31811
|
+
} catch (err) {
|
|
31812
|
+
spinner.fail("Failed to start listener");
|
|
31813
|
+
error2(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
31814
|
+
process.exit(1);
|
|
31815
|
+
}
|
|
31816
|
+
}
|
|
31817
|
+
function handleRequest(req, res) {
|
|
31818
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
31819
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
|
|
31820
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
31821
|
+
if (req.method === "OPTIONS") {
|
|
31822
|
+
res.writeHead(200);
|
|
31823
|
+
res.end();
|
|
31824
|
+
return;
|
|
31825
|
+
}
|
|
31826
|
+
if (req.method === "GET" && req.url === "/status") {
|
|
31827
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
31828
|
+
res.end(JSON.stringify({ status: "online", type: "cli", version: "0.0.6" }));
|
|
31829
|
+
return;
|
|
31830
|
+
}
|
|
31831
|
+
if (req.method === "POST" && req.url === "/upload") {
|
|
31832
|
+
info2(`
|
|
31833
|
+
Incoming transfer request from ${req.socket.remoteAddress}`);
|
|
31834
|
+
let dataLength = 0;
|
|
31835
|
+
req.on("data", (chunk) => {
|
|
31836
|
+
dataLength += chunk.length;
|
|
31837
|
+
});
|
|
31838
|
+
req.on("end", () => {
|
|
31839
|
+
success2(`Received ${dataLength} bytes`);
|
|
31840
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
31841
|
+
res.end(JSON.stringify({ success: true, message: "File received" }));
|
|
31842
|
+
});
|
|
31843
|
+
return;
|
|
31844
|
+
}
|
|
31845
|
+
res.writeHead(404);
|
|
31846
|
+
res.end("Not Found");
|
|
31847
|
+
}
|
|
31848
|
+
function getLocalIPs() {
|
|
31849
|
+
const nets = networkInterfaces2();
|
|
31850
|
+
const results = [];
|
|
31851
|
+
for (const name of Object.keys(nets)) {
|
|
31852
|
+
for (const net of nets[name] || []) {
|
|
31853
|
+
if (net.family === "IPv4" && !net.internal) {
|
|
31854
|
+
results.push(net.address);
|
|
31855
|
+
}
|
|
31856
|
+
}
|
|
31857
|
+
}
|
|
31858
|
+
return results;
|
|
31859
|
+
}
|
|
31860
|
+
function startAdvertising() {
|
|
31861
|
+
info2(`
|
|
31862
|
+
Advertising CLI on local network...`);
|
|
31863
|
+
networkScanner.startAdvertising("SyncStuff CLI", PORT);
|
|
31864
|
+
info2("Ready to receive files. Press Ctrl+C to stop.");
|
|
31865
|
+
}
|
|
31866
|
+
var PORT = 54321;
|
|
31867
|
+
var init_listen = __esm(() => {
|
|
31868
|
+
init_source();
|
|
31869
|
+
init_network();
|
|
31870
|
+
init_ui();
|
|
31871
|
+
});
|
|
31872
|
+
|
|
31737
31873
|
// src/core.ts
|
|
31738
31874
|
var DebugMode;
|
|
31739
31875
|
var init_core = __esm(() => {
|
|
@@ -31899,6 +32035,12 @@ async function run() {
|
|
|
31899
32035
|
await scanLocal2(commandArgs, ctx);
|
|
31900
32036
|
}
|
|
31901
32037
|
break;
|
|
32038
|
+
case "listen":
|
|
32039
|
+
{
|
|
32040
|
+
const { listen: listen2 } = await Promise.resolve().then(() => (init_listen(), exports_listen));
|
|
32041
|
+
await listen2(commandArgs, ctx);
|
|
32042
|
+
}
|
|
32043
|
+
break;
|
|
31902
32044
|
case "help":
|
|
31903
32045
|
{
|
|
31904
32046
|
const { showHelp: showHelp2 } = await Promise.resolve().then(() => (init_help(), exports_help));
|
package/package.json
CHANGED
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
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -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");
|
package/src/utils/network.ts
CHANGED
|
@@ -157,6 +157,51 @@ class NetworkScanner {
|
|
|
157
157
|
});
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Start advertising this device on the local network
|
|
163
|
+
*/
|
|
164
|
+
startAdvertising(
|
|
165
|
+
deviceName: string,
|
|
166
|
+
port: number,
|
|
167
|
+
platform = "cli",
|
|
168
|
+
): NodeJS.Timer {
|
|
169
|
+
const socket = createSocket({ type: "udp4", reuseAddr: true });
|
|
170
|
+
|
|
171
|
+
socket.bind(() => {
|
|
172
|
+
socket.setBroadcast(true);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const advertise = () => {
|
|
176
|
+
const message = JSON.stringify({
|
|
177
|
+
service: "syncstuff",
|
|
178
|
+
deviceId: "cli-device-" + Math.floor(Math.random() * 10000), // Simple random ID for CLI
|
|
179
|
+
deviceName,
|
|
180
|
+
platform,
|
|
181
|
+
port,
|
|
182
|
+
version: "0.0.6", // Should match package.json
|
|
183
|
+
timestamp: Date.now(),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const localIPs = this.getLocalIPs();
|
|
187
|
+
for (const ip of localIPs) {
|
|
188
|
+
const parts = ip.split(".");
|
|
189
|
+
parts[3] = "255";
|
|
190
|
+
const broadcastAddr = parts.join(".");
|
|
191
|
+
|
|
192
|
+
socket.send(message, 0, message.length, SYNCSTUFF_PORT, broadcastAddr);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Also send to multicast group
|
|
196
|
+
socket.send(message, 0, message.length, SYNCSTUFF_PORT, "224.0.0.251");
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Advertise immediately
|
|
200
|
+
advertise();
|
|
201
|
+
|
|
202
|
+
// Then every 3 seconds
|
|
203
|
+
return setInterval(advertise, 3000);
|
|
204
|
+
}
|
|
160
205
|
}
|
|
161
206
|
|
|
162
207
|
export const networkScanner = new NetworkScanner();
|