Updates to tools

This commit is contained in:
Raven Scott
2026-04-26 08:40:53 -04:00
parent 67743621a5
commit 6f90952611
52 changed files with 4452 additions and 1134 deletions
+121 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
{
"schema": 1,
"atMs": 1777206448060,
"atMs": 1777207206853,
"commands": [
"agent",
"arch",
File diff suppressed because one or more lines are too long