Updates to tools
This commit is contained in:
+121
-8
@@ -99,6 +99,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -330,17 +438,22 @@ async function bareP2pReadProcJson(ctx, path) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'dhtscan'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const args = parsed.rest
|
||||
const opt = parsed.opt
|
||||
if (opt.help) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
'\n' +
|
||||
'Print compact DHT scan summary from /proc/bare_os/dht_scan.json.\n' +
|
||||
'See man dhtscan.'
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'Print compact DHT scan summary from /proc/bare_os/dht_scan.json.',
|
||||
argv0 + ' [--summary] [--json]',
|
||||
['--summary', '--json'],
|
||||
['dhttop', 'swarmdoctor']
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
const dht = await bareP2pReadProcJson(ctx, '/proc/bare_os/dht_scan.json')
|
||||
ctx.console.log(JSON.stringify(dht, null, 2))
|
||||
bareP2pPrint(ctx, opt.summary ? { peers: dht.peers ?? null, firewalled: dht?.dhtStatus?.firewalled ?? null } : dht, opt)
|
||||
bareP2pMaybeNext(ctx, opt, 'dhttop')
|
||||
}
|
||||
|
||||
+121
-5
@@ -570,6 +570,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -920,18 +1028,25 @@ async function bareP2pRunSimpleTui(ctx, opts) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'dhttop'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const opt = parsed.opt
|
||||
if (opt.help) {
|
||||
ctx.console.log(
|
||||
'usage: ' + argv0 + '\n' + 'Live DHT dashboard TUI.\n' + 'See man dhttop.'
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'Live DHT dashboard TUI.',
|
||||
argv0 + ' [--summary] [--json]',
|
||||
['--summary', '--json'],
|
||||
['dhtscan', 'swarmdoctor']
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
const stdin = ctx.replStdin
|
||||
const isTTY = Boolean(stdin && /** @type {{ isTTY?: boolean }} */ (stdin).isTTY)
|
||||
if (!isTTY) {
|
||||
if (!isTTY || opt.json || opt.summary) {
|
||||
const dht = await bareP2pReadProcJson(ctx, '/proc/bare_os/dht_scan.json')
|
||||
ctx.console.log(JSON.stringify(dht, null, 2))
|
||||
bareP2pPrint(ctx, opt.summary ? { peers: dht.peers ?? null, firewalled: dht?.dhtStatus?.firewalled ?? null } : dht, opt)
|
||||
return
|
||||
}
|
||||
await bareP2pRunSimpleTui(ctx, {
|
||||
@@ -945,6 +1060,7 @@ async function run(ctx, argv) {
|
||||
lines.push('firewalled: ' + String(dht.dhtStatus?.firewalled ?? '?'))
|
||||
lines.push('randomized: ' + String(dht.dhtStatus?.randomized ?? '?'))
|
||||
lines.push('bootstrap: ' + String(dht.bootstrapHint || '(default)'))
|
||||
lines.push('last update age ms: ' + String(Math.max(0, Date.now() - Number(dht.atMs || Date.now()))))
|
||||
lines.push('')
|
||||
lines.push('raw:')
|
||||
lines.push(JSON.stringify(dht))
|
||||
|
||||
+120
-9
@@ -570,6 +570,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -920,22 +1028,25 @@ async function bareP2pRunSimpleTui(ctx, opts) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'holepunch-view'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const opt = parsed.opt
|
||||
if (opt.help) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
'\n' +
|
||||
'NAT and holepunch summary dashboard.\n' +
|
||||
'See man holepunch-view.'
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'NAT and holepunch summary dashboard.',
|
||||
argv0 + ' [--summary] [--json]',
|
||||
['--summary', '--json'],
|
||||
['routeview', 'dhtscan']
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
const stdin = ctx.replStdin
|
||||
const isTTY = Boolean(stdin && /** @type {{ isTTY?: boolean }} */ (stdin).isTTY)
|
||||
if (!isTTY) {
|
||||
if (!isTTY || opt.json || opt.summary) {
|
||||
const h = await bareP2pReadProcJson(ctx, '/proc/bare_os/holepunch_summary.json')
|
||||
ctx.console.log(JSON.stringify(h, null, 2))
|
||||
bareP2pPrint(ctx, opt.summary ? { peers: h.peers ?? null, firewalled: h.firewalled ?? null } : h, opt)
|
||||
return
|
||||
}
|
||||
await bareP2pRunSimpleTui(ctx, {
|
||||
|
||||
+135
-57
@@ -99,6 +99,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -330,26 +438,18 @@ async function bareP2pReadProcJson(ctx, path) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'hypershell-board'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const args = parsed.rest
|
||||
const opt = parsed.opt
|
||||
if (opt.help || args.length === 0) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
' offer-shell LABEL\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' offer-copy PATH\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' claim SESSION_ID\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' close SESSION_ID\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' list\n' +
|
||||
'P2P hypershell-style session board (intent + audit feed).\n' +
|
||||
'See man hypershell-board.'
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'P2P hypershell-style session board (intent + audit feed).',
|
||||
argv0 + ' offer-shell LABEL | offer-copy PATH | claim SESSION_ID | close SESSION_ID | list',
|
||||
['offer-shell "ops shell"', 'claim session-xxx --yes', 'list --summary'],
|
||||
['taskmesh', 'peerctl']
|
||||
)
|
||||
)
|
||||
if (args.length === 0) ctx.exitCode = 1
|
||||
return
|
||||
@@ -358,53 +458,40 @@ async function run(ctx, argv) {
|
||||
const sub = args[0]
|
||||
if (sub === 'offer-shell' || sub === 'offer-copy') {
|
||||
const label = args.slice(1).join(' ').trim()
|
||||
if (!label) {
|
||||
ctx.console.error(argv0 + ': missing label/path')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
if (!label) return bareP2pError(ctx, argv0, 'missing label/path', argv0 + ' --help', opt)
|
||||
const sessionId = bareP2pId('session')
|
||||
const mode = sub === 'offer-shell' ? 'shell' : 'copy'
|
||||
const r = bareP2pSend(ctx, 'hypershell-board', 'session.offer', {
|
||||
sessionId,
|
||||
mode,
|
||||
label
|
||||
})
|
||||
if (r && r.ok === false) ctx.exitCode = 1
|
||||
else ctx.console.log('offered ' + sessionId + ' (' + mode + ')')
|
||||
if (opt.dryRun) return bareP2pPrint(ctx, { ok: true, dryRun: true, sessionId, mode, label }, opt)
|
||||
const r = bareP2pSend(ctx, 'hypershell-board', 'session.offer', { sessionId, mode, label })
|
||||
if (r && r.ok === false) bareP2pError(ctx, argv0, String(r.reason || 'send failed'), 'swarmdoctor', opt)
|
||||
else bareP2pPrint(ctx, 'offered ' + sessionId + ' (' + mode + ')', opt)
|
||||
return
|
||||
}
|
||||
|
||||
if (sub === 'claim' || sub === 'close') {
|
||||
const sessionId = String(args[1] || '').trim()
|
||||
if (!sessionId) {
|
||||
ctx.console.error(argv0 + ': missing SESSION_ID')
|
||||
if (!sessionId) return bareP2pError(ctx, argv0, 'missing SESSION_ID', argv0 + ' list', opt)
|
||||
if (!opt.yes) {
|
||||
bareP2pPrint(ctx, 'confirmation required: pass --yes for ' + sub, opt)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const kind = sub === 'claim' ? 'session.claim' : 'session.close'
|
||||
const r = bareP2pSend(ctx, 'hypershell-board', kind, { sessionId })
|
||||
if (r && r.ok === false) ctx.exitCode = 1
|
||||
else ctx.console.log(sub + ' ' + sessionId)
|
||||
if (r && r.ok === false) bareP2pError(ctx, argv0, String(r.reason || 'send failed'), 'swarmdoctor', opt)
|
||||
else bareP2pPrint(ctx, sub + ' ' + sessionId, opt)
|
||||
return
|
||||
}
|
||||
|
||||
if (sub === 'list') {
|
||||
const rows = bareP2pCollectFromHistory(ctx, 'hypershell-board', 2000)
|
||||
/** @type {Map<string, { sessionId: string, mode: string, label: string, state: string, atMs: number }>} */
|
||||
const sessions = new Map()
|
||||
for (const row of rows) {
|
||||
const kind = row.packet?.kind
|
||||
const p = row.packet?.payload
|
||||
if (!p || typeof p !== 'object') continue
|
||||
if (kind === 'session.offer' && typeof p.sessionId === 'string') {
|
||||
sessions.set(p.sessionId, {
|
||||
sessionId: p.sessionId,
|
||||
mode: typeof p.mode === 'string' ? p.mode : 'shell',
|
||||
label: typeof p.label === 'string' ? p.label : '',
|
||||
state: 'open',
|
||||
atMs: row.receivedAtMs
|
||||
})
|
||||
sessions.set(p.sessionId, { sessionId: p.sessionId, mode: typeof p.mode === 'string' ? p.mode : 'shell', label: typeof p.label === 'string' ? p.label : '', state: 'open', atMs: row.receivedAtMs })
|
||||
} else if (kind === 'session.claim' && typeof p.sessionId === 'string') {
|
||||
const s = sessions.get(p.sessionId)
|
||||
if (s) s.state = 'claimed'
|
||||
@@ -413,21 +500,12 @@ async function run(ctx, argv) {
|
||||
if (s) s.state = 'closed'
|
||||
}
|
||||
}
|
||||
for (const s of [...sessions.values()].sort((a, b) => b.atMs - a.atMs)) {
|
||||
ctx.console.log(
|
||||
'[' +
|
||||
s.state +
|
||||
'] ' +
|
||||
s.sessionId +
|
||||
' ' +
|
||||
s.mode +
|
||||
' ' +
|
||||
s.label
|
||||
)
|
||||
}
|
||||
const arr=[...sessions.values()].sort((a,b)=>b.atMs-a.atMs)
|
||||
if (opt.summary) return bareP2pPrint(ctx, { sessions: arr.length, open: arr.filter((x)=>x.state==='open').length }, opt)
|
||||
for (const s of arr) bareP2pPrint(ctx, '[' + s.state + '] ' + s.sessionId + ' ' + s.mode + ' ' + s.label, opt)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.console.error(argv0 + ': unsupported subcommand')
|
||||
ctx.exitCode = 1
|
||||
const sug = bareP2pSuggestSubcommand(sub, ['offer-shell', 'offer-copy', 'claim', 'close', 'list'])
|
||||
bareP2pError(ctx, argv0, 'unsupported subcommand' + (sug ? ' (did you mean ' + sug + '?)' : ''), argv0 + ' --help', opt)
|
||||
}
|
||||
|
||||
+163
-90
@@ -99,6 +99,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -379,32 +487,19 @@ function bareMeshdropEnsureTransfer(db, transferId) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'meshdrop'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const args = parsed.rest
|
||||
const opt = parsed.opt
|
||||
if (opt.help || args.length === 0) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'P2P file inbox/outbox over bare-p2p envelopes on chat transport.',
|
||||
argv0 +
|
||||
' offer FILE [--to PEER_HINT]\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' send-next OFFER_ID [MAX_CHUNKS]\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' inbox [N]\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' accept OFFER_ID\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' fetch OFFER_ID [DEST]\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' status [TRANSFER_ID]\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' cancel TRANSFER_ID\n' +
|
||||
'P2P file inbox/outbox over bare-p2p envelopes on chat transport.\n' +
|
||||
'See man meshdrop.'
|
||||
' offer FILE [--to PEER_HINT] | send-next OFFER_ID [MAX_CHUNKS] | inbox [N] | accept OFFER_ID | fetch OFFER_ID [DEST] | status [TRANSFER_ID] | cancel TRANSFER_ID',
|
||||
['offer ./file.bin --to peerHint', 'send-next offer-123 8', 'status --summary'],
|
||||
['swarmtop', 'peerctl']
|
||||
)
|
||||
)
|
||||
if (args.length === 0) ctx.exitCode = 1
|
||||
return
|
||||
@@ -414,16 +509,16 @@ async function run(ctx, argv) {
|
||||
if (sub === 'offer') {
|
||||
const file = args[1]
|
||||
if (!file) {
|
||||
ctx.console.error(argv0 + ': offer requires FILE')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
return bareP2pError(ctx, argv0, 'offer requires FILE', argv0 + ' --help', opt)
|
||||
}
|
||||
const b = await ctx.vfs.readFile(file)
|
||||
if (!b) {
|
||||
ctx.console.error(argv0 + ': unable to read ' + file)
|
||||
ctx.exitCode = 1
|
||||
return bareP2pError(ctx, argv0, 'unable to read ' + file, 'check file path', opt)
|
||||
if (opt.dryRun) {
|
||||
bareP2pPrint(ctx, { ok: true, dryRun: true, action: 'offer', file, to }, opt)
|
||||
return
|
||||
}
|
||||
}
|
||||
const b64 = ctx.b4a.toString(b, 'base64')
|
||||
const toIdx = args.indexOf('--to')
|
||||
const to = toIdx >= 0 ? String(args[toIdx + 1] || '').trim() : ''
|
||||
@@ -480,22 +575,17 @@ async function run(ctx, argv) {
|
||||
to
|
||||
})
|
||||
if (r && r.ok === false) {
|
||||
ctx.console.error(argv0 + ': ' + String(r.reason || 'send failed'))
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
return bareP2pError(ctx, argv0, String(r.reason || 'send failed'), 'swarmdoctor', opt)
|
||||
}
|
||||
ctx.console.log(
|
||||
'offered ' + file + ' as ' + offerId + ' (' + chunksTotal + ' chunks)'
|
||||
)
|
||||
bareP2pPrint(ctx, 'offered ' + file + ' as ' + offerId + ' (' + chunksTotal + ' chunks)', opt)
|
||||
bareP2pMaybeNext(ctx, opt, argv0 + ' send-next ' + offerId)
|
||||
return
|
||||
}
|
||||
|
||||
if (sub === 'send-next') {
|
||||
const offerId = String(args[1] || '').trim()
|
||||
if (!offerId) {
|
||||
ctx.console.error(argv0 + ': send-next requires OFFER_ID')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
return bareP2pError(ctx, argv0, 'send-next requires OFFER_ID', argv0 + ' status', opt)
|
||||
}
|
||||
const maxChunks = Math.max(
|
||||
1,
|
||||
@@ -504,9 +594,7 @@ async function run(ctx, argv) {
|
||||
const db = await bareMeshdropDbRead(ctx)
|
||||
const rec = db?.outgoing?.[offerId]
|
||||
if (!rec) {
|
||||
ctx.console.error(argv0 + ': unknown offer: ' + offerId)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
return bareP2pError(ctx, argv0, 'unknown offer: ' + offerId, argv0 + ' status', opt)
|
||||
}
|
||||
let sent = 0
|
||||
while (sent < maxChunks && rec.sentChunks < rec.chunksTotal) {
|
||||
@@ -535,9 +623,7 @@ async function run(ctx, argv) {
|
||||
sentChunks: rec.sentChunks,
|
||||
chunksTotal: rec.chunksTotal
|
||||
})
|
||||
ctx.console.log(
|
||||
'sent ' + sent + ' chunk(s), ' + rec.sentChunks + '/' + rec.chunksTotal
|
||||
)
|
||||
bareP2pPrint(ctx, 'sent ' + sent + ' chunk(s), ' + rec.sentChunks + '/' + rec.chunksTotal, opt)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -552,7 +638,7 @@ async function run(ctx, argv) {
|
||||
const bytes = typeof p.byteLength === 'number' ? p.byteLength : 0
|
||||
const chunks = typeof p.chunksTotal === 'number' ? p.chunksTotal : '?'
|
||||
const from = row.displayName || row.fromPeerKey.slice(0, 10) || 'peer'
|
||||
ctx.console.log(
|
||||
bareP2pPrint(ctx,
|
||||
'[' +
|
||||
bareP2pFmtClock(row.receivedAtMs) +
|
||||
'] ' +
|
||||
@@ -565,7 +651,7 @@ async function run(ctx, argv) {
|
||||
String(chunks) +
|
||||
' from=' +
|
||||
from
|
||||
)
|
||||
, opt)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -573,9 +659,7 @@ async function run(ctx, argv) {
|
||||
if (sub === 'accept') {
|
||||
const offerId = String(args[1] || '').trim()
|
||||
if (!offerId) {
|
||||
ctx.console.error(argv0 + ': accept requires OFFER_ID')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
return bareP2pError(ctx, argv0, 'accept requires OFFER_ID', argv0 + ' inbox', opt)
|
||||
}
|
||||
const rows = bareP2pCollectFromHistory(ctx, 'meshdrop', 2000)
|
||||
const offer = rows.findLast(
|
||||
@@ -586,9 +670,7 @@ async function run(ctx, argv) {
|
||||
r.packet.payload.offerId === offerId
|
||||
)
|
||||
if (!offer) {
|
||||
ctx.console.error(argv0 + ': offer not found: ' + offerId)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
return bareP2pError(ctx, argv0, 'offer not found: ' + offerId, argv0 + ' inbox', opt)
|
||||
}
|
||||
const p = offer.packet.payload
|
||||
const transferId =
|
||||
@@ -619,16 +701,14 @@ async function run(ctx, argv) {
|
||||
tr.updatedAtMs = Date.now()
|
||||
await bareMeshdropDbWrite(ctx, db)
|
||||
bareP2pSend(ctx, 'meshdrop', 'accept', { offerId, transferId })
|
||||
ctx.console.log('accepted ' + offerId + ' (transfer ' + transferId + ')')
|
||||
bareP2pPrint(ctx, 'accepted ' + offerId + ' (transfer ' + transferId + ')', opt)
|
||||
return
|
||||
}
|
||||
|
||||
if (sub === 'fetch') {
|
||||
const offerId = String(args[1] || '').trim()
|
||||
if (!offerId) {
|
||||
ctx.console.error(argv0 + ': fetch requires OFFER_ID')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
return bareP2pError(ctx, argv0, 'fetch requires OFFER_ID', argv0 + ' inbox', opt)
|
||||
}
|
||||
const rows = bareP2pCollectFromHistory(ctx, 'meshdrop', 2000)
|
||||
const offer = rows.findLast(
|
||||
@@ -639,9 +719,7 @@ async function run(ctx, argv) {
|
||||
r.packet.payload.offerId === offerId
|
||||
)
|
||||
if (!offer) {
|
||||
ctx.console.error(argv0 + ': offer not found: ' + offerId)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
return bareP2pError(ctx, argv0, 'offer not found: ' + offerId, argv0 + ' inbox', opt)
|
||||
}
|
||||
const p = offer.packet.payload
|
||||
const fileName =
|
||||
@@ -650,16 +728,7 @@ async function run(ctx, argv) {
|
||||
const db = await bareMeshdropDbRead(ctx)
|
||||
const incoming = db?.incoming?.[offerId]
|
||||
if (!incoming) {
|
||||
ctx.console.error(
|
||||
argv0 +
|
||||
': offer is not accepted locally; run `' +
|
||||
argv0 +
|
||||
' accept ' +
|
||||
offerId +
|
||||
'` first'
|
||||
)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
return bareP2pError(ctx, argv0, 'offer not accepted locally', argv0 + ' accept ' + offerId, opt)
|
||||
}
|
||||
const chunks = incoming.chunks && typeof incoming.chunks === 'object' ? incoming.chunks : {}
|
||||
const total = typeof incoming.chunksTotal === 'number' ? incoming.chunksTotal : 0
|
||||
@@ -667,20 +736,7 @@ async function run(ctx, argv) {
|
||||
for (let i = 0; i < total; i++) {
|
||||
const part = chunks[i]
|
||||
if (typeof part !== 'string' || !part) {
|
||||
ctx.console.error(
|
||||
argv0 +
|
||||
': missing chunk ' +
|
||||
i +
|
||||
'/' +
|
||||
total +
|
||||
' (run `' +
|
||||
argv0 +
|
||||
' status ' +
|
||||
incoming.transferId +
|
||||
'` to inspect)'
|
||||
)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
return bareP2pError(ctx, argv0, 'missing chunk ' + i + '/' + total, argv0 + ' status ' + incoming.transferId, opt)
|
||||
}
|
||||
pieces.push(part)
|
||||
}
|
||||
@@ -699,7 +755,7 @@ async function run(ctx, argv) {
|
||||
transferId: incoming.transferId,
|
||||
savedTo: dest
|
||||
})
|
||||
ctx.console.log('saved ' + offerId + ' -> ' + dest)
|
||||
bareP2pPrint(ctx, 'saved ' + offerId + ' -> ' + dest, opt)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -752,11 +808,23 @@ async function run(ctx, argv) {
|
||||
.filter((x) => !transferId || x.transferId === transferId)
|
||||
.sort((a, b) => (b.updatedAtMs || 0) - (a.updatedAtMs || 0))
|
||||
if (!items.length) {
|
||||
ctx.console.log('no transfers')
|
||||
bareP2pPrint(ctx, opt.summary ? { transfers: 0 } : 'no transfers', opt)
|
||||
return
|
||||
}
|
||||
if (opt.summary) {
|
||||
bareP2pPrint(
|
||||
ctx,
|
||||
{
|
||||
transfers: items.length,
|
||||
active: items.filter((x) => String(x.status || '').includes('send') || String(x.status || '').includes('receiv')).length
|
||||
},
|
||||
opt
|
||||
)
|
||||
return
|
||||
}
|
||||
for (const it of items) {
|
||||
ctx.console.log(
|
||||
bareP2pPrint(
|
||||
ctx,
|
||||
(it.transferId || '?') +
|
||||
' role=' +
|
||||
String(it.role || '?') +
|
||||
@@ -766,6 +834,8 @@ async function run(ctx, argv) {
|
||||
String(it.chunksReceived || 0) +
|
||||
'/' +
|
||||
String(it.chunksTotal || 0)
|
||||
,
|
||||
opt
|
||||
)
|
||||
}
|
||||
return
|
||||
@@ -774,7 +844,10 @@ async function run(ctx, argv) {
|
||||
if (sub === 'cancel') {
|
||||
const transferId = String(args[1] || '').trim()
|
||||
if (!transferId) {
|
||||
ctx.console.error(argv0 + ': cancel requires TRANSFER_ID')
|
||||
return bareP2pError(ctx, argv0, 'cancel requires TRANSFER_ID', argv0 + ' status', opt)
|
||||
}
|
||||
if (!opt.yes) {
|
||||
bareP2pPrint(ctx, 'confirmation required: pass --yes to cancel transfer', opt)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
@@ -784,10 +857,10 @@ async function run(ctx, argv) {
|
||||
tr.updatedAtMs = Date.now()
|
||||
await bareMeshdropDbWrite(ctx, db)
|
||||
bareP2pSend(ctx, 'meshdrop', 'cancel', { transferId })
|
||||
ctx.console.log('cancelled ' + transferId)
|
||||
bareP2pPrint(ctx, 'cancelled ' + transferId, opt)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.console.error(argv0 + ': unsupported subcommand')
|
||||
ctx.exitCode = 1
|
||||
const sug = bareP2pSuggestSubcommand(sub, ['offer', 'send-next', 'inbox', 'accept', 'fetch', 'status', 'cancel'])
|
||||
bareP2pError(ctx, argv0, 'unsupported subcommand' + (sug ? ' (did you mean ' + sug + '?)' : ''), argv0 + ' --help', opt)
|
||||
}
|
||||
|
||||
+126
-9
@@ -99,6 +99,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -330,14 +438,18 @@ async function bareP2pReadProcJson(ctx, path) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'p2ping'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const args = parsed.rest
|
||||
const opt = parsed.opt
|
||||
if (opt.help) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
' [LABEL]\n' +
|
||||
'Emit a p2p ping event and print local swarm timing hints.\n' +
|
||||
'See man p2ping.'
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'Emit a p2p ping event and print local swarm timing hints.',
|
||||
argv0 + ' [LABEL]',
|
||||
['hello', '--summary', '--json'],
|
||||
['swarmtop', 'p2ptrace']
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -361,6 +473,11 @@ async function run(ctx, argv) {
|
||||
localElapsedMs: t1 - t0,
|
||||
swarmPeers: swarm.peerCount
|
||||
}
|
||||
ctx.console.log(JSON.stringify(out, null, 2))
|
||||
if (send && send.ok === false) ctx.exitCode = 1
|
||||
if (opt.summary) {
|
||||
bareP2pPrint(ctx, { ok: out.ok, ms: out.localElapsedMs, peers: out.swarmPeers }, opt)
|
||||
} else {
|
||||
bareP2pPrint(ctx, out, opt)
|
||||
}
|
||||
if (send && send.ok === false) bareP2pError(ctx, argv0, String(out.sendReason || 'send failed'), 'swarmdoctor', opt)
|
||||
else bareP2pMaybeNext(ctx, opt, 'p2ptrace p2ping 20')
|
||||
}
|
||||
|
||||
+132
-30
@@ -99,6 +99,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -330,15 +438,18 @@ async function bareP2pReadProcJson(ctx, path) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'p2ptrace'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const args = parsed.rest
|
||||
const opt = parsed.opt
|
||||
if (opt.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.'
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'Dump recent p2p events as JSON lines.',
|
||||
argv0 + ' [APP] [N]',
|
||||
['swarmtop 20', 'meshdrop 50', '--summary'],
|
||||
['swarmtop', 'peerctl']
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -346,30 +457,21 @@ async function run(ctx, argv) {
|
||||
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'
|
||||
]
|
||||
: ['swarmtop','meshdrop','taskmesh','peernote','hypershell-board','p2ping','peerdiscover','peerctl']
|
||||
const rows = []
|
||||
for (const a of apps) {
|
||||
rows.push(...bareP2pCollectFromHistory(ctx, a, n))
|
||||
}
|
||||
for (const a of apps) rows.push(...bareP2pCollectFromHistory(ctx, a, n))
|
||||
rows.sort((a, b) => (a.receivedAtMs || 0) - (b.receivedAtMs || 0))
|
||||
if (opt.summary) {
|
||||
bareP2pPrint(ctx, { apps: apps.length, events: rows.length, lastAgeMs: rows.length ? Math.max(0, Date.now()-Number(rows[rows.length-1].receivedAtMs||Date.now())) : null }, opt)
|
||||
return
|
||||
}
|
||||
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 || {}
|
||||
})
|
||||
)
|
||||
bareP2pPrint(ctx, {
|
||||
atMs: r.receivedAtMs || 0,
|
||||
app: r.packet?.app || '?',
|
||||
kind: r.packet?.kind || '?',
|
||||
from: r.displayName || r.fromPeerKey || '',
|
||||
payload: r.packet?.payload || {}
|
||||
}, { ...opt, json: true })
|
||||
}
|
||||
}
|
||||
|
||||
+145
-33
@@ -99,6 +99,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -330,20 +438,18 @@ async function bareP2pReadProcJson(ctx, path) {
|
||||
|
||||
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) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const args = parsed.rest
|
||||
const opt = parsed.opt
|
||||
if (opt.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.'
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'P2P control plane helper over event envelopes.',
|
||||
argv0 + ' health | request ACTION [JSON_PAYLOAD] | history [N]',
|
||||
['health --summary', 'request restart {\"service\":\"swarm\"} --dry-run', 'history 20'],
|
||||
['swarmdoctor', 'p2ptrace']
|
||||
)
|
||||
)
|
||||
if (args.length === 0) ctx.exitCode = 1
|
||||
return
|
||||
@@ -352,25 +458,19 @@ async function run(ctx, argv) {
|
||||
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
|
||||
)
|
||||
)
|
||||
const o = {
|
||||
schema: 1,
|
||||
swarmPeers: swarm.peerCount,
|
||||
health: doctor.health || 'unknown',
|
||||
remediation: doctor.remediation || []
|
||||
}
|
||||
bareP2pPrint(ctx, opt.summary ? { health: o.health, peers: o.swarmPeers } : o, opt)
|
||||
return
|
||||
}
|
||||
if (sub === 'request') {
|
||||
const action = String(args[1] || '').trim()
|
||||
if (!action) {
|
||||
ctx.console.error(argv0 + ': request requires ACTION')
|
||||
ctx.exitCode = 1
|
||||
bareP2pError(ctx, argv0, 'request requires ACTION', argv0 + ' --help', opt)
|
||||
return
|
||||
}
|
||||
let payload = {}
|
||||
@@ -378,34 +478,46 @@ async function run(ctx, argv) {
|
||||
try {
|
||||
payload = JSON.parse(args.slice(2).join(' '))
|
||||
} catch {
|
||||
ctx.console.error(argv0 + ': invalid JSON payload')
|
||||
ctx.exitCode = 1
|
||||
bareP2pError(ctx, argv0, 'invalid JSON payload', argv0 + ' request ACTION {\"key\":\"value\"}', opt)
|
||||
return
|
||||
}
|
||||
}
|
||||
if (opt.dryRun) {
|
||||
bareP2pPrint(ctx, { ok: true, dryRun: true, action, payload }, opt)
|
||||
return
|
||||
}
|
||||
if (!opt.yes) {
|
||||
bareP2pPrint(ctx, 'confirmation required: pass --yes to send control request', opt)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const r = bareP2pSend(ctx, 'peerctl', 'request', {
|
||||
requestId: bareP2pId('ctl'),
|
||||
action,
|
||||
payload
|
||||
})
|
||||
if (r && r.ok === false) ctx.exitCode = 1
|
||||
if (r && r.ok === false) bareP2pError(ctx, argv0, String(r.reason || 'send failed'), 'swarmdoctor', opt)
|
||||
else bareP2pMaybeNext(ctx, opt, argv0 + ' history 20')
|
||||
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(
|
||||
bareP2pPrint(
|
||||
ctx,
|
||||
'[' +
|
||||
bareP2pFmtClock(row.receivedAtMs) +
|
||||
'] ' +
|
||||
String(row.packet?.kind || '?') +
|
||||
' ' +
|
||||
JSON.stringify(row.packet?.payload || {})
|
||||
,
|
||||
opt
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': unsupported subcommand')
|
||||
ctx.exitCode = 1
|
||||
const sug = bareP2pSuggestSubcommand(sub, ['health', 'request', 'history'])
|
||||
bareP2pError(ctx, argv0, 'unsupported subcommand' + (sug ? ' (did you mean ' + sug + '?)' : ''), argv0 + ' --help', opt)
|
||||
}
|
||||
|
||||
+129
-33
@@ -99,6 +99,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -330,17 +438,18 @@ async function bareP2pReadProcJson(ctx, path) {
|
||||
|
||||
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) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const args = parsed.rest
|
||||
const opt = parsed.opt
|
||||
if (opt.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.'
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'Peer service announce/list over p2p event history.',
|
||||
argv0 + ' announce SERVICE [META] | list [N]',
|
||||
['announce filesync "v1"', 'list 20 --summary'],
|
||||
['p2ptrace', 'swarmmap']
|
||||
)
|
||||
)
|
||||
if (args.length === 0) ctx.exitCode = 1
|
||||
return
|
||||
@@ -348,37 +457,24 @@ async function run(ctx, argv) {
|
||||
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
|
||||
}
|
||||
if (!service) return bareP2pError(ctx, argv0, 'announce requires SERVICE', argv0 + ' --help', opt)
|
||||
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
|
||||
if (opt.dryRun) return bareP2pPrint(ctx, { ok: true, dryRun: true, service, meta }, opt)
|
||||
const r = bareP2pSend(ctx, 'peerdiscover', 'service.announce', { service, meta, id: bareP2pId('svc') })
|
||||
if (r && r.ok === false) bareP2pError(ctx, argv0, String(r.reason || 'send failed'), 'swarmdoctor', opt)
|
||||
else bareP2pMaybeNext(ctx, opt, argv0 + ' list 20')
|
||||
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)
|
||||
const rows = bareP2pCollectFromHistory(ctx, 'peerdiscover', 2000).filter((r) => r.packet?.kind === 'service.announce').slice(-n)
|
||||
if (opt.summary) return bareP2pPrint(ctx, { services: rows.length }, opt)
|
||||
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) : '')
|
||||
)
|
||||
bareP2pPrint(ctx, '[' + bareP2pFmtClock(row.receivedAtMs) + '] ' + String(p.service || 'unknown') + (p.meta ? ' ' + String(p.meta) : ''), opt)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': unsupported subcommand')
|
||||
ctx.exitCode = 1
|
||||
const sug = bareP2pSuggestSubcommand(sub, ['announce', 'list'])
|
||||
bareP2pError(ctx, argv0, 'unsupported subcommand' + (sug ? ' (did you mean ' + sug + '?)' : ''), argv0 + ' --help', opt)
|
||||
}
|
||||
|
||||
+129
-26
@@ -99,6 +99,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -330,17 +438,18 @@ async function bareP2pReadProcJson(ctx, path) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'peernote'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const args = parsed.rest
|
||||
const opt = parsed.opt
|
||||
if (opt.help || args.length === 0) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
' add TEXT\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' list [N]\n' +
|
||||
'Shared p2p note stream over bare-p2p envelopes.\n' +
|
||||
'See man peernote.'
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'Shared p2p note stream over bare-p2p envelopes.',
|
||||
argv0 + ' add TEXT | list [N]',
|
||||
['add "hello cluster"', 'list 20 --summary'],
|
||||
['taskmesh', 'p2ptrace']
|
||||
)
|
||||
)
|
||||
if (args.length === 0) ctx.exitCode = 1
|
||||
return
|
||||
@@ -348,31 +457,25 @@ async function run(ctx, argv) {
|
||||
const sub = args[0]
|
||||
if (sub === 'add') {
|
||||
const text = args.slice(1).join(' ').trim()
|
||||
if (!text) {
|
||||
ctx.console.error(argv0 + ': add requires text')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const r = bareP2pSend(ctx, 'peernote', 'note.add', {
|
||||
noteId: bareP2pId('note'),
|
||||
text
|
||||
})
|
||||
if (r && r.ok === false) ctx.exitCode = 1
|
||||
if (!text) return bareP2pError(ctx, argv0, 'add requires text', argv0 + ' add "text"', opt)
|
||||
if (opt.dryRun) return bareP2pPrint(ctx, { ok: true, dryRun: true, text }, opt)
|
||||
const r = bareP2pSend(ctx, 'peernote', 'note.add', { noteId: bareP2pId('note'), text })
|
||||
if (r && r.ok === false) bareP2pError(ctx, argv0, String(r.reason || 'send failed'), 'swarmdoctor', opt)
|
||||
else bareP2pMaybeNext(ctx, opt, argv0 + ' list 20')
|
||||
return
|
||||
}
|
||||
if (sub === 'list') {
|
||||
const n = Math.max(1, Math.min(300, parseInt(args[1] || '30', 10) || 30))
|
||||
const rows = bareP2pCollectFromHistory(ctx, 'peernote', 2000)
|
||||
.filter((r) => r.packet?.kind === 'note.add')
|
||||
.slice(-n)
|
||||
const rows = bareP2pCollectFromHistory(ctx, 'peernote', 2000).filter((r) => r.packet?.kind === 'note.add').slice(-n)
|
||||
if (opt.summary) return bareP2pPrint(ctx, { notes: rows.length }, opt)
|
||||
for (const row of rows) {
|
||||
const p = row.packet?.payload || {}
|
||||
const text = typeof p.text === 'string' ? p.text : ''
|
||||
const who = row.displayName || row.fromPeerKey.slice(0, 10) || 'peer'
|
||||
ctx.console.log('[' + bareP2pFmtClock(row.receivedAtMs) + '] ' + who + ': ' + text)
|
||||
bareP2pPrint(ctx, '[' + bareP2pFmtClock(row.receivedAtMs) + '] ' + who + ': ' + text, opt)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': unsupported subcommand')
|
||||
ctx.exitCode = 1
|
||||
const sug = bareP2pSuggestSubcommand(sub, ['add', 'list'])
|
||||
bareP2pError(ctx, argv0, 'unsupported subcommand' + (sug ? ' (did you mean ' + sug + '?)' : ''), argv0 + ' --help', opt)
|
||||
}
|
||||
|
||||
+121
-5
@@ -570,6 +570,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -920,18 +1028,26 @@ async function bareP2pRunSimpleTui(ctx, opts) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'routeview'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const args = parsed.rest
|
||||
const opt = parsed.opt
|
||||
if (opt.help) {
|
||||
ctx.console.log(
|
||||
'usage: ' + argv0 + '\n' + 'Route and relay visibility TUI.\n' + 'See man routeview.'
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'Route and relay visibility TUI.',
|
||||
argv0 + ' [--summary] [--json]',
|
||||
['--summary', '--json'],
|
||||
['holepunch-view', 'swarmdoctor']
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
const stdin = ctx.replStdin
|
||||
const isTTY = Boolean(stdin && /** @type {{ isTTY?: boolean }} */ (stdin).isTTY)
|
||||
if (!isTTY) {
|
||||
if (!isTTY || opt.json || opt.summary) {
|
||||
const r = await bareP2pReadProcJson(ctx, '/proc/bare_os/route_summary.json')
|
||||
ctx.console.log(JSON.stringify(r, null, 2))
|
||||
bareP2pPrint(ctx, opt.summary ? { peers: r.peers ?? null, direct: r.directLikely ?? null, relay: r.relayLikely ?? null } : r, opt)
|
||||
return
|
||||
}
|
||||
await bareP2pRunSimpleTui(ctx, {
|
||||
|
||||
+120
-8
@@ -99,6 +99,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -330,20 +438,24 @@ async function bareP2pReadProcJson(ctx, path) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'swarmdoctor'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const opt = parsed.opt
|
||||
if (opt.help) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
'\n' +
|
||||
'Print swarm diagnosis from /proc/bare_os/swarm_doctor.json.\n' +
|
||||
'See man swarmdoctor.'
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'Print swarm diagnosis from /proc/bare_os/swarm_doctor.json.',
|
||||
argv0 + ' [--summary] [--json]',
|
||||
['--summary', '--json'],
|
||||
['swarmtop', 'peerctl']
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
const doc = await bareP2pReadProcJson(ctx, '/proc/bare_os/swarm_doctor.json')
|
||||
ctx.console.log(JSON.stringify(doc, null, 2))
|
||||
bareP2pPrint(ctx, opt.summary ? { health: doc.health || 'unknown', findings: (doc.findings && doc.findings.length) || 0 } : doc, opt)
|
||||
if (doc && typeof doc === 'object' && doc.health && doc.health !== 'ok') {
|
||||
if (!opt.quiet) ctx.console.error('hint: check routeview and holepunch-view for network posture')
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
|
||||
+120
-5
@@ -570,6 +570,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -920,18 +1028,25 @@ async function bareP2pRunSimpleTui(ctx, opts) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'swarmmap'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const opt = parsed.opt
|
||||
if (opt.help) {
|
||||
ctx.console.log(
|
||||
'usage: ' + argv0 + '\n' + 'Swarm topology map TUI.\n' + 'See man swarmmap.'
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'Swarm topology map TUI.',
|
||||
argv0 + ' [--summary] [--json]',
|
||||
['--summary', '--json'],
|
||||
['swarmtop', 'peerdiscover']
|
||||
)
|
||||
)
|
||||
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))
|
||||
if (!isTTY || opt.json || opt.summary) {
|
||||
bareP2pPrint(ctx, opt.summary ? { peers: snap.peerCount, topics: snap.topicCount } : snap, opt)
|
||||
return
|
||||
}
|
||||
await bareP2pRunSimpleTui(ctx, {
|
||||
|
||||
+167
-20
@@ -570,6 +570,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -920,36 +1028,46 @@ async function bareP2pRunSimpleTui(ctx, opts) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'swarmtop'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help')) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const args = parsed.rest
|
||||
const opt = parsed.opt
|
||||
if (opt.help) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
' [watch | events [N] | ping [LABEL]]\n' +
|
||||
'P2P fleet dashboard over Bare OS chat + /proc swarm data.\n' +
|
||||
'No args on a TTY opens full-screen swarmtop.\n' +
|
||||
'See man swarmtop.'
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'P2P fleet dashboard over Bare OS chat + /proc swarm data.',
|
||||
argv0 + ' [watch | events [N] | ping [LABEL]]',
|
||||
['ping hello', 'events 20', 'watch --json'],
|
||||
['swarmdoctor', 'swarmmap', 'p2ptrace']
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
bareP2pFirstRunHint(ctx, 'swarmtop', "run 'swarmdoctor' when peer rows look incomplete")
|
||||
|
||||
const sub = args[0] || ''
|
||||
if (sub === 'ping') {
|
||||
const label = args.slice(1).join(' ').trim() || 'hello'
|
||||
if (opt.dryRun) {
|
||||
bareP2pPrint(ctx, { ok: true, dryRun: true, action: 'ping', label }, opt)
|
||||
return
|
||||
}
|
||||
const r = bareP2pSend(ctx, 'swarmtop', 'ping', {
|
||||
id: bareP2pId('ping'),
|
||||
label
|
||||
})
|
||||
if (r && r.ok === false) ctx.exitCode = 1
|
||||
if (r && r.ok === false) bareP2pError(ctx, argv0, String(r.reason || 'send failed'), 'swarmdoctor', opt)
|
||||
else bareP2pMaybeNext(ctx, opt, 'swarmtop events 20')
|
||||
return
|
||||
}
|
||||
if (sub === 'events') {
|
||||
const n = Math.max(1, Math.min(2000, parseInt(args[1] || '30', 10) || 30))
|
||||
const rows = bareP2pCollectFromHistory(ctx, 'swarmtop', n)
|
||||
const out = []
|
||||
for (const row of rows.slice(-n)) {
|
||||
const pkt = row.packet || {}
|
||||
const payload = pkt.payload && typeof pkt.payload === 'object' ? pkt.payload : {}
|
||||
ctx.console.log(
|
||||
out.push(
|
||||
'[' +
|
||||
bareP2pFmtClock(row.receivedAtMs) +
|
||||
'] ' +
|
||||
@@ -958,6 +1076,12 @@ async function run(ctx, argv) {
|
||||
JSON.stringify(payload)
|
||||
)
|
||||
}
|
||||
if (!out.length) out.push('(none)')
|
||||
if (opt.summary) {
|
||||
bareP2pPrint(ctx, { app: 'swarmtop', events: out.length }, opt)
|
||||
return
|
||||
}
|
||||
for (const ln of out) bareP2pPrint(ctx, ln, opt)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1005,6 +1129,7 @@ async function run(ctx, argv) {
|
||||
lines.push('Peers:')
|
||||
const peers = Array.isArray(snap.peers) ? snap.peers : []
|
||||
if (!peers.length) lines.push(' (no peers reported)')
|
||||
let unknownCount = 0
|
||||
for (const p of peers.slice(0, 12)) {
|
||||
const key =
|
||||
p && typeof p === 'object' && typeof p.remotePublicKey === 'string'
|
||||
@@ -1014,12 +1139,19 @@ async function run(ctx, argv) {
|
||||
p && typeof p === 'object' && typeof p.state === 'string'
|
||||
? p.state
|
||||
: 'connected'
|
||||
const addr =
|
||||
const endpointObj =
|
||||
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 || '?')
|
||||
? p.endpoint
|
||||
: null
|
||||
const addrRaw = String(
|
||||
(endpointObj && endpointObj.address) ||
|
||||
(p && typeof p === 'object' ? p.remoteAddress || '' : '')
|
||||
).trim()
|
||||
const portRaw = String(
|
||||
(endpointObj && endpointObj.port) ||
|
||||
(p && typeof p === 'object' ? p.remotePort || '' : '')
|
||||
).trim()
|
||||
const addr = addrRaw && portRaw ? addrRaw + ':' + portRaw : 'unavailable'
|
||||
const osHint =
|
||||
p &&
|
||||
typeof p === 'object' &&
|
||||
@@ -1028,12 +1160,27 @@ async function run(ctx, argv) {
|
||||
p.peerOs.osHint
|
||||
? String(p.peerOs.osHint)
|
||||
: 'unknown'
|
||||
const osConfidence = osHint === 'unknown' ? 'unknown' : 'reported'
|
||||
if (addr === 'unavailable' || osHint === 'unknown') unknownCount++
|
||||
lines.push(' pubkey: ' + key)
|
||||
lines.push(' state=' + state + ' endpoint=' + addr + ' os=' + osHint)
|
||||
lines.push(
|
||||
' state=' +
|
||||
state +
|
||||
' endpoint=' +
|
||||
addr +
|
||||
' os=' +
|
||||
osHint +
|
||||
' osConfidence=' +
|
||||
osConfidence
|
||||
)
|
||||
}
|
||||
lines.push(' peerData diagnostics: incomplete=' + unknownCount + '/' + peers.length)
|
||||
lines.push('')
|
||||
lines.push('Recent events:')
|
||||
if (!events.length) lines.push(' (none)')
|
||||
if (!events.length) {
|
||||
lines.push(' (none)')
|
||||
lines.push(' event ingest: no app events seen yet; try r, then swarmtop ping hello')
|
||||
}
|
||||
for (const ev of events) {
|
||||
lines.push(
|
||||
' [' +
|
||||
@@ -1050,10 +1197,10 @@ async function run(ctx, argv) {
|
||||
|
||||
if (sub === 'watch') {
|
||||
const snap = await bareP2pReadSwarmSnapshot(ctx)
|
||||
ctx.console.log(JSON.stringify(snap, null, 2))
|
||||
bareP2pPrint(ctx, snap, opt)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.console.error(argv0 + ': unsupported subcommand')
|
||||
ctx.exitCode = 1
|
||||
const sug = bareP2pSuggestSubcommand(sub, ['watch', 'events', 'ping'])
|
||||
bareP2pError(ctx, argv0, 'unsupported subcommand' + (sug ? ' (did you mean ' + sug + '?)' : ''), argv0 + ' --help', opt)
|
||||
}
|
||||
|
||||
+137
-38
@@ -99,6 +99,114 @@ function bareP2pId(prefix) {
|
||||
return prefix + '-' + bareP2pNowMs().toString(36) + '-' + rnd.toString(36)
|
||||
}
|
||||
|
||||
function bareP2pCommonExamples(argv0, rows) {
|
||||
const out = []
|
||||
for (const r of rows || []) out.push(' ' + argv0 + ' ' + r)
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pHelpText(argv0, summary, usage, examples, seeAlso) {
|
||||
const lines = []
|
||||
lines.push('usage: ' + usage)
|
||||
lines.push(summary)
|
||||
lines.push('')
|
||||
lines.push('common options: --help --json --quiet --summary --timeout MS --no-color')
|
||||
if (examples && examples.length) {
|
||||
lines.push('')
|
||||
lines.push('examples:')
|
||||
lines.push(bareP2pCommonExamples(argv0, examples))
|
||||
}
|
||||
if (seeAlso && seeAlso.length) {
|
||||
lines.push('')
|
||||
lines.push('see also: ' + seeAlso.join(', '))
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('troubleshooting: if peer data is empty, run `swarmtop` then `swarmdoctor`.')
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
function bareP2pParseCommonFlags(args) {
|
||||
const rest = []
|
||||
const opt = {
|
||||
help: false,
|
||||
json: false,
|
||||
quiet: false,
|
||||
summary: false,
|
||||
timeoutMs: 4000,
|
||||
noColor: false,
|
||||
dryRun: false,
|
||||
yes: false
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const a = String(args[i] || '')
|
||||
if (a === '-h' || a === '--help') opt.help = true
|
||||
else if (a === '--json') opt.json = true
|
||||
else if (a === '--quiet') opt.quiet = true
|
||||
else if (a === '--summary') opt.summary = true
|
||||
else if (a === '--dry-run') opt.dryRun = true
|
||||
else if (a === '--yes' || a === '-y') opt.yes = true
|
||||
else if (a === '--no-color') opt.noColor = true
|
||||
else if (a === '--timeout') {
|
||||
const n = parseInt(args[i + 1] || '4000', 10)
|
||||
if (Number.isFinite(n)) opt.timeoutMs = Math.max(250, Math.min(120000, n))
|
||||
i++
|
||||
} else rest.push(a)
|
||||
}
|
||||
return { opt, rest }
|
||||
}
|
||||
|
||||
function bareP2pPrint(ctx, data, opt) {
|
||||
if (opt && opt.quiet) return
|
||||
if (opt && (opt.json || typeof data !== 'string')) {
|
||||
ctx.console.log(typeof data === 'string' ? JSON.stringify({ message: data }) : JSON.stringify(data, null, 2))
|
||||
return
|
||||
}
|
||||
ctx.console.log(String(data))
|
||||
}
|
||||
|
||||
function bareP2pError(ctx, argv0, msg, next, opt) {
|
||||
if (opt && opt.quiet) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.error(argv0 + ': ' + msg + (next ? ' · try: ' + next : ''))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
function bareP2pSuggestSubcommand(sub, known) {
|
||||
const s = String(sub || '')
|
||||
if (!s) return ''
|
||||
let best = ''
|
||||
let bestScore = 1e9
|
||||
for (const k of known || []) {
|
||||
const kk = String(k || '')
|
||||
const d = Math.abs(kk.length - s.length) + (kk[0] === s[0] ? 0 : 2)
|
||||
if (d < bestScore) {
|
||||
bestScore = d
|
||||
best = kk
|
||||
}
|
||||
}
|
||||
return bestScore <= 4 ? best : ''
|
||||
}
|
||||
|
||||
function bareP2pFirstRunHint(ctx, app, hint) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.BARE_P2P_NO_HINTS === '1' || env.BARE_P2P_NO_HINTS === 'true') return
|
||||
const key = '__bare_p2p_hint_' + app
|
||||
if (ctx[key]) return
|
||||
ctx[key] = true
|
||||
try {
|
||||
ctx.console.log('hint: ' + hint)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function bareP2pMaybeNext(ctx, opt, next) {
|
||||
if (opt && opt.quiet) return
|
||||
if (next) ctx.console.log('next: ' + next)
|
||||
}
|
||||
|
||||
function bareP2pHome(ctx) {
|
||||
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
const home = typeof env.HOME === 'string' && env.HOME ? env.HOME : '/home/guest'
|
||||
@@ -330,20 +438,18 @@ async function bareP2pReadProcJson(ctx, path) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const argv0 = argv[0] || 'taskmesh'
|
||||
const args = argv.slice(1)
|
||||
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
|
||||
const parsed = bareP2pParseCommonFlags(argv.slice(1))
|
||||
const args = parsed.rest
|
||||
const opt = parsed.opt
|
||||
if (opt.help || args.length === 0) {
|
||||
ctx.console.log(
|
||||
'usage: ' +
|
||||
argv0 +
|
||||
' add TEXT\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' done TASK_ID\n' +
|
||||
' ' +
|
||||
argv0 +
|
||||
' list [open|all]\n' +
|
||||
'P2P task board over append-only bare-p2p events.\n' +
|
||||
'See man taskmesh.'
|
||||
bareP2pHelpText(
|
||||
argv0,
|
||||
'P2P task board over append-only bare-p2p events.',
|
||||
argv0 + ' add TEXT | done TASK_ID | list [open|all]',
|
||||
['add "ship release"', 'list all', 'done task-abc'],
|
||||
['peernote', 'p2ptrace']
|
||||
)
|
||||
)
|
||||
if (args.length === 0) ctx.exitCode = 1
|
||||
return
|
||||
@@ -352,63 +458,56 @@ async function run(ctx, argv) {
|
||||
const sub = args[0]
|
||||
if (sub === 'add') {
|
||||
const text = args.slice(1).join(' ').trim()
|
||||
if (!text) {
|
||||
ctx.console.error(argv0 + ': add requires text')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
if (!text) return bareP2pError(ctx, argv0, 'add requires text', argv0 + ' add "text"', opt)
|
||||
const taskId = bareP2pId('task')
|
||||
const r = bareP2pSend(ctx, 'taskmesh', 'task.add', {
|
||||
taskId,
|
||||
text
|
||||
})
|
||||
if (r && r.ok === false) ctx.exitCode = 1
|
||||
else ctx.console.log('added ' + taskId)
|
||||
if (opt.dryRun) return bareP2pPrint(ctx, { ok: true, dryRun: true, taskId, text }, opt)
|
||||
const r = bareP2pSend(ctx, 'taskmesh', 'task.add', { taskId, text })
|
||||
if (r && r.ok === false) bareP2pError(ctx, argv0, String(r.reason || 'send failed'), 'swarmdoctor', opt)
|
||||
else {
|
||||
bareP2pPrint(ctx, 'added ' + taskId, opt)
|
||||
bareP2pMaybeNext(ctx, opt, argv0 + ' list')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (sub === 'done') {
|
||||
const taskId = String(args[1] || '').trim()
|
||||
if (!taskId) {
|
||||
ctx.console.error(argv0 + ': done requires TASK_ID')
|
||||
if (!taskId) return bareP2pError(ctx, argv0, 'done requires TASK_ID', argv0 + ' list', opt)
|
||||
if (!opt.yes) {
|
||||
bareP2pPrint(ctx, 'confirmation required: pass --yes to mark done', opt)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const r = bareP2pSend(ctx, 'taskmesh', 'task.done', { taskId })
|
||||
if (r && r.ok === false) ctx.exitCode = 1
|
||||
else ctx.console.log('completed ' + taskId)
|
||||
if (r && r.ok === false) bareP2pError(ctx, argv0, String(r.reason || 'send failed'), 'swarmdoctor', opt)
|
||||
else bareP2pPrint(ctx, 'completed ' + taskId, opt)
|
||||
return
|
||||
}
|
||||
|
||||
if (sub === 'list') {
|
||||
const mode = args[1] === 'all' ? 'all' : 'open'
|
||||
const rows = bareP2pCollectFromHistory(ctx, 'taskmesh', 2000)
|
||||
/** @type {Map<string, { taskId: string, text: string, done: boolean, atMs: number }>} */
|
||||
const board = new Map()
|
||||
for (const row of rows) {
|
||||
const kind = row.packet?.kind
|
||||
const p = row.packet?.payload
|
||||
if (!p || typeof p !== 'object') continue
|
||||
if (kind === 'task.add' && typeof p.taskId === 'string') {
|
||||
board.set(p.taskId, {
|
||||
taskId: p.taskId,
|
||||
text: typeof p.text === 'string' ? p.text : '',
|
||||
done: false,
|
||||
atMs: row.receivedAtMs
|
||||
})
|
||||
board.set(p.taskId, { taskId: p.taskId, text: typeof p.text === 'string' ? p.text : '', done: false, atMs: row.receivedAtMs })
|
||||
} else if (kind === 'task.done' && typeof p.taskId === 'string') {
|
||||
const cur = board.get(p.taskId)
|
||||
if (cur) cur.done = true
|
||||
}
|
||||
}
|
||||
const all = [...board.values()].sort((a, b) => a.atMs - b.atMs)
|
||||
if (opt.summary) return bareP2pPrint(ctx, { tasks: all.length, open: all.filter((x)=>!x.done).length }, opt)
|
||||
for (const t of all) {
|
||||
if (mode !== 'all' && t.done) continue
|
||||
ctx.console.log((t.done ? '[x] ' : '[ ] ') + t.taskId + ' ' + t.text)
|
||||
bareP2pPrint(ctx, (t.done ? '[x] ' : '[ ] ') + t.taskId + ' ' + t.text, opt)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.console.error(argv0 + ': unsupported subcommand')
|
||||
ctx.exitCode = 1
|
||||
const sug = bareP2pSuggestSubcommand(sub, ['add', 'done', 'list'])
|
||||
bareP2pError(ctx, argv0, 'unsupported subcommand' + (sug ? ' (did you mean ' + sug + '?)' : ''), argv0 + ' --help', opt)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema": 2,
|
||||
"profileId": "bare-os-posix-like",
|
||||
"generatedAt": "2026-04-26T12:27:28.060Z",
|
||||
"generatedAt": "2026-04-26T12:40:06.854Z",
|
||||
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
|
||||
"commandIndex": [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"atMs": 1777206448060,
|
||||
"atMs": 1777207206853,
|
||||
"commands": [
|
||||
"agent",
|
||||
"arch",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user