@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@involvex/syncstuff-cli",
3
- "version": "0.0.6",
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",
@@ -3,7 +3,6 @@ import { DebugMode } from "../../core";
3
3
  export const showDebug = () => {
4
4
  if (!DebugMode.enabled) {
5
5
  return;
6
- } else {
7
- console.log("Debug mode enabled");
8
6
  }
7
+ console.log("Debug mode enabled");
9
8
  };
@@ -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 { debugLog, type CommandContext } from "../../utils/context.js";
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
- 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 [];
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 { debugLog, type CommandContext } from "../../utils/context.js";
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
- 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!");
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)}`);
@@ -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 { debugLog, type CommandContext } from "../../utils/context.js";
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 { debugLog, type CommandContext } from "../../utils/context.js";
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> {
@@ -1,6 +1,6 @@
1
1
  import chalk from "chalk";
2
- import { debugLog, type CommandContext } from "../../utils/context.js";
3
- import { networkScanner, type LocalDevice } from "../../utils/network.js";
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 { debugLog, type CommandContext } from "../../utils/context.js";
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("Package path: " + packagePath);
22
- console.log("Version: " + version);
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
- ` @involvex/syncstuff-cli`,
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("Packagepath: " + packagePath);
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 { debugLog, type CommandContext } from "../../utils/context.js";
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, type CommandContext } from "../utils/context.js";
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");
@@ -12,6 +12,7 @@ export interface Config {
12
12
  role: string;
13
13
  } | null;
14
14
  apiUrl?: string;
15
+ deviceId?: string;
15
16
  }
16
17
 
17
18
  const CONFIG_DIR = join(homedir(), ".syncstuff");