// Name resolution for hyperident-shell.
//
// Two layers:
// 1. a local names store (~/.hyperident-shell/names) — `name <z-base32-pubkey>`
// per line, SSH known_hosts style. Immediate, offline, per-machine.
// 2. (future) the hyperident HandleRegistry — a P2P `name -> identity` registry
// (plan Phase 2, to be extracted into hyper-identity). resolve() has the hook
// where a registry lookup slots in; the local store then acts as a cache /
// override. The call site (`hyperident-shell <name-or-pubkey>`) is unchanged
// when that lands.
import fs from '#node:fs'
import os from '#node:os'
import path from '#node:path'
import process from '#node:process'
import HypercoreId from 'hypercore-id-encoding'
function homedir () {
return (os.homedir && os.homedir()) || process.env.HOME || process.env.USERPROFILE || '.'
}
function namesFile () {
return path.join(homedir(), '.hyperident-shell', 'names')
}
export function loadNames (file = namesFile()) {
const map = new Map()
let txt
try {
txt = fs.readFileSync(file, 'utf8')
} catch (e) {
if (e.code === 'ENOENT') return map
throw e
}
for (const line of txt.split('\n')) {
const s = line.trim()
if (!s || s.startsWith('#')) continue
const sp = s.indexOf(' ')
const name = sp === -1 ? s : s.slice(0, sp).trim()
const key = sp === -1 ? '' : s.slice(sp + 1).trim()
if (name && key) map.set(name, key)
}
return map
}
export function addName (name, pubkey, file = namesFile()) {
HypercoreId.decode(pubkey) // validate (throws if not a valid key)
fs.mkdirSync(path.dirname(file), { recursive: true })
const map = loadNames(file)
map.set(name, pubkey)
const out = [...map.entries()].map(([n, k]) => n + ' ' + k).join('\n') + '\n'
fs.writeFileSync(file, out, { mode: 0o600 })
}
/**
* Resolve a name-or-pubkey to a 32-byte public key Buffer.
* 1. local names store (name -> pubkey)
* 2. treat the input as a raw z-base32/hex pubkey
* 3. TODO(Phase 2): query the hyperident HandleRegistry (name -> identityRoot
* -> device pubkey) and cache the result in the local store.
*/
export function resolve (nameOrKey, file = namesFile()) {
const map = loadNames(file)
if (map.has(nameOrKey)) return HypercoreId.decode(map.get(nameOrKey))
try {
return HypercoreId.decode(nameOrKey)
} catch {}
throw new Error(
'cannot resolve "' + nameOrKey + '" — not a known name or a valid pubkey.\n' +
' add it with: hyperident-shell add-name ' + nameOrKey + ' <pubkey>'
)
}