instbyte 1.11.1 → 1.12.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/README.md +16 -4
- package/bin/cli/resolve.js +55 -0
- package/bin/cli/send.js +161 -0
- package/bin/cli/status.js +67 -0
- package/bin/cli/watch.js +124 -0
- package/bin/instbyte.js +76 -21
- package/package.json +2 -1
- package/server/server.js +21 -2
package/README.md
CHANGED
|
@@ -189,6 +189,8 @@ The difference between *a tool you use* and *a tool you own.*
|
|
|
189
189
|
|
|
190
190
|
**Security hardened** — rate limiting on all write endpoints, magic number file validation, filename sanitisation, and forced download for executable file types.
|
|
191
191
|
|
|
192
|
+
**CLI companion** — `instbyte send`, `instbyte watch`, and `instbyte status` let you push files, pipe command output, sync clipboard, and check server health without opening a browser. Auto-discovers the running server from your working directory.
|
|
193
|
+
|
|
192
194
|
---
|
|
193
195
|
|
|
194
196
|
## Broadcasting
|
|
@@ -220,7 +222,7 @@ For HTTPS setup and advanced network configuration, see the [Deployment Guide](d
|
|
|
220
222
|
git clone https://github.com/mohitgauniyal/instbyte
|
|
221
223
|
cd instbyte
|
|
222
224
|
npm install
|
|
223
|
-
node
|
|
225
|
+
node bin/instbyte.js
|
|
224
226
|
```
|
|
225
227
|
|
|
226
228
|
---
|
|
@@ -237,9 +239,19 @@ node server/server.js
|
|
|
237
239
|
|
|
238
240
|
---
|
|
239
241
|
|
|
240
|
-
## Terminal Usage
|
|
242
|
+
## CLI & Terminal Usage
|
|
243
|
+
|
|
244
|
+
Send files, pipe command output, watch channels, and check server status from your terminal — no browser needed.
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
instbyte send ./build.zip --channel assets
|
|
248
|
+
instbyte send "http://staging.myapp.com"
|
|
249
|
+
git log --oneline -20 | instbyte send
|
|
250
|
+
instbyte watch --channel projects
|
|
251
|
+
instbyte status
|
|
252
|
+
```
|
|
241
253
|
|
|
242
|
-
|
|
254
|
+
See the [Terminal Usage Guide](docs/terminal-usage.md) for the full CLI reference, CI/CD setup, and raw curl fallback.
|
|
243
255
|
|
|
244
256
|
---
|
|
245
257
|
|
|
@@ -253,7 +265,7 @@ Instbyte follows [Semantic Versioning](https://semver.org). See [Releases](https
|
|
|
253
265
|
|
|
254
266
|
Instbyte is intentionally lightweight and LAN-first. If you want to extend it — CLI tools, themes, integrations — open an issue or submit a pull request.
|
|
255
267
|
|
|
256
|
-
The codebase has a full test suite (
|
|
268
|
+
The codebase has a full test suite (214 tests across unit and integration). Run `npm test` before submitting anything. Issues tagged **good first issue** are a good starting point.
|
|
257
269
|
|
|
258
270
|
---
|
|
259
271
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
|
|
7
|
+
const RUNTIME_FILE = path.join("instbyte-data", ".runtime.json");
|
|
8
|
+
|
|
9
|
+
// Walk up from dir looking for instbyte-data/.runtime.json.
|
|
10
|
+
// Stops at the user's home directory.
|
|
11
|
+
function findRuntimeConfig(dir) {
|
|
12
|
+
const candidate = path.join(dir, RUNTIME_FILE);
|
|
13
|
+
if (fs.existsSync(candidate)) {
|
|
14
|
+
try { return JSON.parse(fs.readFileSync(candidate, "utf-8")); }
|
|
15
|
+
catch { return null; }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const parent = path.dirname(dir);
|
|
19
|
+
if (parent === dir || dir === os.homedir()) return null; // reached root or home
|
|
20
|
+
|
|
21
|
+
return findRuntimeConfig(parent);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Returns { url, passphrase } for CLI commands.
|
|
25
|
+
// Priority: explicit flags → env vars → .runtime.json walk → localhost fallback
|
|
26
|
+
function resolveServer(flags = {}) {
|
|
27
|
+
if (flags.server) {
|
|
28
|
+
return {
|
|
29
|
+
url: flags.server.replace(/\/$/, ""),
|
|
30
|
+
passphrase: flags.passphrase || process.env.INSTBYTE_PASS || ""
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (process.env.INSTBYTE_URL) {
|
|
35
|
+
return {
|
|
36
|
+
url: process.env.INSTBYTE_URL.replace(/\/$/, ""),
|
|
37
|
+
passphrase: flags.passphrase || process.env.INSTBYTE_PASS || ""
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const runtime = findRuntimeConfig(process.cwd());
|
|
42
|
+
if (runtime && runtime.url) {
|
|
43
|
+
return {
|
|
44
|
+
url: runtime.url,
|
|
45
|
+
passphrase: flags.passphrase || runtime.passphrase || ""
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
url: "http://localhost:3000",
|
|
51
|
+
passphrase: flags.passphrase || ""
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { resolveServer, findRuntimeConfig };
|
package/bin/cli/send.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
|
|
7
|
+
const { resolveServer } = require("./resolve");
|
|
8
|
+
|
|
9
|
+
// argv after 'send'
|
|
10
|
+
const argv = process.argv.slice(3);
|
|
11
|
+
|
|
12
|
+
function parseArgs(argv) {
|
|
13
|
+
const flags = {};
|
|
14
|
+
let positional = null;
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < argv.length; i++) {
|
|
17
|
+
const arg = argv[i];
|
|
18
|
+
if (arg.startsWith("--")) {
|
|
19
|
+
const key = arg.slice(2);
|
|
20
|
+
const next = argv[i + 1];
|
|
21
|
+
if (next && !next.startsWith("--")) {
|
|
22
|
+
flags[key] = next;
|
|
23
|
+
i++;
|
|
24
|
+
} else {
|
|
25
|
+
flags[key] = true;
|
|
26
|
+
}
|
|
27
|
+
} else if (positional === null) {
|
|
28
|
+
positional = arg;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { flags, positional };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Null byte in the first 512 bytes → binary
|
|
36
|
+
function isBinary(buf) {
|
|
37
|
+
const limit = Math.min(buf.length, 512);
|
|
38
|
+
for (let i = 0; i < limit; i++) {
|
|
39
|
+
if (buf[i] === 0) return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// fs.readFileSync(0) reads fd 0 (stdin) synchronously — more portable than
|
|
45
|
+
// event-based stream reading, and works correctly in Git Bash / MINGW64.
|
|
46
|
+
function readStdin() {
|
|
47
|
+
return fs.readFileSync(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function fail(msg, hint) {
|
|
51
|
+
console.error(`✗ ${msg}`);
|
|
52
|
+
if (hint) console.error(` ${hint}`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function run() {
|
|
57
|
+
const { flags, positional } = parseArgs(argv);
|
|
58
|
+
|
|
59
|
+
if (flags.help) {
|
|
60
|
+
console.log("Usage: instbyte send [<file|text>] [--channel <name>] [--server <url>] [--passphrase <pass>] [--uploader <name>] [--quiet]");
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { url, passphrase } = resolveServer(flags);
|
|
65
|
+
const channel = flags.channel || "general";
|
|
66
|
+
const uploader = flags.uploader || os.userInfo().username;
|
|
67
|
+
const quiet = !!flags.quiet;
|
|
68
|
+
|
|
69
|
+
const authHeader = passphrase ? { "X-Passphrase": passphrase } : {};
|
|
70
|
+
|
|
71
|
+
// ── FILE ──────────────────────────────────────────────────────────────────
|
|
72
|
+
if (positional && fs.existsSync(positional)) {
|
|
73
|
+
const filePath = path.resolve(positional);
|
|
74
|
+
const filename = path.basename(filePath);
|
|
75
|
+
|
|
76
|
+
const form = new FormData();
|
|
77
|
+
form.append("file", new Blob([fs.readFileSync(filePath)]), filename);
|
|
78
|
+
form.append("channel", channel);
|
|
79
|
+
form.append("uploader", uploader);
|
|
80
|
+
|
|
81
|
+
let res;
|
|
82
|
+
try {
|
|
83
|
+
res = await fetch(`${url}/upload`, { method: "POST", headers: authHeader, body: form });
|
|
84
|
+
} catch {
|
|
85
|
+
fail(
|
|
86
|
+
"Connection failed — is the server running?",
|
|
87
|
+
`Start with: npx instbyte\nOr specify: instbyte send ${positional} --server http://192.168.x.x:3000`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (res.status === 401) fail("Unauthorized — wrong passphrase or auth required");
|
|
92
|
+
if (!res.ok) fail(`Server error: ${res.status}`);
|
|
93
|
+
|
|
94
|
+
if (!quiet) console.log(`✓ Sent to ${channel} — ${url}`);
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── TEXT (positional arg that is not a file path) ─────────────────────────
|
|
99
|
+
if (positional) {
|
|
100
|
+
let res;
|
|
101
|
+
try {
|
|
102
|
+
res = await fetch(`${url}/push`, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: { ...authHeader, "Content-Type": "text/plain", "X-Channel": channel, "X-Uploader": uploader },
|
|
105
|
+
body: positional
|
|
106
|
+
});
|
|
107
|
+
} catch {
|
|
108
|
+
fail(
|
|
109
|
+
"Connection failed — is the server running?",
|
|
110
|
+
`Start with: npx instbyte\nOr specify: instbyte send "${positional}" --server http://192.168.x.x:3000`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (res.status === 401) fail("Unauthorized — wrong passphrase or auth required");
|
|
115
|
+
if (!res.ok) fail(`Server error: ${res.status}`);
|
|
116
|
+
|
|
117
|
+
if (!quiet) console.log(`✓ Sent to ${channel} — ${url}`);
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── STDIN ─────────────────────────────────────────────────────────────────
|
|
122
|
+
if (!process.stdin.isTTY) {
|
|
123
|
+
let buf;
|
|
124
|
+
try { buf = readStdin(); }
|
|
125
|
+
catch { fail("Failed to read stdin"); }
|
|
126
|
+
|
|
127
|
+
if (isBinary(buf)) {
|
|
128
|
+
fail("Binary stdin detected — use `instbyte send ./file` to upload a file");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const content = buf.toString("utf-8");
|
|
132
|
+
if (!content.trim()) fail("Empty input — nothing to send");
|
|
133
|
+
|
|
134
|
+
let res;
|
|
135
|
+
try {
|
|
136
|
+
res = await fetch(`${url}/push`, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: { ...authHeader, "Content-Type": "text/plain", "X-Channel": channel, "X-Uploader": uploader },
|
|
139
|
+
body: content
|
|
140
|
+
});
|
|
141
|
+
} catch {
|
|
142
|
+
fail(
|
|
143
|
+
"Connection failed — is the server running?",
|
|
144
|
+
"Start with: npx instbyte\nOr specify: instbyte send --server http://192.168.x.x:3000"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (res.status === 401) fail("Unauthorized — wrong passphrase or auth required");
|
|
149
|
+
if (!res.ok) fail(`Server error: ${res.status}`);
|
|
150
|
+
|
|
151
|
+
if (!quiet) console.log(`✓ Sent to ${channel} — ${url}`);
|
|
152
|
+
process.exit(0);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── NO INPUT ──────────────────────────────────────────────────────────────
|
|
156
|
+
console.error("Usage: instbyte send <file|text>");
|
|
157
|
+
console.error(" cat file.log | instbyte send");
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
run();
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { resolveServer } = require("./resolve");
|
|
4
|
+
|
|
5
|
+
const argv = process.argv.slice(3);
|
|
6
|
+
|
|
7
|
+
function parseArgs(argv) {
|
|
8
|
+
const flags = {};
|
|
9
|
+
for (let i = 0; i < argv.length; i++) {
|
|
10
|
+
const arg = argv[i];
|
|
11
|
+
if (arg.startsWith("--")) {
|
|
12
|
+
const key = arg.slice(2);
|
|
13
|
+
const next = argv[i + 1];
|
|
14
|
+
if (next && !next.startsWith("--")) { flags[key] = next; i++; }
|
|
15
|
+
else flags[key] = true;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return flags;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function formatUptime(seconds) {
|
|
22
|
+
if (seconds < 60) return `${seconds}s`;
|
|
23
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
|
|
24
|
+
const h = Math.floor(seconds / 3600);
|
|
25
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
26
|
+
return m > 0 ? `${h}h ${m}m` : `${h}h`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function run() {
|
|
30
|
+
const flags = parseArgs(argv);
|
|
31
|
+
|
|
32
|
+
if (flags.help) {
|
|
33
|
+
console.log("Usage: instbyte status [--server <url>] [--passphrase <pass>]");
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { url, passphrase } = resolveServer(flags);
|
|
38
|
+
const headers = passphrase ? { "X-Passphrase": passphrase } : {};
|
|
39
|
+
|
|
40
|
+
let health, channels;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const [hRes, cRes] = await Promise.all([
|
|
44
|
+
fetch(`${url}/health`, { headers }),
|
|
45
|
+
fetch(`${url}/channels`, { headers })
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
if (!hRes.ok) throw new Error(`${hRes.status}`);
|
|
49
|
+
health = await hRes.json();
|
|
50
|
+
channels = await cRes.json();
|
|
51
|
+
} catch {
|
|
52
|
+
console.error(`✗ No Instbyte server found at ${url}`);
|
|
53
|
+
console.error(` Start one with: npx instbyte`);
|
|
54
|
+
console.error(` Or specify: instbyte status --server http://192.168.x.x:3000`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const channelNames = channels.map(c => c.name).join(", ");
|
|
59
|
+
|
|
60
|
+
console.log(`✓ Instbyte v${health.version} running at ${url}`);
|
|
61
|
+
console.log(` Uptime: ${formatUptime(health.uptime)}`);
|
|
62
|
+
console.log(` Connected: ${health.connected} user${health.connected !== 1 ? "s" : ""}`);
|
|
63
|
+
console.log(` Channels: ${channelNames}`);
|
|
64
|
+
if (health.hasAuth) console.log(` Auth: enabled`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
run();
|
package/bin/cli/watch.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { spawn } = require("child_process");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const { resolveServer } = require("./resolve");
|
|
6
|
+
|
|
7
|
+
const argv = process.argv.slice(3);
|
|
8
|
+
|
|
9
|
+
function parseArgs(argv) {
|
|
10
|
+
const flags = {};
|
|
11
|
+
for (let i = 0; i < argv.length; i++) {
|
|
12
|
+
const arg = argv[i];
|
|
13
|
+
if (arg.startsWith("--")) {
|
|
14
|
+
const key = arg.slice(2);
|
|
15
|
+
const next = argv[i + 1];
|
|
16
|
+
if (next && !next.startsWith("--")) { flags[key] = next; i++; }
|
|
17
|
+
else flags[key] = true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return flags;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Write text to the system clipboard.
|
|
24
|
+
// Falls back to printing the text if the clipboard tool is not found.
|
|
25
|
+
function copyToClipboard(text) {
|
|
26
|
+
let cmd, args;
|
|
27
|
+
if (process.platform === "darwin") {
|
|
28
|
+
cmd = "pbcopy"; args = [];
|
|
29
|
+
} else if (process.platform === "win32") {
|
|
30
|
+
cmd = "clip"; args = [];
|
|
31
|
+
} else {
|
|
32
|
+
cmd = "xclip"; args = ["-sel", "c"];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const proc = spawn(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
36
|
+
proc.on("error", () => process.stdout.write(text + "\n")); // fallback
|
|
37
|
+
proc.stdin.write(text, "utf-8");
|
|
38
|
+
proc.stdin.end();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function truncate(str, len) {
|
|
42
|
+
return str.length > len ? str.slice(0, len) + "…" : str;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function run() {
|
|
46
|
+
const flags = parseArgs(argv);
|
|
47
|
+
|
|
48
|
+
if (flags.help) {
|
|
49
|
+
console.log("Usage: instbyte watch [--channel <name>] [--server <url>] [--passphrase <pass>] [--output]");
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { url } = resolveServer(flags);
|
|
54
|
+
const channel = flags.channel || "general";
|
|
55
|
+
const outputMode = !!flags.output;
|
|
56
|
+
|
|
57
|
+
// In --output mode, status messages go to stderr so stdout stays clean for piping
|
|
58
|
+
const status = outputMode
|
|
59
|
+
? (msg) => process.stderr.write(msg + "\n")
|
|
60
|
+
: (msg) => process.stdout.write(msg + "\n");
|
|
61
|
+
|
|
62
|
+
const { io } = require("socket.io-client");
|
|
63
|
+
|
|
64
|
+
const socket = io(url, {
|
|
65
|
+
reconnection: true,
|
|
66
|
+
reconnectionDelay: 2000,
|
|
67
|
+
reconnectionDelayMax: 10000
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
let connected = false;
|
|
71
|
+
let connectErrShown = false;
|
|
72
|
+
let stopping = false;
|
|
73
|
+
|
|
74
|
+
socket.on("connect", () => {
|
|
75
|
+
socket.emit("join", os.userInfo().username);
|
|
76
|
+
status(connected
|
|
77
|
+
? "Reconnected."
|
|
78
|
+
: `Watching ${channel} on ${url}... (Ctrl+C to stop)`
|
|
79
|
+
);
|
|
80
|
+
connected = true;
|
|
81
|
+
connectErrShown = false;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
socket.on("disconnect", () => {
|
|
85
|
+
if (stopping) return;
|
|
86
|
+
connected = false;
|
|
87
|
+
status("Reconnecting...");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
socket.on("connect_error", () => {
|
|
91
|
+
if (!connectErrShown) {
|
|
92
|
+
status(`✗ Cannot connect to ${url}`);
|
|
93
|
+
status(` Start a server with: npx instbyte`);
|
|
94
|
+
connectErrShown = true;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
socket.on("new-item", (item) => {
|
|
99
|
+
if (item.channel !== channel) return;
|
|
100
|
+
|
|
101
|
+
if (item.type === "file") {
|
|
102
|
+
process.stdout.write(`📎 File: ${url}/uploads/${item.filename}\n`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (item.type === "text") {
|
|
107
|
+
if (outputMode) {
|
|
108
|
+
process.stdout.write(item.content + "\n");
|
|
109
|
+
} else {
|
|
110
|
+
copyToClipboard(item.content);
|
|
111
|
+
process.stdout.write(`✓ Copied: "${truncate(item.content, 60)}"\n`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
process.on("SIGINT", () => {
|
|
117
|
+
stopping = true;
|
|
118
|
+
socket.disconnect();
|
|
119
|
+
process.stdout.write("\nStopped watching.\n");
|
|
120
|
+
process.exit(0);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
run();
|
package/bin/instbyte.js
CHANGED
|
@@ -3,35 +3,90 @@
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
5
|
const path = require("path");
|
|
6
|
-
const fs
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const subcommand = args[0];
|
|
10
|
+
|
|
11
|
+
const SUBCOMMANDS = ["send", "watch", "status"];
|
|
7
12
|
|
|
8
13
|
// ========================
|
|
9
|
-
//
|
|
14
|
+
// HELP
|
|
10
15
|
// ========================
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
function showHelp() {
|
|
17
|
+
console.log(`
|
|
18
|
+
Usage:
|
|
19
|
+
instbyte Start the server
|
|
20
|
+
instbyte send <file|text> Send a file or text to a channel
|
|
21
|
+
instbyte send Read from stdin and send
|
|
22
|
+
instbyte watch Watch a channel, copy new text to clipboard
|
|
23
|
+
instbyte status Check if a server is running
|
|
24
|
+
|
|
25
|
+
Options (send / watch / status):
|
|
26
|
+
--server <url> Server URL (overrides auto-discovery)
|
|
27
|
+
--channel <name> Target channel (default: general)
|
|
28
|
+
--passphrase <pass> Passphrase if auth is enabled
|
|
29
|
+
--uploader <name> Display name for sent items (default: OS username)
|
|
30
|
+
--output watch: print to stdout instead of clipboard
|
|
31
|
+
--quiet send: suppress confirmation output
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
instbyte send ./build.zip --channel assets
|
|
35
|
+
instbyte send "http://staging.myapp.com"
|
|
36
|
+
git log --oneline -20 | instbyte send
|
|
37
|
+
instbyte watch --channel projects
|
|
38
|
+
instbyte watch --output | grep error
|
|
39
|
+
instbyte status --server http://192.168.1.10:3000
|
|
40
|
+
`);
|
|
21
41
|
}
|
|
22
42
|
|
|
23
|
-
|
|
24
|
-
|
|
43
|
+
// ========================
|
|
44
|
+
// ROUTE
|
|
45
|
+
// ========================
|
|
46
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
47
|
+
if (!SUBCOMMANDS.includes(subcommand)) {
|
|
48
|
+
showHelp();
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
25
51
|
}
|
|
26
52
|
|
|
27
|
-
|
|
28
|
-
|
|
53
|
+
if (SUBCOMMANDS.includes(subcommand)) {
|
|
54
|
+
// Each command module is loaded only when needed
|
|
55
|
+
const mod = path.join(__dirname, "cli", subcommand + ".js");
|
|
56
|
+
if (!fs.existsSync(mod)) {
|
|
57
|
+
console.error(`${subcommand}: not implemented yet`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
require(mod);
|
|
29
61
|
|
|
30
|
-
if (!
|
|
31
|
-
|
|
62
|
+
} else if (subcommand && !subcommand.startsWith("-")) {
|
|
63
|
+
console.error(`Unknown command: ${subcommand}`);
|
|
64
|
+
showHelp();
|
|
65
|
+
process.exit(1);
|
|
66
|
+
|
|
67
|
+
} else {
|
|
68
|
+
// No subcommand (or only flags) — start the server
|
|
69
|
+
startServer();
|
|
70
|
+
}
|
|
32
71
|
|
|
33
72
|
// ========================
|
|
34
|
-
//
|
|
73
|
+
// SERVER START
|
|
35
74
|
// ========================
|
|
36
|
-
|
|
37
|
-
|
|
75
|
+
function startServer() {
|
|
76
|
+
if (!process.env.INSTBYTE_DATA) {
|
|
77
|
+
process.env.INSTBYTE_DATA = path.join(process.cwd(), "instbyte-data");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!process.env.INSTBYTE_UPLOADS) {
|
|
81
|
+
process.env.INSTBYTE_UPLOADS = path.join(process.env.INSTBYTE_DATA, "uploads");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const dataDir = process.env.INSTBYTE_DATA;
|
|
85
|
+
const uploadsDir = process.env.INSTBYTE_UPLOADS;
|
|
86
|
+
|
|
87
|
+
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
|
88
|
+
if (!fs.existsSync(uploadsDir)) fs.mkdirSync(uploadsDir, { recursive: true });
|
|
89
|
+
|
|
90
|
+
process.env.INSTBYTE_BOOT = "1";
|
|
91
|
+
require("../server/server.js");
|
|
92
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "instbyte",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.1",
|
|
4
4
|
"description": "A self-hosted LAN sharing utility for fast, frictionless file, link, and snippet exchange across devices — no cloud required.",
|
|
5
5
|
"main": "bin/instbyte.js",
|
|
6
6
|
"bin": {
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"multer": "^2.0.2",
|
|
46
46
|
"sharp": "^0.33.2",
|
|
47
47
|
"socket.io": "^4.6.1",
|
|
48
|
+
"socket.io-client": "^4.6.1",
|
|
48
49
|
"sqlite3": "^5.1.6"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
package/server/server.js
CHANGED
|
@@ -855,7 +855,8 @@ app.get("/health", (req, res) => {
|
|
|
855
855
|
res.json({
|
|
856
856
|
status: "ok",
|
|
857
857
|
uptime: Math.floor(process.uptime()),
|
|
858
|
-
version: require("../package.json").version
|
|
858
|
+
version: require("../package.json").version,
|
|
859
|
+
connected: connectedUsers
|
|
859
860
|
});
|
|
860
861
|
});
|
|
861
862
|
|
|
@@ -1071,7 +1072,7 @@ function getLocalIP() {
|
|
|
1071
1072
|
const preferred =
|
|
1072
1073
|
candidates.find(c => c.address.startsWith("192.168.")) ||
|
|
1073
1074
|
candidates.find(c => c.address.startsWith("10.")) ||
|
|
1074
|
-
candidates.find(c => c.address.startsWith("172.
|
|
1075
|
+
candidates.find(c => c.address.startsWith("172.")) ||
|
|
1075
1076
|
candidates[0];
|
|
1076
1077
|
|
|
1077
1078
|
return preferred ? preferred.address : "localhost";
|
|
@@ -1092,6 +1093,23 @@ function listenOnFreePort(server, preferredPort) {
|
|
|
1092
1093
|
});
|
|
1093
1094
|
}
|
|
1094
1095
|
|
|
1096
|
+
function writeRuntimeConfig(ip, port) {
|
|
1097
|
+
const dataDir = process.env.INSTBYTE_DATA;
|
|
1098
|
+
if (!dataDir) return;
|
|
1099
|
+
|
|
1100
|
+
const runtimePath = path.join(dataDir, ".runtime.json");
|
|
1101
|
+
const payload = JSON.stringify({
|
|
1102
|
+
url: `http://${ip}:${port}`,
|
|
1103
|
+
passphrase: config.auth.passphrase
|
|
1104
|
+
}, null, 2);
|
|
1105
|
+
|
|
1106
|
+
try {
|
|
1107
|
+
fs.writeFileSync(runtimePath, payload, "utf-8");
|
|
1108
|
+
} catch (e) {
|
|
1109
|
+
console.warn("Warning: could not write CLI runtime config:", e.message);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1095
1113
|
const PREFERRED = parseInt(process.env.PORT) || config.server.port;
|
|
1096
1114
|
|
|
1097
1115
|
const localIP = getLocalIP();
|
|
@@ -1109,6 +1127,7 @@ if (process.env.INSTBYTE_BOOT === '1') {
|
|
|
1109
1127
|
}
|
|
1110
1128
|
|
|
1111
1129
|
console.log("");
|
|
1130
|
+
writeRuntimeConfig(localIP, PORT);
|
|
1112
1131
|
scanOrphans();
|
|
1113
1132
|
}).catch(err => {
|
|
1114
1133
|
console.error("Failed to start server:", err.message);
|