@decentnetwork/lan 0.1.98 → 0.1.100

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.
Binary file
Binary file
Binary file
Binary file
@@ -16,6 +16,8 @@ export interface PeerManagerOptions {
16
16
  /** Display name advertised to friends (this node's name, so friend lists show
17
17
  * "cn"/"tokyo"/"mac-dev" instead of the generic "@decentnetwork/peer"). */
18
18
  nickname?: string;
19
+ /** Status-message / short bio advertised to friends (Carrier USERINFO descr). */
20
+ statusMessage?: string;
19
21
  }
20
22
  export declare class PeerManager extends EventEmitter {
21
23
  private peer;
@@ -42,6 +44,19 @@ export declare class PeerManager extends EventEmitter {
42
44
  * removed, false if no such friend existed.
43
45
  */
44
46
  removeFriend(userid: string): boolean;
47
+ /**
48
+ * Update our own profile (display name + status-message description) at
49
+ * runtime and re-push it to friends over Carrier. The daemon also persists
50
+ * the name to config.yaml so it survives a restart.
51
+ */
52
+ setUserInfo(info: {
53
+ name?: string;
54
+ description?: string;
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;
45
60
  /**
46
61
  * Send an outbound friend request to a Carrier address (NOT a bare
47
62
  * userid — sendFriendRequest needs the address form because it
@@ -27,6 +27,7 @@ export class PeerManager extends EventEmitter {
27
27
  expressNodes: opts.expressNodes,
28
28
  expressControlPlaneOnly: opts.expressControlPlaneOnly,
29
29
  nickname: opts.nickname,
30
+ statusMessage: opts.statusMessage,
30
31
  // Register our IP channel (163) as the SDK's bulk-data stream so it
31
32
  // rides a single transport instead of fanning out over UDP + relay +
32
33
  // TCP relay (which delivered 3-4 duplicates of every IP packet).
@@ -93,6 +94,28 @@ export class PeerManager extends EventEmitter {
93
94
  this.logger.info(`Removed friend ${userid}`);
94
95
  return removed;
95
96
  }
97
+ /**
98
+ * Update our own profile (display name + status-message description) at
99
+ * runtime and re-push it to friends over Carrier. The daemon also persists
100
+ * the name to config.yaml so it survives a restart.
101
+ */
102
+ setUserInfo(info) {
103
+ if (!this.peer) {
104
+ throw new Error("Peer not created. Call create() first.");
105
+ }
106
+ this.peer.setUserInfo(info);
107
+ this.logger.info(`Profile updated (name="${info.name ?? "(unchanged)"}")`);
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
+ }
96
119
  /**
97
120
  * Send an outbound friend request to a Carrier address (NOT a bare
98
121
  * userid — sendFriendRequest needs the address form because it
@@ -464,6 +487,12 @@ export class PeerManager extends EventEmitter {
464
487
  }
465
488
  this.emit("message", message.pubkey, message.text);
466
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));
467
496
  // decentlan custom packets, all lossless so they traverse relay-only
468
497
  // sessions: IP data (163), session handshake (161), dora control (162).
469
498
  // Each on its own Carrier packet id — a chat message can never be mistaken
@@ -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.
@@ -263,6 +274,16 @@ export declare function cmdChatHistory(args: {
263
274
  peer?: string;
264
275
  configDir?: string;
265
276
  }): Promise<void>;
277
+ /**
278
+ * Launch the interactive terminal UI (TUI). The console is an ink (React-for-CLI)
279
+ * app built separately by esbuild into dist/console/console.js — the .tsx is
280
+ * excluded from the main tsc build, so we import the built bundle at runtime.
281
+ * The specifier is held in a variable so tsc treats it as a dynamic import and
282
+ * doesn't try to resolve the (non-existent) .ts source.
283
+ */
284
+ export declare function cmdConsole(args: {
285
+ configDir?: string;
286
+ }): Promise<void>;
266
287
  export declare function cmdUi(args: {
267
288
  port?: number;
268
289
  listen?: string;
@@ -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.
@@ -1108,6 +1125,18 @@ export async function cmdChatHistory(args) {
1108
1125
  }
1109
1126
  }
1110
1127
  }
1128
+ /**
1129
+ * Launch the interactive terminal UI (TUI). The console is an ink (React-for-CLI)
1130
+ * app built separately by esbuild into dist/console/console.js — the .tsx is
1131
+ * excluded from the main tsc build, so we import the built bundle at runtime.
1132
+ * The specifier is held in a variable so tsc treats it as a dynamic import and
1133
+ * doesn't try to resolve the (non-existent) .ts source.
1134
+ */
1135
+ export async function cmdConsole(args) {
1136
+ const spec = "../console/console.js";
1137
+ const mod = (await import(spec));
1138
+ await mod.runConsole({ configDir: args.configDir });
1139
+ }
1111
1140
  export async function cmdUi(args) {
1112
1141
  const dir = args.configDir || ConfigLoader.defaultConfigDir();
1113
1142
  const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
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, 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")
@@ -152,6 +152,20 @@ async function main() {
152
152
  .option("dora-dir", { type: "string", describe: "If this node runs a dora server, its data-dir (to show allocations)" })
153
153
  .option("config-dir", { type: "string" }), async (argv) => {
154
154
  await cmdUi({ port: argv.port, listen: argv.host ?? argv.listen, doraDir: argv["dora-dir"], configDir: argv["config-dir"] });
155
+ })
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
+ 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
+ }
155
169
  })
156
170
  // Tell the running daemon to re-exec itself with its original argv.
157
171
  // The daemon inherits its own uid (root if it was launched as root)