#!/usr/bin/env node
import process from '#node:process'
import HypercoreId from 'hypercore-id-encoding'
import { loadDeviceKeyPair } from '../index.js'
import { serve } from '../lib/server.js'
import { connect } from '../lib/client.js'
import { addName, loadNames } from '../lib/names.js'
import { openRegistry, resolveViaRegistry, loadRegistryKey, saveRegistryKey } from '../lib/registry.js'
import { localStdio } from '../lib/stdio.js'
const USAGE = `hyperident-shell — P2P shell keyed by your hyperident device identity
Usage:
hyperident-shell id Print your device pubkey (the shell address)
hyperident-shell serve [--firewall <file>] Serve a shell keyed by THIS device
[--disable-firewall]
hyperident-shell <name|pubkey> [-- cmd ...] Connect (resolves a name via the local
names store, else treats it as a pubkey)
hyperident-shell add-name <name> <pubkey> Map a name to a device pubkey
hyperident-shell names List known names
The server/client key IS your hyperident device key; the server address equals
your device pubkey. Runs on node and bare.`
function keyPair () {
const id = loadDeviceKeyPair()
return { id, keyPair: { publicKey: id.publicKey, secretKey: id.secretKey } }
}
async function main () {
const argv = process.argv.slice(2)
const cmd = argv[0]
if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
console.log(USAGE)
return
}
if (cmd === 'id') {
const { id } = keyPair()
console.log(id.pub)
process.stderr.write('device pubkey (hex): ' + id.publicKeyHex + '\n')
return
}
if (cmd === 'serve') {
const { id, keyPair: kp } = keyPair()
let firewall = null
let disableFirewall = false
for (let i = 1; i < argv.length; i++) {
if (argv[i] === '--firewall') firewall = argv[++i]
else if (argv[i] === '--disable-firewall') disableFirewall = true
}
await serve({
keyPair: kp,
firewall,
disableFirewall,
onConnection: (rpk, allow) =>
process.stderr.write((allow ? 'allowed ' : 'DENIED ') + HypercoreId.encode(rpk) + '\n')
})
console.log('hyperident-shell serving as ' + id.pub)
console.log('connect with: hyperident-shell ' + id.pub)
// DHT server handles keep the process alive.
return
}
if (cmd === 'add-name') {
const name = argv[1]
const pubkey = argv[2]
if (!name || !pubkey) {
process.stderr.write('usage: hyperident-shell add-name <name> <pubkey>\n')
process.exit(1)
}
addName(name, pubkey)
console.log('added: ' + name + ' -> ' + pubkey)
return
}
if (cmd === 'names') {
const map = loadNames()
if (map.size === 0) console.log('(no names yet — add with: hyperident-shell add-name <name> <pubkey>)')
for (const [n, k] of map) console.log(n.padEnd(20) + ' ' + k)
return
}
if (cmd === 'registry') {
await handleRegistry(argv.slice(1))
return
}
// Otherwise: connect. Resolve cmd → server device pubkey.
const { keyPair: kp } = keyPair()
let serverPublicKey
try {
serverPublicKey = await resolveTarget(cmd)
} catch (e) {
process.stderr.write(e.message + '\n')
process.exit(1)
}
connect({ serverPublicKey, keyPair: kp, command: argv.slice(1), stdio: localStdio() })
}
// Resolve a name-or-pubkey to a device pubkey Buffer:
// 1. a raw z-base32/hex pubkey 2. a local name 3. the P2P registry (if configured)
async function resolveTarget (nameOrKey) {
try { return HypercoreId.decode(nameOrKey) } catch {}
const names = loadNames()
if (names.has(nameOrKey)) return HypercoreId.decode(names.get(nameOrKey))
const registryKey = loadRegistryKey()
if (registryKey) {
process.stderr.write('resolving "' + nameOrKey + '" via P2P registry…\n')
const entry = await resolveViaRegistry(nameOrKey, { registryKey })
if (entry && entry.identity && entry.identity.devicePubkey) {
return HypercoreId.decode(entry.identity.devicePubkey)
}
}
throw new Error(
'cannot resolve "' + nameOrKey + '" — not a pubkey, not a local name' +
(registryKey ? ', not in the registry' : ', and no registry configured (hyperident-shell registry use <key>)')
)
}
async function handleRegistry (args) {
const sub = args[0]
if (sub === 'use') {
saveRegistryKey(args[1])
console.log('registry set: ' + args[1])
return
}
if (sub === 'key') {
console.log(loadRegistryKey() || '(none)')
return
}
if (sub === 'resolve') {
const registryKey = loadRegistryKey()
if (!registryKey) { process.stderr.write('no registry configured (hyperident-shell registry use <key>)\n'); process.exit(1) }
const entry = await resolveViaRegistry(args[1], { registryKey })
console.log(entry ? entry.identity.devicePubkey : '(not found)')
process.exit(0)
}
if (sub === 'host') {
let key = null
const claims = []
for (let i = 1; i < args.length; i++) {
if (args[i] === '--key') key = args[++i]
else if (args[i] === '--claim') { const [n, k] = String(args[++i]).split('='); claims.push([n, k]) }
}
const r = await openRegistry({ registryKey: key, host: true })
for (const [n, k] of claims) {
await r.reg.claim({ name: n, identity: { devicePubkey: k, identityRoot: k } })
process.stderr.write('claimed ' + n + ' -> ' + k + '\n')
}
console.log('registry host up — key:')
console.log(r.reg.keyHex)
process.stderr.write('hosting over hyperswarm (keep this running)\n')
return // the swarm keeps the process alive
}
process.stderr.write('usage: hyperident-shell registry host [--key <hex>] [--claim <name>=<pubkey>]... | use <key> | key | resolve <name>\n')
process.exit(1)
}
main().catch((e) => {
process.stderr.write('hyperident-shell: ' + e.message + '\n')
process.exit(1)
})