// The operator's CLI, reached as `ap-bridge admin …`.
//
// ⛔ A SUBCOMMAND, NOT A SECOND BINARY. pgh installs exactly one launcher per app — `pgh.bin` is a
// single name, and schema.js rejects anything else — and that launcher is `exec … index.js "$@"`, so
// arguments already pass through. Shipping a second bin/ would have been a file nobody could install:
// it exists in the tree, is absent from PATH, and the documented command silently is not there.
//
// ⛔ OVER THE UNIX SOCKET, which is the gate. Only a local process that can open the socket path may
// call this, which is the fleet's pattern for an operator surface — pgv3's handlers gate on
// `ctx.transport`, and hyper-proto-peer's UDS surface is "TRUSTED WITHOUT the writer-allowlist" for
// the same reason.
//
// ⛔ WHY IT EXISTS. Lose every operator browser — cleared storage, a dead laptop — and the web surface
// is shut for good: the bootstrap token gates the FIRST claim only, and the instance never reopens,
// not even when the claimant is revoked. Without this the recovery is deleting the identity store,
// which throws away every operator grant in order to get one back.
import { Client } from 'hyper-svc'
export const USAGE = `ap-bridge admin <command>
status who operates this bridge
invite mint a single-use invite for another browser
revoke <pubkey> retire an operator browser
The bridge must be running with AP_ADMIN_PORT set — the control socket comes up beside the
admin surface. Only a process on this machine that can open the socket may call it.`
/**
* @returns {Promise<number>} the process exit code — this function never exits on its own, so it
* stays testable and a caller keeps control of the process.
*/
export async function runAdminCli (argv, { uds, out = console.log, err = console.error, connect = null } = {}) {
const cmd = argv[0]
if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') { out(USAGE); return 0 }
const client = connect
? await connect(uds).catch(() => null)
: await Client.discover({ uds, prefer: 'uds' }).catch(() => null)
if (!client) {
err('no control socket at ' + uds)
err('Is the bridge running, and is AP_ADMIN_PORT set? The control socket comes up with the admin surface.')
return 1
}
try {
if (cmd === 'status') {
const s = await client.call('admin-status', {})
out(s.unclaimed
? 'UNCLAIMED — claim it with the bootstrap token printed in the service log'
: 'claimed')
if (!s.operators.length) out(' no operator browsers')
for (const o of s.operators) out(' ' + o.publicKey + (o.revoked ? ' (revoked)' : ''))
return 0
}
if (cmd === 'invite') {
const { token, expiresAt } = await client.call('admin-invite', {})
// ⚠ The token alone on stdout, everything else on stderr, so `… invite | pbcopy` pipes a token
// and not a paragraph.
out(token)
err('single-use' + (expiresAt ? ', expires ' + new Date(expiresAt).toISOString() : '') +
' — paste it into the Invite field on the sign-in page')
return 0
}
if (cmd === 'revoke') {
if (!argv[1]) { err('usage: ap-bridge admin revoke <publicKey>'); return 1 }
await client.call('admin-revoke', { publicKey: argv[1] })
out('revoked ' + argv[1])
return 0
}
err(USAGE)
return 1
} catch (e) {
err((e && e.message) || String(e))
return 1
} finally {
try { await client.close() } catch {}
}
}
export default { runAdminCli, USAGE }