@decentnetwork/lan 0.1.273 → 0.1.275
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/commands.d.ts +19 -0
- package/dist/cli/commands.js +136 -1
- package/dist/cli/index.js +5 -2
- package/dist/config/loader.js +7 -5
- package/dist/console/console.js +7 -5
- package/dist/daemon/server.js +5 -2
- package/dist/ui/desktop/app.js +1 -1
- package/package.json +1 -1
package/dist/cli/commands.d.ts
CHANGED
|
@@ -70,6 +70,7 @@ export declare function cmdUp(args: {
|
|
|
70
70
|
configDir?: string;
|
|
71
71
|
realTun?: boolean;
|
|
72
72
|
}): Promise<void>;
|
|
73
|
+
export declare function resolveBeaglesName(input: string): Promise<string | null>;
|
|
73
74
|
/**
|
|
74
75
|
* Send a friend request to another peer's address.
|
|
75
76
|
* Run while daemon is DOWN — opens a temporary peer, sends request, exits.
|
|
@@ -486,6 +487,24 @@ export declare function windowsServiceLauncherScript(args: {
|
|
|
486
487
|
configDir: string;
|
|
487
488
|
logPath: string;
|
|
488
489
|
}): string;
|
|
490
|
+
/**
|
|
491
|
+
* Key-only SSH for connections arriving over the AgentNet subnet.
|
|
492
|
+
*
|
|
493
|
+
* Runs automatically whenever the user turns on the TUN / virtual LAN
|
|
494
|
+
* (`agentnet up --real-tun`, `agentnet service install`) — a daemon friend
|
|
495
|
+
* gets an L3 route to this machine, and password-guessable SSH over that
|
|
496
|
+
* route is the #1 exposure (docs/SECURITY-L3-HARDENING.md).
|
|
497
|
+
*
|
|
498
|
+
* STRICTLY SCOPED (product rule 2026-08-06): appends a
|
|
499
|
+
* `Match Address 10.86.0.0/16` block to the END of sshd_config — the
|
|
500
|
+
* user's existing physical-LAN/WAN login policy is untouched, and we
|
|
501
|
+
* never rewrite anything they configured. Idempotent via a marker line;
|
|
502
|
+
* original config backed up once at sshd_config.bak.agentnet; validated
|
|
503
|
+
* with `sshd -t` and rolled back if the daemon rejects it.
|
|
504
|
+
*/
|
|
505
|
+
export declare function cmdHardenSsh(args?: {
|
|
506
|
+
quiet?: boolean;
|
|
507
|
+
}): Promise<void>;
|
|
489
508
|
export declare function cmdServiceInstall(args: {
|
|
490
509
|
uninstall?: boolean;
|
|
491
510
|
configDir?: string;
|
package/dist/cli/commands.js
CHANGED
|
@@ -449,6 +449,17 @@ export async function cmdUp(args) {
|
|
|
449
449
|
// full disk stops the daemon outright. Covers the SDK's [peer-debug] output
|
|
450
450
|
// too, since this wraps the process console rather than our Logger.
|
|
451
451
|
installLogThrottle();
|
|
452
|
+
// Turning on the real TUN = giving friends an L3 route here. Close the
|
|
453
|
+
// password-SSH door on that route first (scoped, idempotent, no-op when
|
|
454
|
+
// already done or when there is no sshd).
|
|
455
|
+
if (args.realTun) {
|
|
456
|
+
try {
|
|
457
|
+
await cmdHardenSsh({ quiet: true });
|
|
458
|
+
}
|
|
459
|
+
catch (err) {
|
|
460
|
+
console.warn(String(err.message ?? err));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
452
463
|
const daemon = new DaemonServer({
|
|
453
464
|
config,
|
|
454
465
|
configDir: dir,
|
|
@@ -469,6 +480,42 @@ export async function cmdUp(args) {
|
|
|
469
480
|
// Keep alive
|
|
470
481
|
await new Promise(() => { });
|
|
471
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* Resolve a *.beagles.eth name to its Carrier address via the ens-gateway
|
|
485
|
+
* worker (the registry the Beagle mobile apps register into). Only names
|
|
486
|
+
* from OUR gateway resolve — this is not a general ENS resolver.
|
|
487
|
+
*
|
|
488
|
+
* Returns null when the input doesn't look like a name (so callers can
|
|
489
|
+
* fall through to treating it as a raw Carrier address). Throws when it
|
|
490
|
+
* IS a name but the gateway doesn't know it.
|
|
491
|
+
*/
|
|
492
|
+
const ENS_GATEWAY = "https://ens-gateway.beaglechat.workers.dev";
|
|
493
|
+
export async function resolveBeaglesName(input) {
|
|
494
|
+
const raw = input.trim();
|
|
495
|
+
// A Carrier address is 52 base58 chars, a userid 44 — both dot-free.
|
|
496
|
+
// Anything with a dot, a space, or too short to be either is a name.
|
|
497
|
+
const looksLikeName = raw.includes(".") || raw.includes(" ") || raw.length < 40;
|
|
498
|
+
if (!looksLikeName)
|
|
499
|
+
return null;
|
|
500
|
+
const full = raw.includes(".") ? raw : `${raw}.beagles.eth`;
|
|
501
|
+
// Registered labels aren't strictly normalized upstream (mixed case,
|
|
502
|
+
// stray spaces exist in the registry) — compare loosely.
|
|
503
|
+
const norm = (s) => s.toLowerCase().replace(/\s+/g, "");
|
|
504
|
+
const want = norm(full);
|
|
505
|
+
const res = await fetch(`${ENS_GATEWAY}/names`);
|
|
506
|
+
if (!res.ok)
|
|
507
|
+
throw new Error(`name lookup failed: gateway HTTP ${res.status}`);
|
|
508
|
+
const names = (await res.json());
|
|
509
|
+
for (const [key, val] of Object.entries(names)) {
|
|
510
|
+
if (norm(key) !== want)
|
|
511
|
+
continue;
|
|
512
|
+
const addr = val?.texts?.carrierAddress;
|
|
513
|
+
if (!addr)
|
|
514
|
+
throw new Error(`'${key}' is registered but has no Carrier address bound`);
|
|
515
|
+
return addr;
|
|
516
|
+
}
|
|
517
|
+
throw new Error(`name '${full}' is not registered on the beagles.eth gateway`);
|
|
518
|
+
}
|
|
472
519
|
/**
|
|
473
520
|
* Send a friend request to another peer's address.
|
|
474
521
|
* Run while daemon is DOWN — opens a temporary peer, sends request, exits.
|
|
@@ -478,6 +525,12 @@ export async function cmdUp(args) {
|
|
|
478
525
|
export async function cmdFriendRequest(args) {
|
|
479
526
|
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
480
527
|
const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
|
|
528
|
+
// Accept a *.beagles.eth name in place of a raw Carrier address.
|
|
529
|
+
const resolved = await resolveBeaglesName(args.address);
|
|
530
|
+
if (resolved !== null) {
|
|
531
|
+
console.log(`Resolved '${args.address.trim()}' → ${resolved}`);
|
|
532
|
+
args = { ...args, address: resolved };
|
|
533
|
+
}
|
|
481
534
|
// If the daemon is running, route through IPC instead of spawning a
|
|
482
535
|
// second Carrier peer. The daemon's existing Peer instance sends the
|
|
483
536
|
// request — no session conflict, no second DHT announce, no race.
|
|
@@ -867,7 +920,7 @@ export async function cmdFriendsAutoAccept(args) {
|
|
|
867
920
|
const configPath = resolve(dir, "config.yaml");
|
|
868
921
|
const config = await ConfigLoader.load(configPath);
|
|
869
922
|
if (!args.mode) {
|
|
870
|
-
const cur = config.friends?.autoAccept ??
|
|
923
|
+
const cur = config.friends?.autoAccept ?? false;
|
|
871
924
|
console.log(`friends.autoAccept: ${cur ? "on" : "off"}`);
|
|
872
925
|
console.log(cur
|
|
873
926
|
? " Incoming friend-requests are accepted automatically."
|
|
@@ -2195,6 +2248,76 @@ while ($true) {
|
|
|
2195
2248
|
}
|
|
2196
2249
|
`;
|
|
2197
2250
|
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Key-only SSH for connections arriving over the AgentNet subnet.
|
|
2253
|
+
*
|
|
2254
|
+
* Runs automatically whenever the user turns on the TUN / virtual LAN
|
|
2255
|
+
* (`agentnet up --real-tun`, `agentnet service install`) — a daemon friend
|
|
2256
|
+
* gets an L3 route to this machine, and password-guessable SSH over that
|
|
2257
|
+
* route is the #1 exposure (docs/SECURITY-L3-HARDENING.md).
|
|
2258
|
+
*
|
|
2259
|
+
* STRICTLY SCOPED (product rule 2026-08-06): appends a
|
|
2260
|
+
* `Match Address 10.86.0.0/16` block to the END of sshd_config — the
|
|
2261
|
+
* user's existing physical-LAN/WAN login policy is untouched, and we
|
|
2262
|
+
* never rewrite anything they configured. Idempotent via a marker line;
|
|
2263
|
+
* original config backed up once at sshd_config.bak.agentnet; validated
|
|
2264
|
+
* with `sshd -t` and rolled back if the daemon rejects it.
|
|
2265
|
+
*/
|
|
2266
|
+
export async function cmdHardenSsh(args = {}) {
|
|
2267
|
+
const MARK = "agentnet L3 hardening";
|
|
2268
|
+
const say = (m) => { if (!args.quiet)
|
|
2269
|
+
console.log(m); };
|
|
2270
|
+
if (process.platform === "win32") {
|
|
2271
|
+
say("[harden-ssh] Windows: append to C:\\ProgramData\\ssh\\sshd_config:\n" +
|
|
2272
|
+
" Match Address 10.86.0.0/16\n PasswordAuthentication no\n" +
|
|
2273
|
+
"then Restart-Service sshd (as Administrator).");
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
const cfg = "/etc/ssh/sshd_config";
|
|
2277
|
+
if (!existsSync(cfg)) {
|
|
2278
|
+
say("[harden-ssh] no /etc/ssh/sshd_config (no OpenSSH server) — nothing to do");
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
if (readFileSync(cfg, "utf-8").includes(MARK)) {
|
|
2282
|
+
say("[harden-ssh] already hardened — nothing to do");
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
if (typeof process.getuid === "function" && process.getuid() !== 0) {
|
|
2286
|
+
// Not fatal: the caller may be a non-root `up` on a mock TUN box.
|
|
2287
|
+
console.log("[harden-ssh] needs root — run: sudo agentnet harden-ssh");
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
const { execSync } = await import("child_process");
|
|
2291
|
+
const { copyFileSync, appendFileSync } = await import("fs");
|
|
2292
|
+
const bak = `${cfg}.bak.agentnet`;
|
|
2293
|
+
if (!existsSync(bak))
|
|
2294
|
+
copyFileSync(cfg, bak);
|
|
2295
|
+
appendFileSync(cfg, `\n# ${MARK}: key-only SSH from the virtual net (10.86/16 only)\n` +
|
|
2296
|
+
`Match Address 10.86.0.0/16\n PasswordAuthentication no\n KbdInteractiveAuthentication no\n`);
|
|
2297
|
+
try {
|
|
2298
|
+
execSync("/usr/sbin/sshd -t", { stdio: "pipe" });
|
|
2299
|
+
}
|
|
2300
|
+
catch (err) {
|
|
2301
|
+
copyFileSync(bak, cfg);
|
|
2302
|
+
throw new Error(`[harden-ssh] sshd rejected the change — restored original config: ${err.message}`);
|
|
2303
|
+
}
|
|
2304
|
+
if (process.platform === "darwin") {
|
|
2305
|
+
try {
|
|
2306
|
+
execSync("launchctl kickstart -k system/com.openssh.sshd", { stdio: "pipe" });
|
|
2307
|
+
}
|
|
2308
|
+
catch { /* socket-activated: new connections read the config anyway */ }
|
|
2309
|
+
}
|
|
2310
|
+
else {
|
|
2311
|
+
for (const c of ["systemctl reload ssh", "systemctl reload sshd", "service ssh reload"]) {
|
|
2312
|
+
try {
|
|
2313
|
+
execSync(c, { stdio: "pipe" });
|
|
2314
|
+
break;
|
|
2315
|
+
}
|
|
2316
|
+
catch { /* try the next spelling */ }
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
console.log(`[harden-ssh] SSH from 10.86.0.0/16 is now key-only. Physical-network logins unchanged; backup: ${bak}`);
|
|
2320
|
+
}
|
|
2198
2321
|
export async function cmdServiceInstall(args) {
|
|
2199
2322
|
// Detect sudo's $HOME=/root trap. When `service install` is run via
|
|
2200
2323
|
// sudo and no --config-dir is provided, derive the home dir from
|
|
@@ -2337,6 +2460,12 @@ WantedBy=multi-user.target
|
|
|
2337
2460
|
}
|
|
2338
2461
|
execSync("systemctl daemon-reload");
|
|
2339
2462
|
execSync("systemctl enable --now agentnet");
|
|
2463
|
+
try {
|
|
2464
|
+
await cmdHardenSsh({ quiet: true });
|
|
2465
|
+
}
|
|
2466
|
+
catch (err) {
|
|
2467
|
+
console.warn(String(err.message ?? err));
|
|
2468
|
+
}
|
|
2340
2469
|
console.log(`Installed ${unitPath} and started agentnet.service.`);
|
|
2341
2470
|
console.log(`Logs: journalctl -u agentnet -f`);
|
|
2342
2471
|
console.log(`Optional — watch China video (CCTV etc.): agentnet proxy trust-ca && agentnet proxy router --hls-accel (docs/CCTV-VIEWING.md)`);
|
|
@@ -2414,6 +2543,12 @@ WantedBy=multi-user.target
|
|
|
2414
2543
|
console.log(`Note: could not install the newsyslog rotation rule (${err instanceof Error ? err.message : err}). The log is still throttled in-process.`);
|
|
2415
2544
|
}
|
|
2416
2545
|
execSync(`launchctl load ${plistPath}`);
|
|
2546
|
+
try {
|
|
2547
|
+
await cmdHardenSsh({ quiet: true });
|
|
2548
|
+
}
|
|
2549
|
+
catch (err) {
|
|
2550
|
+
console.warn(String(err.message ?? err));
|
|
2551
|
+
}
|
|
2417
2552
|
console.log(`Installed ${plistPath} and started com.decentlan.agentnet.`);
|
|
2418
2553
|
console.log(`Optional — watch China video (CCTV etc.): agentnet proxy trust-ca && agentnet proxy router --hls-accel (docs/CCTV-VIEWING.md)`);
|
|
2419
2554
|
console.log(`Logs: tail -f /var/log/agentnet.log`);
|
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, cmdFriendsAutoAccept, cmdFriendsReject, cmdProxyEnable, cmdProxyDisable, cmdProxyStatus, cmdProxyWho, cmdProxyAccess, cmdProxyAllowHost, cmdProxyRevokeHost, cmdProxyListHosts, cmdProxyWhitelist, cmdProxyFileWhitelist, cmdProxyBusyMbps, cmdProxyUse, cmdProxyRouter, cmdProxyTrustCa, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDoraAutofriend, cmdBootstrapShow, cmdBootstrapUpdate, cmdDiag, cmdDoctor, cmdDnsInstall, cmdDnsHosts, cmdServiceInstall, cmdRestart, cmdServiceStatus, cmdServiceRestart, cmdUi, cmdConsole, cmdFileSend, cmdChatSend, cmdChatHistory, cmdFriendRemove, cmdFriendAlias, } from "./commands.js";
|
|
13
|
+
import { cmdInit, cmdIdentityShow, cmdPeersList, cmdIpamAssign, cmdGrant, cmdRevoke, cmdResolve, cmdStatus, cmdUp, cmdAuditLog, cmdFriendRequest, cmdFriendAccept, cmdFriendsList, cmdFriendsPending, cmdFriendsAccept, cmdFriendsAutoAccept, cmdFriendsReject, cmdProxyEnable, cmdProxyDisable, cmdProxyStatus, cmdProxyWho, cmdProxyAccess, cmdProxyAllowHost, cmdProxyRevokeHost, cmdProxyListHosts, cmdProxyWhitelist, cmdProxyFileWhitelist, cmdProxyBusyMbps, cmdProxyUse, cmdProxyRouter, cmdProxyTrustCa, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDoraAutofriend, cmdBootstrapShow, cmdBootstrapUpdate, cmdDiag, cmdDoctor, cmdDnsInstall, cmdDnsHosts, cmdHardenSsh, 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")
|
|
@@ -149,6 +149,9 @@ async function main() {
|
|
|
149
149
|
})
|
|
150
150
|
.demandCommand(1, "Specify a dns subcommand (run 'agentnet dns --help')"), () => {
|
|
151
151
|
// parent handler — never invoked because demandCommand
|
|
152
|
+
})
|
|
153
|
+
.command("harden-ssh", "Make SSH key-only for connections from the AgentNet subnet (10.86/16 only; physical-network logins unchanged)", (y) => y, async () => {
|
|
154
|
+
await cmdHardenSsh();
|
|
152
155
|
})
|
|
153
156
|
// Persistent system service — wraps systemctl (Linux), launchctl
|
|
154
157
|
// (macOS), or a highest-privilege startup task (Windows) so a new operator runs ONE command to install
|
|
@@ -224,7 +227,7 @@ async function main() {
|
|
|
224
227
|
});
|
|
225
228
|
})
|
|
226
229
|
.command("friend-request <address>", "Send a friend request (routes through the running daemon when up; opens a standalone peer when down)", (y) => y
|
|
227
|
-
.positional("address", { type: "string", demandOption: true, describe: "Recipient Carrier address" })
|
|
230
|
+
.positional("address", { type: "string", demandOption: true, describe: "Recipient Carrier address, or a *.beagles.eth name (bare label ok, e.g. 's3ns')" })
|
|
228
231
|
.option("hello", { type: "string", describe: "Greeting message" })
|
|
229
232
|
.option("wait-ms", { type: "number", default: 8000, describe: "Wait time for relay delivery" })
|
|
230
233
|
.option("config-dir", { type: "string" }), async (argv) => {
|
package/dist/config/loader.js
CHANGED
|
@@ -301,12 +301,14 @@ export class ConfigLoader {
|
|
|
301
301
|
enabled: false,
|
|
302
302
|
port: 8888,
|
|
303
303
|
},
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
304
|
+
// Manual friend approval by default (2026-08-06 security decision).
|
|
305
|
+
// A daemon friend gets an L3 route to this machine, and addresses
|
|
306
|
+
// are now publicly listed (Beagle discover/names directories) — so
|
|
307
|
+
// auto-accept + TUN would let strangers onto your virtual LAN.
|
|
308
|
+
// Infra nodes (doras/exits) that must accept strangers set
|
|
309
|
+
// `friends.autoAccept: true` explicitly.
|
|
308
310
|
friends: {
|
|
309
|
-
autoAccept:
|
|
311
|
+
autoAccept: false,
|
|
310
312
|
},
|
|
311
313
|
// Dora integration is ON by default and points at the public
|
|
312
314
|
// canonical dora — `agentnet init` follows up with a one-time
|
package/dist/console/console.js
CHANGED
|
@@ -883,12 +883,14 @@ var ConfigLoader = class {
|
|
|
883
883
|
enabled: false,
|
|
884
884
|
port: 8888
|
|
885
885
|
},
|
|
886
|
-
//
|
|
887
|
-
//
|
|
888
|
-
//
|
|
889
|
-
//
|
|
886
|
+
// Manual friend approval by default (2026-08-06 security decision).
|
|
887
|
+
// A daemon friend gets an L3 route to this machine, and addresses
|
|
888
|
+
// are now publicly listed (Beagle discover/names directories) — so
|
|
889
|
+
// auto-accept + TUN would let strangers onto your virtual LAN.
|
|
890
|
+
// Infra nodes (doras/exits) that must accept strangers set
|
|
891
|
+
// `friends.autoAccept: true` explicitly.
|
|
890
892
|
friends: {
|
|
891
|
-
autoAccept:
|
|
893
|
+
autoAccept: false
|
|
892
894
|
},
|
|
893
895
|
// Dora integration is ON by default and points at the public
|
|
894
896
|
// canonical dora — `agentnet init` follows up with a one-time
|
package/dist/daemon/server.js
CHANGED
|
@@ -919,7 +919,7 @@ export class DaemonServer {
|
|
|
919
919
|
statusMessage: this.config.node.statusMessage ?? "",
|
|
920
920
|
// Beagle's Profile tab renders the auto-accept toggle only when
|
|
921
921
|
// the backend reports the current state.
|
|
922
|
-
autoAccept: this.config.friends?.autoAccept ??
|
|
922
|
+
autoAccept: this.config.friends?.autoAccept ?? false,
|
|
923
923
|
},
|
|
924
924
|
tun: this.tunDevice?.getConfig(),
|
|
925
925
|
// When Dora is unavailable, config.network.ip can still be the
|
|
@@ -1255,7 +1255,10 @@ export class DaemonServer {
|
|
|
1255
1255
|
this.ipcEvents.emit("event", { type: "request", userid });
|
|
1256
1256
|
// Read the flag LIVE (not captured at start) so `agentnet friends
|
|
1257
1257
|
// autoaccept off` takes effect immediately, no daemon restart.
|
|
1258
|
-
|
|
1258
|
+
// Default flipped to false 2026-08-06: friend = L3 route, and
|
|
1259
|
+
// addresses are public in the Beagle directories now. Infra
|
|
1260
|
+
// (doras/exits) opts back in explicitly in its config.
|
|
1261
|
+
if (this.config.friends?.autoAccept ?? false) {
|
|
1259
1262
|
this.logger.info(`Friend request from ${who}${hello} — auto-accepting`);
|
|
1260
1263
|
this.peerManager?.acceptFriendRequest(req.pubkey).catch((err) => {
|
|
1261
1264
|
this.logger.warn(`Auto-accept failed for ${who}: ${err}`);
|
package/dist/ui/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.275";
|
|
2
2
|
const ICON_PATHS = {
|
|
3
3
|
// ---- tab bar (the four must feel like one set) ----
|
|
4
4
|
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.275",
|
|
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",
|