/**
* pgh user — manage users on a pgh-managed app.
*
* Subcommands:
* pgh user list --app <name>
* pgh user promote <identity-hex> --app <name>
* pgh user revoke <identity-hex> --app <name>
*
* Dispatches via pgh-sidecar's app registry: looks up the running app's
* UDS by name, then calls the app's RPCs (list-users / grant-permission /
* revoke-identity). The app must be running and registered with sidecar.
*/
import process from '#node:process'
import { isAbstract } from '../daemon-client.js'
import fs from '#node:fs'
import path from '#node:path'
import { homedir } from '#node:os'
const SIDECAR_SOCK = process.env.PGH_SIDECAR_SOCK || path.join(homedir(), '.pgh', 'svc.sock')
export const usage = `pgh user — manage users on a pgh-managed app
pgh user list --app <name>
pgh user promote <identity-hex> --app <name>
pgh user revoke <identity-hex> --app <name>
The app must be running and registered with pgh-sidecar.`
async function appClient (appName) {
if (!isAbstract(SIDECAR_SOCK) && !fs.existsSync(SIDECAR_SOCK)) {
throw new Error('pgh-sidecar is not running (no socket at ' + SIDECAR_SOCK + ')')
}
const { Client } = await import('hyper-svc')
const sc = await Client.connectUds(SIDECAR_SOCK)
let entry
try {
entry = await sc.call('app-lookup', { name: appName })
} finally {
try { await sc.close() } catch {}
}
if (!entry || !entry.found) {
throw new Error(`app '${appName}' not registered with pgh-sidecar (is it running?)`)
}
return Client.connectUds(entry.udsPath)
}
function parseAppFlag (args) {
const i = args.indexOf('--app')
if (i === -1 || !args[i + 1]) {
throw new Error('--app <name> is required (the pgh-managed app to talk to)')
}
return args[i + 1]
}
export async function run (_client, args /*, globals */) {
const sub = args[0]
if (!sub || sub === 'help') {
return [
'usage: pgh user <subcommand> [options]',
' list List users (--app required)',
' promote <identity-hex> Grant admin globally',
' revoke <identity-hex> Revoke a linked identity',
'',
'Common: --app <name> The pgh-managed app to talk to (e.g. pear-git-v3)'
].join('\n')
}
if (sub === 'list') {
const app = parseAppFlag(args)
const c = await appClient(app)
try {
const users = await c.call('list-users', {})
return users
} finally { try { await c.close() } catch {} }
}
if (sub === 'promote') {
const identity = args[1]
if (!identity || identity.length !== 64) {
throw new Error('usage: pgh user promote <identity-hex> --app <name>')
}
const app = parseAppFlag(args)
const c = await appClient(app)
try {
const updated = await c.call('grant-permission', {
identity,
permissions: ['admin']
})
return {
promoted: identity,
userId: updated.userId,
name: updated.name || null,
globalPermissions: updated.globalPermissions || []
}
} finally { try { await c.close() } catch {} }
}
if (sub === 'revoke') {
const identity = args[1]
if (!identity || identity.length !== 64) {
throw new Error('usage: pgh user revoke <identity-hex> --app <name>')
}
const app = parseAppFlag(args)
const c = await appClient(app)
try {
const r = await c.call('revoke-identity', { identityId: identity })
return r
} finally { try { await c.close() } catch {} }
}
throw new Error('unknown user subcommand: ' + sub)
}