HeadlessSession supports waitEvent steps and optional hyperswarm DHT; README lists leave-voice-noop and settings-deep-link scenarios with smoke commands. Co-authored-by: Cursor <[email protected]>
185 lines
5.7 KiB
JavaScript
Executable File
185 lines
5.7 KiB
JavaScript
Executable File
#!/usr/bin/env bare
|
|
'use strict'
|
|
|
|
const fs = require('bare-fs')
|
|
const path = require('bare-path')
|
|
const {
|
|
AgentClient,
|
|
HeadlessSession,
|
|
PearcordMeshCluster,
|
|
resolveAgentctlAddress
|
|
} = require('..')
|
|
|
|
function usage () {
|
|
console.log(`pearcord-agentctl — control a running Pearcord worker or run headless IPC
|
|
|
|
Usage:
|
|
agentctl ping [--host HOST] [--port PORT]
|
|
agentctl view [--light] [--host HOST] [--port PORT]
|
|
agentctl ipc '<json>' [--host HOST] [--port PORT]
|
|
agentctl wait '<match-json>' [--timeout MS] [--host HOST] [--port PORT]
|
|
agentctl wait-event '<match-json>' [--timeout MS] [--since MS]
|
|
agentctl log-tail [--lines N] [--host HOST] [--port PORT]
|
|
agentctl run <scenario.json> [--timeout MS] [--host HOST] [--port PORT]
|
|
agentctl headless run <scenario.json>
|
|
agentctl headless mesh run <scenario.json> [--peers N]
|
|
agentctl headless ipc '<json>'
|
|
agentctl mesh run <scenario.json> [--peers N]
|
|
|
|
Environment:
|
|
PEARCORD_AGENTCTL=1 Enable TCP server in apps/pearcord worker
|
|
PEARCORD_AGENTCTL_HOST Default 127.0.0.1
|
|
PEARCORD_AGENTCTL_PORT Default 39482
|
|
PEARCORD_AGENTCTL_TOKEN Optional shared secret for TCP ops
|
|
PEARCORD_STORAGE Storage root (~/.config/pearcord)
|
|
|
|
Start Pearcord with agentctl:
|
|
PEARCORD_AGENTCTL=1 pear run
|
|
`)
|
|
}
|
|
|
|
function parseArgs (argv) {
|
|
const out = { _: [], host: null, port: null, light: false, timeout: 15000, lines: 40 }
|
|
for (let i = 2; i < argv.length; i++) {
|
|
const a = argv[i]
|
|
if (a === '--host') out.host = argv[++i]
|
|
else if (a === '--port') out.port = Number(argv[++i])
|
|
else if (a === '--light') out.light = true
|
|
else if (a === '--timeout') out.timeout = Number(argv[++i])
|
|
else if (a === '--lines') out.lines = Number(argv[++i])
|
|
else if (a === '-h' || a === '--help') out.help = true
|
|
else out._.push(a)
|
|
}
|
|
return out
|
|
}
|
|
|
|
function clientFromArgs (args) {
|
|
const def = resolveAgentctlAddress()
|
|
return new AgentClient({
|
|
host: args.host || def.host,
|
|
port: args.port || def.port
|
|
})
|
|
}
|
|
|
|
async function main () {
|
|
const args = parseArgs(process.argv)
|
|
if (args.help || !args._.length) {
|
|
usage()
|
|
process.exit(args.help ? 0 : 1)
|
|
}
|
|
const cmd = args._[0]
|
|
|
|
if (cmd === 'headless') {
|
|
const sub = args._[1]
|
|
if (sub === 'mesh') {
|
|
const meshSub = args._[2]
|
|
if (meshSub !== 'run') throw new Error('use: headless mesh run <scenario.json>')
|
|
const file = args._[3]
|
|
if (!file) throw new Error('scenario.json path required')
|
|
const cluster = new PearcordMeshCluster({ peerCount: 3 })
|
|
try {
|
|
const result = await cluster.runScenario(path.resolve(file))
|
|
console.log(JSON.stringify({ ok: true, result }, null, 2))
|
|
} finally {
|
|
await cluster.close()
|
|
}
|
|
return
|
|
}
|
|
const session = new HeadlessSession()
|
|
await session.start()
|
|
try {
|
|
if (sub === 'run') {
|
|
const file = args._[2]
|
|
if (!file) throw new Error('scenario.json path required')
|
|
const results = await session.runScenario(path.resolve(file))
|
|
console.log(JSON.stringify({ ok: true, steps: results.length, view: session.lastView }, null, 2))
|
|
return
|
|
}
|
|
if (sub === 'ipc') {
|
|
const raw = args._[2]
|
|
if (!raw) throw new Error('ipc json required')
|
|
const payload = JSON.parse(raw)
|
|
const view = await session.ipc(payload)
|
|
console.log(JSON.stringify({ ok: true, view }, null, 2))
|
|
return
|
|
}
|
|
throw new Error(`unknown headless subcommand: ${sub}`)
|
|
} finally {
|
|
await session.close()
|
|
}
|
|
}
|
|
|
|
if (cmd === 'mesh') {
|
|
const meshSub = args._[1]
|
|
if (meshSub !== 'run') throw new Error('use: mesh run <scenario.json>')
|
|
const file = args._[2]
|
|
if (!file) throw new Error('scenario.json path required')
|
|
const cluster = new PearcordMeshCluster({ peerCount: 3 })
|
|
try {
|
|
const result = await cluster.runScenario(path.resolve(file))
|
|
console.log(JSON.stringify({ ok: true, result }, null, 2))
|
|
} finally {
|
|
await cluster.close()
|
|
}
|
|
return
|
|
}
|
|
|
|
const client = clientFromArgs(args)
|
|
try {
|
|
if (cmd === 'ping') {
|
|
const res = await client.ping()
|
|
console.log(JSON.stringify(res, null, 2))
|
|
return
|
|
}
|
|
if (cmd === 'view') {
|
|
const view = await client.view(args.light)
|
|
console.log(JSON.stringify({ ok: true, view }, null, 2))
|
|
return
|
|
}
|
|
if (cmd === 'ipc') {
|
|
const raw = args._[1]
|
|
if (!raw) throw new Error('ipc json required')
|
|
const payload = JSON.parse(raw)
|
|
const res = await client.ipc(payload)
|
|
console.log(JSON.stringify(res, null, 2))
|
|
return
|
|
}
|
|
if (cmd === 'wait') {
|
|
const raw = args._[1]
|
|
if (!raw) throw new Error('match json required')
|
|
const match = JSON.parse(raw)
|
|
const view = await client.wait(match, args.timeout, args.light)
|
|
console.log(JSON.stringify({ ok: true, view }, null, 2))
|
|
return
|
|
}
|
|
if (cmd === 'log-tail') {
|
|
const res = await client.logTail(args.lines)
|
|
console.log(JSON.stringify(res, null, 2))
|
|
return
|
|
}
|
|
if (cmd === 'wait-event') {
|
|
const raw = args._[1]
|
|
if (!raw) throw new Error('match json required')
|
|
const match = JSON.parse(raw)
|
|
const event = await client.waitEvent(match, args.timeout, 0)
|
|
console.log(JSON.stringify({ ok: true, event }, null, 2))
|
|
return
|
|
}
|
|
if (cmd === 'run') {
|
|
const file = args._[1]
|
|
if (!file) throw new Error('scenario.json path required')
|
|
const res = await client.run(path.resolve(file), args.timeout || 120000)
|
|
console.log(JSON.stringify(res, null, 2))
|
|
return
|
|
}
|
|
throw new Error(`unknown command: ${cmd}`)
|
|
} finally {
|
|
client.close()
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err?.message || err)
|
|
process.exit(1)
|
|
})
|