@decentnetwork/lan 0.1.14 → 0.1.15
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 +0 -3
- package/dist/cli/commands.js +104 -38
- package/dist/cli/index.js +19 -2
- package/package.json +1 -1
package/dist/cli/commands.d.ts
CHANGED
|
@@ -15,9 +15,6 @@ export declare function cmdInit(args: {
|
|
|
15
15
|
export declare function cmdIdentityShow(args: {
|
|
16
16
|
configDir?: string;
|
|
17
17
|
}): Promise<void>;
|
|
18
|
-
/**
|
|
19
|
-
* List peers from IPAM
|
|
20
|
-
*/
|
|
21
18
|
export declare function cmdPeersList(args: {
|
|
22
19
|
configDir?: string;
|
|
23
20
|
}): Promise<void>;
|
package/dist/cli/commands.js
CHANGED
|
@@ -11,7 +11,6 @@ import { Ipam } from "../ipam/ipam.js";
|
|
|
11
11
|
import { Policy } from "../acl/policy.js";
|
|
12
12
|
import { AclEngine } from "../acl/acl-engine.js";
|
|
13
13
|
import { AuditLog } from "../acl/audit.js";
|
|
14
|
-
import { DnsResolver } from "../dns/resolver.js";
|
|
15
14
|
/**
|
|
16
15
|
* Refuse to open a second Carrier peer with this identity if the
|
|
17
16
|
* daemon is already running with the same keypair. Two peers sharing
|
|
@@ -162,29 +161,51 @@ export async function cmdIdentityShow(args) {
|
|
|
162
161
|
/**
|
|
163
162
|
* List peers from IPAM
|
|
164
163
|
*/
|
|
164
|
+
/**
|
|
165
|
+
* Fetch the IPAM contents from wherever they're live. When the daemon
|
|
166
|
+
* is up, dora-merged entries live in the daemon's memory only — reading
|
|
167
|
+
* the on-disk file gives the operator a stale (often empty) snapshot.
|
|
168
|
+
* The daemon publishes the in-memory list over IPC via `diag`. Falls
|
|
169
|
+
* back to disk when the daemon is down.
|
|
170
|
+
*/
|
|
171
|
+
async function fetchLiveIpam(config) {
|
|
172
|
+
if (daemonPid(config) !== null) {
|
|
173
|
+
const res = await ipcCall(config, { op: "diag" });
|
|
174
|
+
if (res.ok) {
|
|
175
|
+
const data = res.data;
|
|
176
|
+
return { peers: data.ipam ?? [], source: "daemon" };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const ipam = await Ipam.loadOrCreate(config.paths.ipamFile, config.node.namespace);
|
|
180
|
+
return {
|
|
181
|
+
peers: ipam.getPeers().map((p) => ({
|
|
182
|
+
name: p.name,
|
|
183
|
+
virtualIp: p.virtualIp,
|
|
184
|
+
carrierId: p.carrierId,
|
|
185
|
+
})),
|
|
186
|
+
source: "disk",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
165
189
|
export async function cmdPeersList(args) {
|
|
166
190
|
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
167
191
|
const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
|
|
168
|
-
const
|
|
169
|
-
const peers = ipam.getPeers();
|
|
192
|
+
const { peers, source } = await fetchLiveIpam(config);
|
|
170
193
|
if (peers.length === 0) {
|
|
171
|
-
|
|
194
|
+
if (source === "daemon") {
|
|
195
|
+
console.log("Daemon up but IPAM is empty — dora roster hasn't merged yet, or dora isn't configured.");
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
console.log("No peers configured. Daemon is down — use 'agentnet ipam assign' to add peers,");
|
|
199
|
+
console.log("or 'agentnet up' to start the daemon (dora will populate IPAM automatically).");
|
|
200
|
+
}
|
|
172
201
|
return;
|
|
173
202
|
}
|
|
174
|
-
console.log(`Peers (${peers.length}):`);
|
|
203
|
+
console.log(`Peers (${peers.length}) ${source === "daemon" ? "[live]" : "[disk]"}:`);
|
|
175
204
|
console.log("");
|
|
176
205
|
for (const peer of peers) {
|
|
177
|
-
const expires = peer.expiresAt
|
|
178
|
-
? ` (expires ${new Date(peer.expiresAt).toISOString()})`
|
|
179
|
-
: "";
|
|
180
206
|
console.log(` ${peer.name}.${config.network.dnsDomain}`);
|
|
181
207
|
console.log(` Virtual IP: ${peer.virtualIp}`);
|
|
182
208
|
console.log(` Carrier ID: ${peer.carrierId.slice(0, 32)}...`);
|
|
183
|
-
if (peer.services.length > 0) {
|
|
184
|
-
console.log(` Services: ${peer.services.map((s) => `${s.name}:${s.port}`).join(", ")}`);
|
|
185
|
-
}
|
|
186
|
-
if (expires)
|
|
187
|
-
console.log(` ${expires.trim()}`);
|
|
188
209
|
console.log("");
|
|
189
210
|
}
|
|
190
211
|
}
|
|
@@ -282,16 +303,29 @@ export async function cmdRevoke(args) {
|
|
|
282
303
|
export async function cmdResolve(args) {
|
|
283
304
|
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
284
305
|
const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
306
|
+
// Prefer the live daemon's IPAM — the on-disk ipam.yaml is empty
|
|
307
|
+
// until the operator runs `agentnet ipam assign` manually, but dora
|
|
308
|
+
// populates the daemon's in-memory IPAM with the full roster.
|
|
309
|
+
// Falls back to disk when the daemon is down.
|
|
310
|
+
const { peers } = await fetchLiveIpam(config);
|
|
311
|
+
const stripped = args.name.endsWith(`.${config.network.dnsDomain}`)
|
|
312
|
+
? args.name.slice(0, -(config.network.dnsDomain.length + 1))
|
|
313
|
+
: args.name;
|
|
314
|
+
const byName = peers.find((p) => p.name === stripped);
|
|
315
|
+
const byIp = peers.find((p) => p.virtualIp === args.name);
|
|
316
|
+
const byCarrier = peers.find((p) => p.carrierId === args.name);
|
|
317
|
+
const hit = byName ?? byIp ?? byCarrier;
|
|
318
|
+
if (hit) {
|
|
319
|
+
if (byIp) {
|
|
320
|
+
console.log(`${args.name} -> ${hit.name} (${hit.carrierId.slice(0, 16)}...)`);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
console.log(`${args.name} -> ${hit.virtualIp}`);
|
|
324
|
+
}
|
|
325
|
+
return;
|
|
294
326
|
}
|
|
327
|
+
console.log(`Cannot resolve: ${args.name}`);
|
|
328
|
+
process.exit(1);
|
|
295
329
|
}
|
|
296
330
|
/**
|
|
297
331
|
* Show daemon status (must be run while daemon is up — placeholder for now)
|
|
@@ -529,26 +563,58 @@ export async function cmdFriendsPending(args) {
|
|
|
529
563
|
if (daemonPid(config) === null) {
|
|
530
564
|
throw new Error("Daemon not running — pending friend-requests are held by the daemon. Start it with 'agentnet up' first.");
|
|
531
565
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
566
|
+
// Two sources of "in-flight" friend state, both useful to the
|
|
567
|
+
// operator under one command:
|
|
568
|
+
// 1) inbound — peers who sent us a friend-request that hasn't
|
|
569
|
+
// been accepted yet. Auto-accept (default) drains this
|
|
570
|
+
// instantly, so the inbound list is usually empty.
|
|
571
|
+
// 2) outbound — peers WE sent a request to who haven't
|
|
572
|
+
// accepted; the SDK's friends() returns these with
|
|
573
|
+
// status="requested". Useful for "is my friend-request
|
|
574
|
+
// stuck?" debugging.
|
|
575
|
+
const [pendingRes, diagRes] = await Promise.all([
|
|
576
|
+
ipcCall(config, { op: "friends-pending" }),
|
|
577
|
+
ipcCall(config, { op: "diag" }),
|
|
578
|
+
]);
|
|
579
|
+
if (!pendingRes.ok)
|
|
580
|
+
throw new Error(`Daemon refused: ${pendingRes.error}`);
|
|
581
|
+
if (!diagRes.ok)
|
|
582
|
+
throw new Error(`Daemon diag failed: ${diagRes.error}`);
|
|
583
|
+
const inbound = pendingRes.data?.pending ?? [];
|
|
584
|
+
const friends = diagRes.data.friends ?? [];
|
|
585
|
+
const outbound = friends.filter((f) => f.status === "requested");
|
|
586
|
+
if (inbound.length === 0 && outbound.length === 0) {
|
|
587
|
+
console.log("No friend-requests in flight.");
|
|
588
|
+
console.log(" inbound: none queued (autoAccept handles them automatically)");
|
|
589
|
+
console.log(" outbound: every sent request has been accepted");
|
|
538
590
|
return;
|
|
539
591
|
}
|
|
540
|
-
|
|
541
|
-
|
|
592
|
+
if (inbound.length > 0) {
|
|
593
|
+
console.log(`Inbound (waiting for your accept) — ${inbound.length}:`);
|
|
594
|
+
for (const e of inbound) {
|
|
595
|
+
console.log("");
|
|
596
|
+
console.log(` ${e.name || "(unnamed)"}`);
|
|
597
|
+
console.log(` userid: ${e.userid}`);
|
|
598
|
+
if (e.hello)
|
|
599
|
+
console.log(` hello: ${e.hello}`);
|
|
600
|
+
console.log(` arrived: ${e.arrivedAt}`);
|
|
601
|
+
}
|
|
602
|
+
console.log("");
|
|
603
|
+
console.log(" Accept: agentnet friends accept --userid <USERID>");
|
|
604
|
+
console.log(" Reject: agentnet friends reject --userid <USERID>");
|
|
542
605
|
console.log("");
|
|
543
|
-
console.log(` ${e.name || "(unnamed)"}`);
|
|
544
|
-
console.log(` userid: ${e.userid}`);
|
|
545
|
-
if (e.hello)
|
|
546
|
-
console.log(` hello: ${e.hello}`);
|
|
547
|
-
console.log(` arrived: ${e.arrivedAt}`);
|
|
548
606
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
607
|
+
if (outbound.length > 0) {
|
|
608
|
+
console.log(`Outbound (we requested, peer hasn't accepted yet) — ${outbound.length}:`);
|
|
609
|
+
for (const f of outbound) {
|
|
610
|
+
const id = f.carrierId || f.pubkey;
|
|
611
|
+
console.log(` ${f.name || "(no name)"} userid: ${id}`);
|
|
612
|
+
}
|
|
613
|
+
console.log("");
|
|
614
|
+
console.log(" These resolve automatically once the recipient's daemon comes up");
|
|
615
|
+
console.log(" and accepts. If a peer never accepts, re-send via:");
|
|
616
|
+
console.log(" agentnet friend-request --address <ADDRESS>");
|
|
617
|
+
}
|
|
552
618
|
}
|
|
553
619
|
/**
|
|
554
620
|
* Accept a queued friend-request by userid. Routes through IPC;
|
package/dist/cli/index.js
CHANGED
|
@@ -26,7 +26,14 @@ async function main() {
|
|
|
26
26
|
.command("peers list", "List configured peers", (y) => y.option("config-dir", { type: "string" }), async (argv) => {
|
|
27
27
|
await cmdPeersList({ configDir: argv["config-dir"] });
|
|
28
28
|
})
|
|
29
|
-
|
|
29
|
+
// Nested so 'ipam list' (user-expected) doesn't get routed to
|
|
30
|
+
// 'ipam assign' by yargs' flat-positional matching. Same trap
|
|
31
|
+
// 'friends' fell into.
|
|
32
|
+
.command("ipam", "Manage IP address allocations (run 'agentnet ipam --help')", (y) => y
|
|
33
|
+
.command("list", "List peer IP allocations (live from daemon if up)", (yy) => yy.option("config-dir", { type: "string" }), async (argv) => {
|
|
34
|
+
await cmdPeersList({ configDir: argv["config-dir"] });
|
|
35
|
+
})
|
|
36
|
+
.command("assign", "Register peer with virtual IP", (yy) => yy
|
|
30
37
|
.option("peer", { type: "string", demandOption: true, describe: "Carrier ID" })
|
|
31
38
|
.option("ip", { type: "string", describe: "Virtual IP (auto if omitted)" })
|
|
32
39
|
.option("name", { type: "string", demandOption: true, describe: "Hostname" })
|
|
@@ -43,6 +50,9 @@ async function main() {
|
|
|
43
50
|
services: argv.service,
|
|
44
51
|
configDir: argv["config-dir"],
|
|
45
52
|
});
|
|
53
|
+
})
|
|
54
|
+
.demandCommand(1, "Specify an ipam subcommand (run 'agentnet ipam --help')"), () => {
|
|
55
|
+
// parent handler — never invoked because demandCommand above
|
|
46
56
|
})
|
|
47
57
|
.command("grant", "Grant access to a peer", (y) => y
|
|
48
58
|
.option("peer", { type: "string", demandOption: true })
|
|
@@ -152,8 +162,15 @@ async function main() {
|
|
|
152
162
|
.demandCommand(1, "Specify a friends subcommand (run 'agentnet friends --help')"), () => {
|
|
153
163
|
// parent handler — never invoked because demandCommand above
|
|
154
164
|
})
|
|
155
|
-
|
|
165
|
+
// Audit is also nested for consistency / future subcommands.
|
|
166
|
+
.command("audit", "View audit trail (run 'agentnet audit --help')", (y) => y
|
|
167
|
+
.command("log", "View audit log", (yy) => yy
|
|
168
|
+
.option("tail", { type: "number", default: 50 })
|
|
169
|
+
.option("config-dir", { type: "string" }), async (argv) => {
|
|
156
170
|
await cmdAuditLog({ tail: argv.tail, configDir: argv["config-dir"] });
|
|
171
|
+
})
|
|
172
|
+
.demandCommand(1, "Specify an audit subcommand (run 'agentnet audit --help')"), () => {
|
|
173
|
+
// parent handler — never invoked because demandCommand above
|
|
157
174
|
})
|
|
158
175
|
// Proxy commands — nested under a single `proxy` command so yargs
|
|
159
176
|
// builds a proper subcommand tree. Without nesting, the dotted form
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
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",
|