fix(swarmtop): expose and render full peer identity/details from proc surfaces
This commit is contained in:
@@ -57,6 +57,40 @@ const preamble = {
|
||||
'p2p-suite.js',
|
||||
'p2p-suite-tui.js'
|
||||
],
|
||||
dhttop: [
|
||||
'edit-ansi.js',
|
||||
'edit-key-parse.js',
|
||||
'edit-stream-read.js',
|
||||
'p2p-suite.js',
|
||||
'p2p-suite-tui.js'
|
||||
],
|
||||
swarmmap: [
|
||||
'edit-ansi.js',
|
||||
'edit-key-parse.js',
|
||||
'edit-stream-read.js',
|
||||
'p2p-suite.js',
|
||||
'p2p-suite-tui.js'
|
||||
],
|
||||
routeview: [
|
||||
'edit-ansi.js',
|
||||
'edit-key-parse.js',
|
||||
'edit-stream-read.js',
|
||||
'p2p-suite.js',
|
||||
'p2p-suite-tui.js'
|
||||
],
|
||||
'holepunch-view': [
|
||||
'edit-ansi.js',
|
||||
'edit-key-parse.js',
|
||||
'edit-stream-read.js',
|
||||
'p2p-suite.js',
|
||||
'p2p-suite-tui.js'
|
||||
],
|
||||
p2ping: ['p2p-suite.js'],
|
||||
dhtscan: ['p2p-suite.js'],
|
||||
swarmdoctor: ['p2p-suite.js'],
|
||||
peerdiscover: ['p2p-suite.js'],
|
||||
p2ptrace: ['p2p-suite.js'],
|
||||
peerctl: ['p2p-suite.js'],
|
||||
meshdrop: ['p2p-suite.js'],
|
||||
taskmesh: ['p2p-suite.js'],
|
||||
peernote: ['p2p-suite.js'],
|
||||
|
||||
@@ -33,6 +33,8 @@ export const COREUTILS_COMMANDS = [
|
||||
'dir',
|
||||
'dirname',
|
||||
'dircolors',
|
||||
'dhtscan',
|
||||
'dhttop',
|
||||
'du',
|
||||
'edit',
|
||||
'echo',
|
||||
@@ -54,6 +56,7 @@ export const COREUTILS_COMMANDS = [
|
||||
'hdms',
|
||||
'help',
|
||||
'holesail',
|
||||
'holepunch-view',
|
||||
'hostid',
|
||||
'hostname',
|
||||
'hypershell-board',
|
||||
@@ -99,6 +102,10 @@ export const COREUTILS_COMMANDS = [
|
||||
'patch',
|
||||
'pathchk',
|
||||
'pathcap-verify',
|
||||
'p2ping',
|
||||
'p2ptrace',
|
||||
'peerctl',
|
||||
'peerdiscover',
|
||||
'peernote',
|
||||
'pr',
|
||||
'printenv',
|
||||
@@ -112,6 +119,7 @@ export const COREUTILS_COMMANDS = [
|
||||
'rev',
|
||||
'rm',
|
||||
'rmdir',
|
||||
'routeview',
|
||||
'savevault',
|
||||
'say',
|
||||
'sed',
|
||||
@@ -131,6 +139,8 @@ export const COREUTILS_COMMANDS = [
|
||||
'sshd',
|
||||
'stat',
|
||||
'swarmtop',
|
||||
'swarmdoctor',
|
||||
'swarmmap',
|
||||
'sum',
|
||||
'sync',
|
||||
'tar',
|
||||
|
||||
@@ -208,6 +208,33 @@ async function bareP2pReadSwarmSnapshot(ctx) {
|
||||
if (typeof snap.peerCount === 'number') out.peerCount = snap.peerCount
|
||||
if (typeof snap.topicCount === 'number') out.topicCount = snap.topicCount
|
||||
const peers = Array.isArray(snap.peers) ? snap.peers : []
|
||||
out.peers = peers.slice(0, 128)
|
||||
const detailsRaw = await bareP2pReadJson(ctx, '/proc/bare_os/peer_details.json')
|
||||
const details =
|
||||
detailsRaw && typeof detailsRaw === 'object' && Array.isArray(detailsRaw.peers)
|
||||
? detailsRaw.peers
|
||||
: []
|
||||
/** @type {Map<string, Record<string, unknown>>} */
|
||||
const byKey = new Map()
|
||||
for (const d of details) {
|
||||
if (!d || typeof d !== 'object') continue
|
||||
const rk = typeof d.remotePublicKey === 'string' ? d.remotePublicKey : ''
|
||||
const pid = typeof d.peerId === 'string' ? d.peerId : ''
|
||||
if (rk) byKey.set('k:' + rk, d)
|
||||
if (pid) byKey.set('p:' + pid, d)
|
||||
}
|
||||
const merged = []
|
||||
for (const p of peers) {
|
||||
if (!p || typeof p !== 'object') continue
|
||||
const rk = typeof p.remotePublicKey === 'string' ? p.remotePublicKey : ''
|
||||
const pid = typeof p.peerId === 'string' ? p.peerId : ''
|
||||
const d = (rk && byKey.get('k:' + rk)) || (pid && byKey.get('p:' + pid)) || null
|
||||
merged.push(d && typeof d === 'object' ? { ...p, ...d } : p)
|
||||
}
|
||||
out.peers = merged.slice(0, 128)
|
||||
return out
|
||||
}
|
||||
|
||||
async function bareP2pReadProcJson(ctx, path) {
|
||||
const v = await bareP2pReadJson(ctx, path)
|
||||
return v && typeof v === 'object' ? v : {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "dhtscan",
|
||||
"section": 1,
|
||||
"title": "dht scan summary",
|
||||
"synopsis": ["dhtscan"],
|
||||
"description": "Reads and prints compact DHT diagnostics from /proc/bare_os/dht_scan.json.",
|
||||
"options": [],
|
||||
"keywords": ["p2p", "dht", "diagnostics"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "dhttop",
|
||||
"section": 1,
|
||||
"title": "live dht tui",
|
||||
"synopsis": ["dhttop"],
|
||||
"description": "TTY dashboard for DHT status and bootstrap/firewall hints.",
|
||||
"options": [],
|
||||
"keywords": ["p2p", "dht", "tui"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "holepunch-view",
|
||||
"section": 1,
|
||||
"title": "holepunch and nat tui",
|
||||
"synopsis": ["holepunch-view"],
|
||||
"description": "Displays NAT and holepunch summary telemetry for the current swarm session.",
|
||||
"options": [],
|
||||
"keywords": ["p2p", "nat", "holepunch", "tui"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "p2ping",
|
||||
"section": 1,
|
||||
"title": "p2p ping probe",
|
||||
"synopsis": ["p2ping [LABEL]"],
|
||||
"description": "Emits a p2p ping event and prints local latency and swarm peer hints.",
|
||||
"options": [],
|
||||
"keywords": ["p2p", "ping", "latency", "swarm"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "p2ptrace",
|
||||
"section": 1,
|
||||
"title": "p2p trace collector",
|
||||
"synopsis": ["p2ptrace [APP] [N]"],
|
||||
"description": "Dumps recent p2p events as JSON lines for tracing and diagnostics.",
|
||||
"options": [],
|
||||
"keywords": ["p2p", "trace", "json"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "peerctl",
|
||||
"section": 1,
|
||||
"title": "peer control plane utility",
|
||||
"synopsis": ["peerctl health", "peerctl request ACTION [JSON_PAYLOAD]", "peerctl history [N]"],
|
||||
"description": "P2P control helper for health checks and request envelopes.",
|
||||
"options": [],
|
||||
"keywords": ["p2p", "control", "rpc"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "peerdiscover",
|
||||
"section": 1,
|
||||
"title": "peer service discovery utility",
|
||||
"synopsis": ["peerdiscover announce SERVICE [META]", "peerdiscover list [N]"],
|
||||
"description": "Announces and lists peer service metadata over p2p event history.",
|
||||
"options": [],
|
||||
"keywords": ["p2p", "discovery", "service"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "routeview",
|
||||
"section": 1,
|
||||
"title": "route visibility tui",
|
||||
"synopsis": ["routeview"],
|
||||
"description": "Shows direct-vs-relay route summary from /proc/bare_os/route_summary.json.",
|
||||
"options": [],
|
||||
"keywords": ["p2p", "routing", "relay", "tui"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "swarmdoctor",
|
||||
"section": 1,
|
||||
"title": "swarm diagnostic doctor",
|
||||
"synopsis": ["swarmdoctor"],
|
||||
"description": "Reads /proc/bare_os/swarm_doctor.json and reports swarm health/remediation.",
|
||||
"options": [],
|
||||
"keywords": ["p2p", "swarm", "doctor"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "swarmmap",
|
||||
"section": 1,
|
||||
"title": "swarm topology map tui",
|
||||
"synopsis": ["swarmmap"],
|
||||
"description": "Live peer topology viewer for swarm peers and states.",
|
||||
"options": [],
|
||||
"keywords": ["p2p", "swarm", "tui", "topology"]
|
||||
}
|
||||
@@ -6,6 +6,6 @@
|
||||
"description": "Build JS /bin utilities for bare-operating-system (concat + stage to kernel/)",
|
||||
"scripts": {
|
||||
"build": "node ./scripts/ensure-man-pages.mjs && node ./build.mjs",
|
||||
"test": "node ./test/clear-sequence.test.mjs && node ./test/help-bin-list.test.mjs && node ./test/whois-rdap.test.mjs && node ./test/edit-key-parse.test.mjs && node ./test/edit-teardown.test.mjs && node ./test/baresay-say-bundle.test.mjs && node ./test/baretop-bundle.test.mjs && node ./test/baretop-fixture.test.mjs && node ./test/baretop-compose.test.mjs && node ./test/baretop-incremental.test.mjs && node ./test/baretop-ui-helpers.test.mjs && node ./test/posix-test-int-compare.test.mjs && node ./test/posix-utils-edge.test.mjs && node ./test/posix-golden-issue7.test.mjs && node ./test/getconf-pathconf-union-mirror.test.mjs && node ./test/getconf-posix-shm.test.mjs && node ./test/awk-sed-posix-smoke.test.mjs && node ./test/xattr-acl-utils.test.mjs && node ./test/xcu-issue7-sweep.test.mjs && node ./test/expand-tab-stops.test.mjs && node ./test/pkg-swarm-index-pathcap.test.mjs && node ./test/sha224-sha384-sum.test.mjs && node ./test/agent-sse-parse.test.mjs && node ./test/agent-web-fetch.test.mjs && node ./test/agent-helpers.test.mjs && node ./test/agent-workspace.test.mjs && node ./test/agent-skills.test.mjs && node ./test/agent-config-surface.test.mjs && node ./test/telnet-protocol.test.mjs && node ./test/telnet-cli.test.mjs && node ./test/login.test.mjs && node ./test/p2p-suite-bundles.test.mjs"
|
||||
"test": "node ./test/clear-sequence.test.mjs && node ./test/help-bin-list.test.mjs && node ./test/whois-rdap.test.mjs && node ./test/edit-key-parse.test.mjs && node ./test/edit-teardown.test.mjs && node ./test/baresay-say-bundle.test.mjs && node ./test/baretop-bundle.test.mjs && node ./test/baretop-fixture.test.mjs && node ./test/baretop-compose.test.mjs && node ./test/baretop-incremental.test.mjs && node ./test/baretop-ui-helpers.test.mjs && node ./test/posix-test-int-compare.test.mjs && node ./test/posix-utils-edge.test.mjs && node ./test/posix-golden-issue7.test.mjs && node ./test/getconf-pathconf-union-mirror.test.mjs && node ./test/getconf-posix-shm.test.mjs && node ./test/awk-sed-posix-smoke.test.mjs && node ./test/xattr-acl-utils.test.mjs && node ./test/xcu-issue7-sweep.test.mjs && node ./test/expand-tab-stops.test.mjs && node ./test/pkg-swarm-index-pathcap.test.mjs && node ./test/sha224-sha384-sum.test.mjs && node ./test/agent-sse-parse.test.mjs && node ./test/agent-web-fetch.test.mjs && node ./test/agent-helpers.test.mjs && node ./test/agent-workspace.test.mjs && node ./test/agent-skills.test.mjs && node ./test/agent-config-surface.test.mjs && node ./test/telnet-protocol.test.mjs && node ./test/telnet-cli.test.mjs && node ./test/login.test.mjs && node ./test/p2p-suite-bundles.test.mjs && node ./test/swarmtop-peer-visibility.test.mjs"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'dhtscan'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
'\n' +
|
||||
'Print compact DHT scan summary from /proc/bare_os/dht_scan.json.\n' +
|
||||
'See man dhtscan.'
|
||||
)
|
||||
return
|
||||
}
|
||||
const dht = await bareP2pReadProcJson(ctx, '/proc/bare_os/dht_scan.json')
|
||||
ctx.console.log(JSON.stringify(dht, null, 2))
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'dhttop'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
ctx.console.log(
|
||||
'usage: ' + argv0 + '\n' + 'Live DHT dashboard TUI.\n' + 'See man dhttop.'
|
||||
)
|
||||
return
|
||||
}
|
||||
const stdin = ctx.replStdin
|
||||
const isTTY = Boolean(stdin && /** @type {{ isTTY?: boolean }} */ (stdin).isTTY)
|
||||
if (!isTTY) {
|
||||
const dht = await bareP2pReadProcJson(ctx, '/proc/bare_os/dht_scan.json')
|
||||
ctx.console.log(JSON.stringify(dht, null, 2))
|
||||
return
|
||||
}
|
||||
await bareP2pRunSimpleTui(ctx, {
|
||||
title: 'dhttop',
|
||||
subtitle: 'dht operations · q quit · r refresh',
|
||||
renderLines: async () => {
|
||||
const dht = await bareP2pReadProcJson(ctx, '/proc/bare_os/dht_scan.json')
|
||||
const lines = []
|
||||
lines.push('schema: ' + String(dht.schema ?? '?'))
|
||||
lines.push('peers: ' + String(dht.peers ?? '?'))
|
||||
lines.push('firewalled: ' + String(dht.dhtStatus?.firewalled ?? '?'))
|
||||
lines.push('randomized: ' + String(dht.dhtStatus?.randomized ?? '?'))
|
||||
lines.push('bootstrap: ' + String(dht.bootstrapHint || '(default)'))
|
||||
lines.push('')
|
||||
lines.push('raw:')
|
||||
lines.push(JSON.stringify(dht))
|
||||
return lines
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'holepunch-view'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
'\n' +
|
||||
'NAT and holepunch summary dashboard.\n' +
|
||||
'See man holepunch-view.'
|
||||
)
|
||||
return
|
||||
}
|
||||
const stdin = ctx.replStdin
|
||||
const isTTY = Boolean(stdin && /** @type {{ isTTY?: boolean }} */ (stdin).isTTY)
|
||||
if (!isTTY) {
|
||||
const h = await bareP2pReadProcJson(ctx, '/proc/bare_os/holepunch_summary.json')
|
||||
ctx.console.log(JSON.stringify(h, null, 2))
|
||||
return
|
||||
}
|
||||
await bareP2pRunSimpleTui(ctx, {
|
||||
title: 'holepunch-view',
|
||||
subtitle: 'nat and holepunch telemetry · q quit · r refresh',
|
||||
renderLines: async () => {
|
||||
const h = await bareP2pReadProcJson(ctx, '/proc/bare_os/holepunch_summary.json')
|
||||
return [
|
||||
'peers: ' + String(h.peers ?? '?'),
|
||||
'firewalled: ' + String(h.firewalled ?? '?'),
|
||||
'randomized: ' + String(h.randomized ?? '?'),
|
||||
'',
|
||||
'note: ' + String(h.note || '')
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'p2ping'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
' [LABEL]\n' +
|
||||
'Emit a p2p ping event and print local swarm timing hints.\n' +
|
||||
'See man p2ping.'
|
||||
)
|
||||
return
|
||||
}
|
||||
const label = args.join(' ').trim() || 'ping'
|
||||
const t0 = Date.now()
|
||||
const swarm = await bareP2pReadSwarmSnapshot(ctx)
|
||||
const evtId = bareP2pId('p2ping')
|
||||
const send = bareP2pSend(ctx, 'p2ping', 'ping', {
|
||||
evtId,
|
||||
label,
|
||||
sentAtMs: t0
|
||||
})
|
||||
const t1 = Date.now()
|
||||
const out = {
|
||||
schema: 1,
|
||||
tool: argv0,
|
||||
evtId,
|
||||
label,
|
||||
ok: !(send && send.ok === false),
|
||||
sendReason: send && send.ok === false ? String(send.reason || 'send_failed') : null,
|
||||
localElapsedMs: t1 - t0,
|
||||
swarmPeers: swarm.peerCount
|
||||
}
|
||||
ctx.console.log(JSON.stringify(out, null, 2))
|
||||
if (send && send.ok === false) ctx.exitCode = 1
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'p2ptrace'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
' [APP] [N]\n' +
|
||||
'Dump recent p2p events as JSON lines.\n' +
|
||||
'APP defaults to all known p2p apps.\n' +
|
||||
'See man p2ptrace.'
|
||||
)
|
||||
return
|
||||
}
|
||||
const app = String(args[0] || '').trim()
|
||||
const n = Math.max(1, Math.min(2000, parseInt(args[1] || '200', 10) || 200))
|
||||
const apps = app
|
||||
? [app]
|
||||
: [
|
||||
'swarmtop',
|
||||
'meshdrop',
|
||||
'taskmesh',
|
||||
'peernote',
|
||||
'hypershell-board',
|
||||
'p2ping',
|
||||
'peerdiscover',
|
||||
'peerctl'
|
||||
]
|
||||
const rows = []
|
||||
for (const a of apps) {
|
||||
rows.push(...bareP2pCollectFromHistory(ctx, a, n))
|
||||
}
|
||||
rows.sort((a, b) => (a.receivedAtMs || 0) - (b.receivedAtMs || 0))
|
||||
for (const r of rows.slice(-n)) {
|
||||
ctx.console.log(
|
||||
JSON.stringify({
|
||||
atMs: r.receivedAtMs || 0,
|
||||
app: r.packet?.app || '?',
|
||||
kind: r.packet?.kind || '?',
|
||||
from: r.displayName || r.fromPeerKey || '',
|
||||
payload: r.packet?.payload || {}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'peerctl'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
' health\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' request ACTION [JSON_PAYLOAD]\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' history [N]\n' +
|
||||
'P2P control plane helper over event envelopes.\n' +
|
||||
'See man peerctl.'
|
||||
)
|
||||
if (args.length === 0) ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const sub = args[0]
|
||||
if (sub === 'health') {
|
||||
const swarm = await bareP2pReadSwarmSnapshot(ctx)
|
||||
const doctor = await bareP2pReadProcJson(ctx, '/proc/bare_os/swarm_doctor.json')
|
||||
ctx.console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
schema: 1,
|
||||
swarmPeers: swarm.peerCount,
|
||||
health: doctor.health || 'unknown',
|
||||
remediation: doctor.remediation || []
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
if (sub === 'request') {
|
||||
const action = String(args[1] || '').trim()
|
||||
if (!action) {
|
||||
ctx.console.error(argv0 + ': request requires ACTION')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
let payload = {}
|
||||
if (args[2]) {
|
||||
try {
|
||||
payload = JSON.parse(args.slice(2).join(' '))
|
||||
} catch {
|
||||
ctx.console.error(argv0 + ': invalid JSON payload')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
const r = bareP2pSend(ctx, 'peerctl', 'request', {
|
||||
requestId: bareP2pId('ctl'),
|
||||
action,
|
||||
payload
|
||||
})
|
||||
if (r && r.ok === false) ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
if (sub === 'history') {
|
||||
const n = Math.max(1, Math.min(1000, parseInt(args[1] || '50', 10) || 50))
|
||||
const rows = bareP2pCollectFromHistory(ctx, 'peerctl', 2000).slice(-n)
|
||||
for (const row of rows) {
|
||||
ctx.console.log(
|
||||
'[' +
|
||||
bareP2pFmtClock(row.receivedAtMs) +
|
||||
'] ' +
|
||||
String(row.packet?.kind || '?') +
|
||||
' ' +
|
||||
JSON.stringify(row.packet?.payload || {})
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': unsupported subcommand')
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'peerdiscover'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
' announce SERVICE [META]\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' list [N]\n' +
|
||||
'Peer service announce/list over p2p event history.\n' +
|
||||
'See man peerdiscover.'
|
||||
)
|
||||
if (args.length === 0) ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const sub = args[0]
|
||||
if (sub === 'announce') {
|
||||
const service = String(args[1] || '').trim()
|
||||
if (!service) {
|
||||
ctx.console.error(argv0 + ': announce requires SERVICE')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const meta = args.slice(2).join(' ').trim()
|
||||
const r = bareP2pSend(ctx, 'peerdiscover', 'service.announce', {
|
||||
service,
|
||||
meta,
|
||||
id: bareP2pId('svc')
|
||||
})
|
||||
if (r && r.ok === false) ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
if (sub === 'list') {
|
||||
const n = Math.max(1, Math.min(500, parseInt(args[1] || '50', 10) || 50))
|
||||
const rows = bareP2pCollectFromHistory(ctx, 'peerdiscover', 2000)
|
||||
.filter((r) => r.packet?.kind === 'service.announce')
|
||||
.slice(-n)
|
||||
for (const row of rows) {
|
||||
const p = row.packet?.payload || {}
|
||||
ctx.console.log(
|
||||
'[' +
|
||||
bareP2pFmtClock(row.receivedAtMs) +
|
||||
'] ' +
|
||||
String(p.service || 'unknown') +
|
||||
(p.meta ? ' ' + String(p.meta) : '')
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': unsupported subcommand')
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'routeview'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
ctx.console.log(
|
||||
'usage: ' + argv0 + '\n' + 'Route and relay visibility TUI.\n' + 'See man routeview.'
|
||||
)
|
||||
return
|
||||
}
|
||||
const stdin = ctx.replStdin
|
||||
const isTTY = Boolean(stdin && /** @type {{ isTTY?: boolean }} */ (stdin).isTTY)
|
||||
if (!isTTY) {
|
||||
const r = await bareP2pReadProcJson(ctx, '/proc/bare_os/route_summary.json')
|
||||
ctx.console.log(JSON.stringify(r, null, 2))
|
||||
return
|
||||
}
|
||||
await bareP2pRunSimpleTui(ctx, {
|
||||
title: 'routeview',
|
||||
subtitle: 'direct vs relay summary · q quit · r refresh',
|
||||
renderLines: async () => {
|
||||
const r = await bareP2pReadProcJson(ctx, '/proc/bare_os/route_summary.json')
|
||||
return [
|
||||
'peers: ' + String(r.peers ?? '?'),
|
||||
'directLikely: ' + String(r.directLikely ?? '?'),
|
||||
'relayLikely: ' + String(r.relayLikely ?? '?'),
|
||||
'',
|
||||
'note: ' + String(r.note || '')
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'swarmdoctor'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
'\n' +
|
||||
'Print swarm diagnosis from /proc/bare_os/swarm_doctor.json.\n' +
|
||||
'See man swarmdoctor.'
|
||||
)
|
||||
return
|
||||
}
|
||||
const doc = await bareP2pReadProcJson(ctx, '/proc/bare_os/swarm_doctor.json')
|
||||
ctx.console.log(JSON.stringify(doc, null, 2))
|
||||
if (doc && typeof doc === 'object' && doc.health && doc.health !== 'ok') {
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'swarmmap'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
ctx.console.log(
|
||||
'usage: ' + argv0 + '\n' + 'Swarm topology map TUI.\n' + 'See man swarmmap.'
|
||||
)
|
||||
return
|
||||
}
|
||||
const stdin = ctx.replStdin
|
||||
const isTTY = Boolean(stdin && /** @type {{ isTTY?: boolean }} */ (stdin).isTTY)
|
||||
const snap = await bareP2pReadSwarmSnapshot(ctx)
|
||||
if (!isTTY) {
|
||||
ctx.console.log(JSON.stringify(snap, null, 2))
|
||||
return
|
||||
}
|
||||
await bareP2pRunSimpleTui(ctx, {
|
||||
title: 'swarmmap',
|
||||
subtitle: 'peer topology · q quit · r refresh',
|
||||
renderLines: async () => {
|
||||
const s = await bareP2pReadSwarmSnapshot(ctx)
|
||||
const lines = []
|
||||
lines.push('peerCount: ' + String(s.peerCount ?? '?'))
|
||||
lines.push('topicCount: ' + String(s.topicCount ?? '?'))
|
||||
lines.push('')
|
||||
lines.push('Peers:')
|
||||
const peers = Array.isArray(s.peers) ? s.peers : []
|
||||
if (!peers.length) lines.push(' (none)')
|
||||
for (const p of peers.slice(0, 48)) {
|
||||
const id =
|
||||
p && typeof p === 'object' && typeof p.remotePublicKey === 'string'
|
||||
? p.remotePublicKey.slice(0, 20)
|
||||
: '?'
|
||||
lines.push(' ' + id + ' ' + String(p.state || 'connected'))
|
||||
}
|
||||
return lines
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -85,16 +85,31 @@ async function run(ctx, argv) {
|
||||
lines.push('Peers:')
|
||||
const peers = Array.isArray(snap.peers) ? snap.peers : []
|
||||
if (!peers.length) lines.push(' (no peers reported)')
|
||||
for (const p of peers.slice(0, 24)) {
|
||||
for (const p of peers.slice(0, 12)) {
|
||||
const key =
|
||||
p && typeof p === 'object' && typeof p.remotePublicKey === 'string'
|
||||
? p.remotePublicKey.slice(0, 16)
|
||||
? p.remotePublicKey
|
||||
: '?'
|
||||
const state =
|
||||
p && typeof p === 'object' && typeof p.state === 'string'
|
||||
? p.state
|
||||
: 'connected'
|
||||
lines.push(' ' + key + ' ' + state)
|
||||
const addr =
|
||||
p && typeof p === 'object' && p.endpoint && typeof p.endpoint === 'object'
|
||||
? String(p.endpoint.address || p.remoteAddress || '?') +
|
||||
':' +
|
||||
String(p.endpoint.port || p.remotePort || '?')
|
||||
: String(p?.remoteAddress || '?') + ':' + String(p?.remotePort || '?')
|
||||
const osHint =
|
||||
p &&
|
||||
typeof p === 'object' &&
|
||||
p.peerOs &&
|
||||
typeof p.peerOs === 'object' &&
|
||||
p.peerOs.osHint
|
||||
? String(p.peerOs.osHint)
|
||||
: 'unknown'
|
||||
lines.push(' pubkey: ' + key)
|
||||
lines.push(' state=' + state + ' endpoint=' + addr + ' os=' + osHint)
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('Recent events:')
|
||||
|
||||
@@ -17,7 +17,17 @@ test('p2p suite commands include shared envelope helper in kernel bins', async (
|
||||
'meshdrop',
|
||||
'taskmesh',
|
||||
'peernote',
|
||||
'hypershell-board'
|
||||
'hypershell-board',
|
||||
'p2ping',
|
||||
'dhtscan',
|
||||
'dhttop',
|
||||
'swarmdoctor',
|
||||
'swarmmap',
|
||||
'peerdiscover',
|
||||
'p2ptrace',
|
||||
'peerctl',
|
||||
'routeview',
|
||||
'holepunch-view'
|
||||
]
|
||||
for (const name of names) {
|
||||
const src = await read(join(kernelBin, name))
|
||||
@@ -32,7 +42,17 @@ test('p2p suite man pages exist', async (t) => {
|
||||
'meshdrop',
|
||||
'taskmesh',
|
||||
'peernote',
|
||||
'hypershell-board'
|
||||
'hypershell-board',
|
||||
'p2ping',
|
||||
'dhtscan',
|
||||
'dhttop',
|
||||
'swarmdoctor',
|
||||
'swarmmap',
|
||||
'peerdiscover',
|
||||
'p2ptrace',
|
||||
'peerctl',
|
||||
'routeview',
|
||||
'holepunch-view'
|
||||
]
|
||||
for (const page of pages) {
|
||||
const body = await read(join(coreutilsRoot, 'man/pages', page + '.json'))
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import test from 'brittle'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const coreutilsRoot = join(__dirname, '..')
|
||||
|
||||
test('p2p suite reads peer_details proc for swarm snapshots', async (t) => {
|
||||
const src = await readFile(join(coreutilsRoot, 'lib/p2p-suite.js'), 'utf8')
|
||||
t.ok(src.includes('/proc/bare_os/peer_details.json'), 'reads peer details proc')
|
||||
t.ok(src.includes('out.peers = merged.slice(0, 128)'), 'merges swarm + peer details rows')
|
||||
})
|
||||
|
||||
test('swarmtop renders full public keys and endpoint details', async (t) => {
|
||||
const src = await readFile(join(coreutilsRoot, 'src/swarmtop.js'), 'utf8')
|
||||
t.ok(src.includes("lines.push(' pubkey: ' + key)"), 'shows full pubkey label')
|
||||
t.ok(src.includes('endpoint='), 'shows endpoint details')
|
||||
t.ok(
|
||||
src.includes('? p.remotePublicKey') || src.includes('? p.remotePublicKey\n'),
|
||||
'uses full remotePublicKey value in peer rows'
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user