feat: add pearcord-agentctl agent control plane (v0.1.0)
TCP server and headless session for driving Pearcord via the same IPC surface as the UI, with CLI, view assertions, and log tail support. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Executable
+130
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env bare
|
||||
'use strict'
|
||||
|
||||
const fs = require('bare-fs')
|
||||
const path = require('bare-path')
|
||||
const { AgentClient, HeadlessSession, 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 log-tail [--lines N] [--host HOST] [--port PORT]
|
||||
agentctl headless run <scenario.json>
|
||||
agentctl headless ipc '<json>'
|
||||
|
||||
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_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]
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
throw new Error(`unknown command: ${cmd}`)
|
||||
} finally {
|
||||
client.close()
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err?.message || err)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user