All checks were successful
PR Checks / plugin-checks (pull_request) Successful in -1m44s
The repository RunicNPC is built in (docs/runicnpc/PLAN.md §9, stage 0): - plugin/RunicNPC.cs: `// Requires: Kits` (D217), `[Info]` with the 0.0.0 placeholder the release stamps, `RunicNpc_ApiVersion()` (API 1), and `rnpc.status`, which reports the version and which hooks have fired. It spawns nothing. - plugin.toml: the API version, the framework floors it was loaded on (Oxide 2.0.7726, Carbon 2.0.259) and requires_plugins = ["Kits"]. - scripts/checkPlugin.js, adapted from Rust-Plugins': every hook listed and void unless written down; chat-command signatures; every RunicNpc_ call reachable by Call (the HumanNPC trap, PLAN.md §1.2); ApiVersion, `// Requires:` and [Info] agreeing with plugin.toml. 23 self-tests, including the real plugin and a CRLF checkout. - PR Checks on PRs into main and edge; the release workflow on main, with Rust-Plugins' release engine unchanged and an adapter that ships runicnpc-<ver>.tar.gz (runicnpc/RunicNPC.cs + manifest.json) and SHA256SUMS. No bundle dispatch until stage 4. - tools/: the rig panel scripts, with the panel and server ids moved into a git-ignored tools/rigs.json. `con.js` became `console.js`: CON is a reserved device name on Windows, and git there cannot open the file. - README, CONTRIBUTING (edge-based flow, AI disclosure, borrow-not-copy), SECURITY, the code of conduct, issue and PR templates. `feat:` so the cutover to main cuts the first release, 0.1.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
61 lines
2.1 KiB
JavaScript
61 lines
2.1 KiB
JavaScript
// Runs one console command on a rig through the panel's websocket and prints the console lines
|
|
// that follow — the way to read a command's reply, which rig.js's `cmd` cannot.
|
|
//
|
|
// node tools/console.js <rig> "<command>" [seconds=6] [filter]
|
|
//
|
|
// node tools/console.js oxide "oxide.plugins" 6 RunicNPC
|
|
// node tools/console.js carbon "rnpc.status"
|
|
//
|
|
// Needs Node 22+ for the global WebSocket.
|
|
|
|
const { load, serverId, unmsys } = require('./panel')
|
|
|
|
const config = load()
|
|
const [rig, command, secs = '6', filter] = process.argv.slice(2).map(unmsys)
|
|
const strip = (s) => s.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '')
|
|
|
|
async function main() {
|
|
if (!command) {
|
|
console.error('usage: node tools/console.js <rig> "<command>" [seconds=6] [filter]')
|
|
process.exit(2)
|
|
}
|
|
|
|
const r = await fetch(`${config.panel}/api/client/servers/${serverId(config, rig)}/websocket`, {
|
|
headers: { Authorization: 'Bearer ' + config.key, Accept: 'application/json' },
|
|
})
|
|
if (!r.ok) {
|
|
console.error(r.status, await r.text())
|
|
process.exit(1)
|
|
}
|
|
const { data } = await r.json()
|
|
|
|
const ws = new WebSocket(data.socket, { headers: { Origin: config.panel } })
|
|
let sent = false
|
|
|
|
ws.onopen = () => ws.send(JSON.stringify({ event: 'auth', args: [data.token] }))
|
|
ws.onmessage = (m) => {
|
|
const msg = JSON.parse(m.data)
|
|
if (msg.event === 'auth success' && !sent) {
|
|
sent = true
|
|
// The panel replays recent history on connect; let it pass, then send.
|
|
setTimeout(() => ws.send(JSON.stringify({ event: 'send command', args: [command] })), 800)
|
|
setTimeout(() => {
|
|
ws.close()
|
|
process.exit(0)
|
|
}, Number(secs) * 1000)
|
|
} else if (msg.event === 'console output' && sent) {
|
|
for (const line of strip(String(msg.args[0])).split('\n')) {
|
|
if (!filter || line.toLowerCase().includes(filter.toLowerCase())) console.log(line)
|
|
}
|
|
} else if (msg.event === 'jwt error' || msg.event === 'token expired') {
|
|
console.error('ws:', msg.event, msg.args)
|
|
}
|
|
}
|
|
ws.onerror = (e) => console.error('ws error', e.message || e)
|
|
}
|
|
|
|
main().catch((e) => {
|
|
console.error(e.message || e)
|
|
process.exit(1)
|
|
})
|