/**
* Shared helper: resolve a pgh-managed app's daemon UDS via pgh-sidecar's
* app-lookup, then return a hyper-svc Client connected to that UDS.
*
* Used by all the daemon-RPC commands ported in from
* pear-git-v3/lib/pgh/commands/* during the CLI consolidation (see
* pear-git-v3 README for the architectural plan).
*/
import fs from '#node:fs'
import path from '#node:path'
import { homedir } from '#node:os'
import process from '#node:process'
// Honour PGH_SIDECAR_SOCK so an experimental sidecar at a non-default
// path (~/.pgh-experimental/svc.sock) is reachable without patching
// every command. Falls back to the canonical ~/.pgh/svc.sock.
const SIDECAR_SOCK = process.env.PGH_SIDECAR_SOCK ||
path.join(homedir(), '.pgh', 'svc.sock')
// An '@'-prefixed socket is a Linux abstract-namespace socket (no filesystem
// entry), used where SELinux denies pathname sockets (unrooted Android/celltron).
export function isAbstract (p) {
return typeof p === 'string' && p.length > 1 && p[0] === '@'
}
export async function daemonClient (appName = 'pear-git-v3') {
// The existence check only applies to pathname sockets; an abstract socket has
// no fs entry, so fs.existsSync would always be false and wrongly report the
// sidecar as down. hyper-svc's Client handles the '@' at connect time.
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)
}
/**
* Run an async fn with a daemon client, ensuring the client is closed
* even on error. Most ported commands take a client arg from their
* dispatcher; this helper auto-resolves one when called from base pgh's
* dispatcher (which passes null as the client).
*/
export async function withDaemon (appName, fn) {
const client = await daemonClient(appName)
try { return await fn(client) }
finally { try { await client.close() } catch {} }
}