@decentnetwork/lan 0.1.99 → 0.1.101
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/bin/tun-helper-darwin-amd64 +0 -0
- package/bin/tun-helper-darwin-arm64 +0 -0
- package/bin/tun-helper-linux-amd64 +0 -0
- package/bin/tun-helper-linux-arm64 +0 -0
- package/dist/carrier/peer-manager.d.ts +4 -0
- package/dist/carrier/peer-manager.js +16 -0
- package/dist/cli/commands.d.ts +11 -0
- package/dist/cli/commands.js +17 -0
- package/dist/cli/index.js +12 -1
- package/dist/daemon/ipc.d.ts +4 -1
- package/dist/daemon/ipc.js +7 -0
- package/dist/daemon/server.js +32 -0
- package/package.json +2 -2
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -53,6 +53,10 @@ export declare class PeerManager extends EventEmitter {
|
|
|
53
53
|
name?: string;
|
|
54
54
|
description?: string;
|
|
55
55
|
}): void;
|
|
56
|
+
/** Offer a file to a friend (toxcore-standard transfer). Returns the fileId. */
|
|
57
|
+
sendFile(userid: string, data: Uint8Array, name: string): string | null;
|
|
58
|
+
/** Accept an incoming file offer. */
|
|
59
|
+
acceptFile(userid: string, fileNumber: number): void;
|
|
56
60
|
/**
|
|
57
61
|
* Send an outbound friend request to a Carrier address (NOT a bare
|
|
58
62
|
* userid — sendFriendRequest needs the address form because it
|
|
@@ -106,6 +106,16 @@ export class PeerManager extends EventEmitter {
|
|
|
106
106
|
this.peer.setUserInfo(info);
|
|
107
107
|
this.logger.info(`Profile updated (name="${info.name ?? "(unchanged)"}")`);
|
|
108
108
|
}
|
|
109
|
+
/** Offer a file to a friend (toxcore-standard transfer). Returns the fileId. */
|
|
110
|
+
sendFile(userid, data, name) {
|
|
111
|
+
if (!this.peer)
|
|
112
|
+
throw new Error("Peer not created. Call create() first.");
|
|
113
|
+
return this.peer.sendFile(userid, data, { name });
|
|
114
|
+
}
|
|
115
|
+
/** Accept an incoming file offer. */
|
|
116
|
+
acceptFile(userid, fileNumber) {
|
|
117
|
+
this.peer?.acceptFile(userid, fileNumber);
|
|
118
|
+
}
|
|
109
119
|
/**
|
|
110
120
|
* Send an outbound friend request to a Carrier address (NOT a bare
|
|
111
121
|
* userid — sendFriendRequest needs the address form because it
|
|
@@ -477,6 +487,12 @@ export class PeerManager extends EventEmitter {
|
|
|
477
487
|
}
|
|
478
488
|
this.emit("message", message.pubkey, message.text);
|
|
479
489
|
});
|
|
490
|
+
// Toxcore-standard file transfer (native-compatible). Forward offers,
|
|
491
|
+
// progress, and completions to the daemon, which auto-accepts and saves.
|
|
492
|
+
this.peer.onFile((o) => this.emit("file-offer", o));
|
|
493
|
+
this.peer.onFileProgress((p) => this.emit("file-progress", p));
|
|
494
|
+
this.peer.onFileComplete((p) => this.emit("file-complete", p));
|
|
495
|
+
this.peer.onFileCancel((p) => this.emit("file-cancel", p));
|
|
480
496
|
// decentlan custom packets, all lossless so they traverse relay-only
|
|
481
497
|
// sessions: IP data (163), session handshake (161), dora control (162).
|
|
482
498
|
// Each on its own Carrier packet id — a chat message can never be mistaken
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -254,6 +254,17 @@ export declare function cmdChatSend(args: {
|
|
|
254
254
|
text: string;
|
|
255
255
|
configDir?: string;
|
|
256
256
|
}): Promise<void>;
|
|
257
|
+
/**
|
|
258
|
+
* Send a file to a friend over toxcore-standard file transfer (direct, over the
|
|
259
|
+
* reliable net_crypto channel — no express, no size cap). The daemon reads the
|
|
260
|
+
* file by absolute path, so it must be readable by the daemon's user. The
|
|
261
|
+
* recipient's daemon auto-accepts and saves to its <configDir>/downloads/.
|
|
262
|
+
*/
|
|
263
|
+
export declare function cmdFileSend(args: {
|
|
264
|
+
to: string;
|
|
265
|
+
path: string;
|
|
266
|
+
configDir?: string;
|
|
267
|
+
}): Promise<void>;
|
|
257
268
|
/**
|
|
258
269
|
* Print chat history (sent + received). Incoming messages are logged by the
|
|
259
270
|
* daemon as they arrive, so this is also how you READ messages from friends.
|
package/dist/cli/commands.js
CHANGED
|
@@ -1083,6 +1083,23 @@ export async function cmdChatSend(args) {
|
|
|
1083
1083
|
throw new Error(`Daemon refused: ${res.error}`);
|
|
1084
1084
|
console.log(`-> ${args.to.slice(0, 16)}… ${args.text}`);
|
|
1085
1085
|
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Send a file to a friend over toxcore-standard file transfer (direct, over the
|
|
1088
|
+
* reliable net_crypto channel — no express, no size cap). The daemon reads the
|
|
1089
|
+
* file by absolute path, so it must be readable by the daemon's user. The
|
|
1090
|
+
* recipient's daemon auto-accepts and saves to its <configDir>/downloads/.
|
|
1091
|
+
*/
|
|
1092
|
+
export async function cmdFileSend(args) {
|
|
1093
|
+
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
1094
|
+
const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
|
|
1095
|
+
const abs = resolve(process.cwd(), args.path);
|
|
1096
|
+
const res = await ipcCall(config, { op: "file-send", userid: args.to, path: abs }, 30000);
|
|
1097
|
+
if (!res.ok)
|
|
1098
|
+
throw new Error(`File send failed: ${res.error}`);
|
|
1099
|
+
const d = (res.data ?? {});
|
|
1100
|
+
console.log(`Offered "${d.name}" (${d.size} bytes) to ${args.to.slice(0, 16)}… — fileId ${d.fileId}`);
|
|
1101
|
+
console.log("Streams once the recipient accepts; their daemon saves it to ~/.agentnet/downloads/.");
|
|
1102
|
+
}
|
|
1086
1103
|
/**
|
|
1087
1104
|
* Print chat history (sent + received). Incoming messages are logged by the
|
|
1088
1105
|
* daemon as they arrive, so this is also how you READ messages from friends.
|
package/dist/cli/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { hideBin } from "yargs/helpers";
|
|
|
10
10
|
// Belt-and-braces — also raise it here in case the CLI is run directly
|
|
11
11
|
// (e.g. `node dist/cli/index.js` rather than via dist/index.js).
|
|
12
12
|
EventEmitter.defaultMaxListeners = 100;
|
|
13
|
-
import { cmdInit, cmdIdentityShow, cmdPeersList, cmdIpamAssign, cmdGrant, cmdRevoke, cmdResolve, cmdStatus, cmdUp, cmdAuditLog, cmdFriendRequest, cmdFriendAccept, cmdFriendsList, cmdFriendsPending, cmdFriendsAccept, cmdFriendsReject, cmdProxyEnable, cmdProxyDisable, cmdProxyStatus, cmdProxyAllowHost, cmdProxyRevokeHost, cmdProxyListHosts, cmdProxyUse, cmdProxyRouter, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDoraAutofriend, cmdDiag, cmdDoctor, cmdDnsInstall, cmdDnsHosts, cmdServiceInstall, cmdRestart, cmdServiceStatus, cmdServiceRestart, cmdUi, cmdConsole, cmdChatSend, cmdChatHistory, cmdFriendRemove, cmdFriendAlias, } from "./commands.js";
|
|
13
|
+
import { cmdInit, cmdIdentityShow, cmdPeersList, cmdIpamAssign, cmdGrant, cmdRevoke, cmdResolve, cmdStatus, cmdUp, cmdAuditLog, cmdFriendRequest, cmdFriendAccept, cmdFriendsList, cmdFriendsPending, cmdFriendsAccept, cmdFriendsReject, cmdProxyEnable, cmdProxyDisable, cmdProxyStatus, cmdProxyAllowHost, cmdProxyRevokeHost, cmdProxyListHosts, cmdProxyUse, cmdProxyRouter, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDoraAutofriend, cmdDiag, cmdDoctor, cmdDnsInstall, cmdDnsHosts, cmdServiceInstall, cmdRestart, cmdServiceStatus, cmdServiceRestart, cmdUi, cmdConsole, cmdFileSend, cmdChatSend, cmdChatHistory, cmdFriendRemove, cmdFriendAlias, } from "./commands.js";
|
|
14
14
|
async function main() {
|
|
15
15
|
await yargs(hideBin(process.argv))
|
|
16
16
|
.scriptName("agentnet")
|
|
@@ -155,6 +155,17 @@ async function main() {
|
|
|
155
155
|
})
|
|
156
156
|
.command("console", "Interactive terminal UI (TUI) — friends list + chat, keyboard-driven (q to quit)", (y) => y.option("config-dir", { type: "string" }), async (argv) => {
|
|
157
157
|
await cmdConsole({ configDir: argv["config-dir"] });
|
|
158
|
+
})
|
|
159
|
+
.command("file <action> <userid> [path]", "Send a file to a friend (toxcore-standard, direct; recipient auto-saves to ~/.agentnet/downloads)", (y) => y
|
|
160
|
+
.positional("action", { type: "string", choices: ["send"], demandOption: true })
|
|
161
|
+
.positional("userid", { type: "string", demandOption: true, describe: "Friend's userid" })
|
|
162
|
+
.positional("path", { type: "string", describe: "Path to the file to send" })
|
|
163
|
+
.option("config-dir", { type: "string" }), async (argv) => {
|
|
164
|
+
if (argv.action === "send") {
|
|
165
|
+
if (!argv.path)
|
|
166
|
+
throw new Error("path is required: agentnet file send <userid> <path>");
|
|
167
|
+
await cmdFileSend({ to: argv.userid, path: argv.path, configDir: argv["config-dir"] });
|
|
168
|
+
}
|
|
158
169
|
})
|
|
159
170
|
// Tell the running daemon to re-exec itself with its original argv.
|
|
160
171
|
// The daemon inherits its own uid (root if it was launched as root)
|
package/dist/daemon/ipc.d.ts
CHANGED
|
@@ -56,6 +56,8 @@ export interface IpcHandlers {
|
|
|
56
56
|
name?: string;
|
|
57
57
|
description?: string;
|
|
58
58
|
}) => Promise<void>;
|
|
59
|
+
/** Offer a local file (by path) to a friend over toxcore file transfer. */
|
|
60
|
+
fileSend: (userid: string, path: string) => Promise<Record<string, unknown>>;
|
|
59
61
|
/** Mark a conversation read up to `ts` (defaults to now) — clears unread. */
|
|
60
62
|
chatMarkRead: (userid: string, ts?: number) => Promise<void>;
|
|
61
63
|
/** Re-read proxy allowlist from config and apply it to the running
|
|
@@ -74,7 +76,7 @@ export interface IpcHandlers {
|
|
|
74
76
|
selfRestart: () => Promise<Record<string, unknown>>;
|
|
75
77
|
}
|
|
76
78
|
export interface IpcRequest {
|
|
77
|
-
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "chat-send" | "chat-history" | "friends-list" | "friend-remove" | "friend-set-alias" | "set-profile" | "chat-mark-read" | "proxy-reload" | "self-restart";
|
|
79
|
+
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "chat-send" | "chat-history" | "friends-list" | "friend-remove" | "friend-set-alias" | "set-profile" | "file-send" | "chat-mark-read" | "proxy-reload" | "self-restart";
|
|
78
80
|
address?: string;
|
|
79
81
|
hello?: string;
|
|
80
82
|
userid?: string;
|
|
@@ -82,6 +84,7 @@ export interface IpcRequest {
|
|
|
82
84
|
alias?: string;
|
|
83
85
|
name?: string;
|
|
84
86
|
description?: string;
|
|
87
|
+
path?: string;
|
|
85
88
|
before?: number;
|
|
86
89
|
limit?: number;
|
|
87
90
|
ts?: number;
|
package/dist/daemon/ipc.js
CHANGED
|
@@ -170,6 +170,13 @@ export class IpcServer {
|
|
|
170
170
|
await this.handlers.setProfile({ name: req.name, description: req.description });
|
|
171
171
|
return;
|
|
172
172
|
}
|
|
173
|
+
case "file-send": {
|
|
174
|
+
if (!req.userid)
|
|
175
|
+
throw new Error("userid is required");
|
|
176
|
+
if (!req.path)
|
|
177
|
+
throw new Error("path is required");
|
|
178
|
+
return await this.handlers.fileSend(req.userid, req.path);
|
|
179
|
+
}
|
|
173
180
|
case "chat-mark-read": {
|
|
174
181
|
if (!req.userid)
|
|
175
182
|
throw new Error("userid is required");
|
package/dist/daemon/server.js
CHANGED
|
@@ -295,6 +295,17 @@ export class DaemonServer {
|
|
|
295
295
|
this.logger.warn(`Failed to persist profile: ${e.message}`);
|
|
296
296
|
});
|
|
297
297
|
},
|
|
298
|
+
fileSend: async (userid, path) => {
|
|
299
|
+
const fs = await import("fs/promises");
|
|
300
|
+
const { basename } = await import("path");
|
|
301
|
+
const data = await fs.readFile(path);
|
|
302
|
+
const name = basename(path);
|
|
303
|
+
const fileId = this.peerManager?.sendFile(userid, new Uint8Array(data), name);
|
|
304
|
+
if (!fileId)
|
|
305
|
+
throw new Error("No free transfer slot (or friend unknown)");
|
|
306
|
+
this.logger.info(`Offering file "${name}" (${data.length}B) to ${userid.slice(0, 8)}`);
|
|
307
|
+
return { fileId, name, size: data.length };
|
|
308
|
+
},
|
|
298
309
|
chatMarkRead: async (userid, ts) => {
|
|
299
310
|
this.friendMeta?.markRead(userid, ts);
|
|
300
311
|
},
|
|
@@ -544,6 +555,27 @@ export class DaemonServer {
|
|
|
544
555
|
this.peerManager.on("message", (pubkey, text) => {
|
|
545
556
|
this.logChat(pubkey, "in", text);
|
|
546
557
|
});
|
|
558
|
+
// File transfer (toxcore-standard): auto-accept offers from friends and
|
|
559
|
+
// save completed files under <configDir>/downloads/.
|
|
560
|
+
this.peerManager.on("file-offer", (o) => {
|
|
561
|
+
this.logger.info(`Incoming file "${o.name}" (${o.size}B) from ${o.friendId.slice(0, 8)} — accepting`);
|
|
562
|
+
this.peerManager?.acceptFile(o.friendId, o.fileNumber);
|
|
563
|
+
});
|
|
564
|
+
this.peerManager.on("file-complete", (p) => {
|
|
565
|
+
if (p.sending || !p.data) {
|
|
566
|
+
if (p.sending)
|
|
567
|
+
this.logger.info(`File "${p.name}" sent to ${p.friendId.slice(0, 8)}`);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
void (async () => {
|
|
571
|
+
const dir = resolve(this.configDir, "downloads");
|
|
572
|
+
const fs = await import("fs/promises");
|
|
573
|
+
await fs.mkdir(dir, { recursive: true });
|
|
574
|
+
const safe = (p.name || "file").replace(/[/\\]/g, "_");
|
|
575
|
+
await fs.writeFile(resolve(dir, safe), Buffer.from(p.data));
|
|
576
|
+
this.logger.info(`Saved file "${p.name}" (${p.size}B) from ${p.friendId.slice(0, 8)} → ${resolve(dir, safe)}`);
|
|
577
|
+
})().catch((e) => this.logger.warn(`Failed to save file: ${e.message}`));
|
|
578
|
+
});
|
|
547
579
|
this.peerManager.on("friend-request", (req) => {
|
|
548
580
|
void pubkeyHexToUserid(req.pubkey).then((userid) => {
|
|
549
581
|
const who = `${req.name || "(unnamed)"} ${userid}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.101",
|
|
4
4
|
"description": "Private virtual LAN for self-hosted services and AI agents, built on Elastos Carrier. NAT-traversal, name service, ACL, all over a peer-to-peer mesh — no public IP required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
81
|
"@decentnetwork/dora": "^0.1.6",
|
|
82
|
-
"@decentnetwork/peer": "^0.1.
|
|
82
|
+
"@decentnetwork/peer": "^0.1.44",
|
|
83
83
|
"ink": "^5.2.1",
|
|
84
84
|
"js-yaml": "^4.1.0",
|
|
85
85
|
"react": "^18.3.1",
|