#!/usr/bin/env node
// Hyperident CLI - Identity management for hyperswarm applications
//
// Provides persistent, deterministic addressing through keet-identity-key integration.
// pgh provides P2P OTA updates (content-addressed) when installed via pgh.
//
// Usage: bare index.js <command> [options]
// node index.js <command> [options]
import process from 'process'
if (typeof globalThis.process === 'undefined') globalThis.process = process
import { IdentityManager } from './lib/identity-manager.js'
import { DeviceLinkingManager } from './lib/device-linking-manager.js'
import { DeviceMeshManager } from './lib/device-mesh-manager.js'
import { DebugManager } from './lib/debug-manager.js'
/**
* Display help message
*/
function showHelp() {
console.log(`
🔑 Hyperident - Identity Manager for Hyperswarm
USAGE:
hyperident <command> [options]
COMMANDS:
init [--with-passphrase] [--device-name <name>] [--force]
Initialize new identity (--force: move an existing
vault to ~/.hyperident.bak-<time> first)
create-profile <name> [desc] Create new profile
list-profiles List all profiles
delete-profile <name> Delete profile
address <profile> [--cached] Show hyperswarm address for profile
refresh-addresses Refresh all cached addresses
export-addresses [file] [--enhanced] Export addresses for external programs
sign <file> Sign file with device identity
verify <signature> <file> Verify signature against file
export-signing-config Export signing config for applications
device list List device information
device export [file] Export device bundle for sync
device import <file> --device-name <name> Import device bundle
device verify Verify device chain
device bootstrap Bootstrap as first node in network
device create-network Create and start identity mesh network
device share [--timeout <minutes>] Share device for network linking
device join <code> --device-name <name> Join shared device over network
device status Show device mesh status
device sync Sync profiles across mesh
device mesh join Join identity mesh network
device mesh leave Leave identity mesh network
device revoke <device-id> Revoke device from identity
debug inspect [--passphrase] Inspect identity and system state
debug health Run comprehensive health check
debug network Run network diagnostics
debug trust Verify trust relationships
debug system Show system information
--help, -h Show this help
--version, -v Show version
EXAMPLES:
node index.js init # Create identity without passphrase
node index.js init --with-passphrase # Create identity with passphrase
node index.js init --device-name "laptop" # Create identity with device name
node index.js create-profile work # Create 'work' profile
node index.js list-profiles # Show all profiles
node index.js delete-profile work # Delete 'work' profile
node index.js address work # Show address for 'work' profile
node index.js device export bundle.json # Export for cross-device sync
node index.js device import bundle.json --device-name "phone" # Import on new device
For more information, visit: https://github.com/hyperident/hyperident
`)
}
/**
* Display version information
*/
function showVersion() {
console.log('hyperident v0.1.0')
}
/**
* Handle init command
* @param {Object} parsed - Parsed command arguments
*/
async function handleInit(parsed) {
try {
// Check if identity already exists. --force moves it aside (never deletes it).
const force = parsed.flags.includes('--force')
if (IdentityManager.identityExists() && !force) {
console.log('❌ Identity already exists!')
console.log('💡 To create a new identity anyway, run: hyperident init --force')
console.log(' The existing vault is moved to ~/.hyperident.bak-<time> first; nothing is deleted.')
console.log(' Identity location: ~/.hyperident/')
process.exit(1)
}
const withPassphrase = parsed.flags.includes('--with-passphrase')
// Extract device name from flags
let deviceName = 'default'
const deviceNameIndex = parsed.flags.indexOf('--device-name')
if (deviceNameIndex !== -1 && deviceNameIndex + 1 < parsed.flags.length) {
deviceName = parsed.flags[deviceNameIndex + 1]
}
let passphrase = null
if (withPassphrase) {
// Import readline for passphrase input
const readline = await import('readline')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
// Helper function to get hidden input
const getHiddenInput = (prompt) => {
return new Promise((resolve) => {
process.stdout.write(prompt)
process.stdin.setRawMode(true)
process.stdin.resume()
process.stdin.setEncoding('utf8')
let input = ''
const onData = (char) => {
if (char === '\u0003') { // Ctrl+C
process.exit(1)
} else if (char === '\r' || char === '\n') {
process.stdin.setRawMode(false)
process.stdin.pause()
process.stdin.removeListener('data', onData)
process.stdout.write('\n')
resolve(input)
} else if (char === '\u007f') { // Backspace
if (input.length > 0) {
input = input.slice(0, -1)
process.stdout.write('\b \b')
}
} else {
input += char
process.stdout.write('*')
}
}
process.stdin.on('data', onData)
})
}
console.log('🔐 Creating encrypted identity...')
passphrase = await getHiddenInput('Enter passphrase: ')
if (!passphrase || passphrase.length < 8) {
console.log('❌ Passphrase must be at least 8 characters long')
process.exit(1)
}
const confirmPassphrase = await getHiddenInput('Confirm passphrase: ')
if (passphrase !== confirmPassphrase) {
console.log('❌ Passphrases do not match')
process.exit(1)
}
rl.close()
}
console.log('🔑 Generating new identity with device chain...')
// Generate and save the identity with device chain
const identity = await IdentityManager.generateIdentity(passphrase, deviceName, { force })
if (identity.backupPath) {
console.log('📦 Previous identity moved to: ' + identity.backupPath)
}
console.log('✅ Identity created successfully!')
console.log('')
console.log('📋 Identity Details:')
console.log(` Public Key: ${identity.publicKey}`)
console.log(` Device Name: ${deviceName}`)
console.log(` Created: ${identity.created}`)
console.log(` Algorithm: ${identity.algorithm}`)
console.log(` Device Chain: ${identity.deviceChain ? 'Enabled' : 'Disabled'}`)
console.log(` Encrypted: ${withPassphrase ? 'Yes' : 'No'}`)
console.log('')
console.log('💾 Identity saved to: ~/.hyperident/')
console.log('')
console.log('🚀 Next steps:')
console.log(' • Create profiles: node index.js create-profile <name>')
console.log(' • List profiles: node index.js list-profiles')
console.log(' • View device info: node index.js device list')
console.log(' • Export for sync: node index.js device export')
console.log(' • Get help: node index.js --help')
if (withPassphrase) {
console.log('')
console.log('🔒 Important: Your identity is encrypted with a passphrase.')
console.log(' Keep your passphrase safe - it cannot be recovered if lost!')
}
} catch (error) {
console.error('❌ Failed to initialize identity:', error.message)
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle create-profile command
* @param {Object} parsed - Parsed command arguments
*/
async function handleCreateProfile(parsed) {
try {
const profileName = parsed.positional[0]
const description = parsed.positional[1] || ''
console.log(`📝 Creating profile '${profileName}'...`)
// Create the profile using IdentityManager
await IdentityManager.createProfile(profileName, description)
console.log('✅ Profile created successfully!')
console.log('')
console.log('📋 Profile Details:')
console.log(` Name: ${profileName}`)
console.log(` Description: ${description || '(no description)'}`)
console.log('')
console.log('🚀 Next steps:')
console.log(' • List all profiles: hyperident list-profiles')
console.log(' • Delete profile: hyperident delete-profile ' + profileName)
console.log(' • Get help: hyperident --help')
} catch (error) {
console.error('❌ Failed to create profile:', error.message)
// Provide specific guidance based on error type
if (error.message.includes('Identity not found')) {
console.error('💡 Run "hyperident init" first to create an identity')
} else if (error.message.includes('already exists')) {
console.error('💡 Choose a different profile name or delete the existing one first')
} else if (error.message.includes('Invalid profile name')) {
console.error('💡 Profile names must be 1-32 characters, alphanumeric and hyphens only')
console.error(' Cannot start/end with hyphen or use reserved names')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle list-profiles command
* @param {Object} parsed - Parsed command arguments
*/
async function handleListProfiles(parsed) {
try {
console.log('📋 Listing profiles...')
// Get all profiles using IdentityManager
const profilesData = await IdentityManager.listProfiles()
const profiles = Object.values(profilesData.profiles)
if (profiles.length === 0) {
console.log('📭 No profiles found')
console.log('')
console.log('🚀 Get started:')
console.log(' • Create a profile: hyperident create-profile <name>')
console.log(' • Get help: hyperident --help')
return
}
console.log(`✅ Found ${profiles.length} profile${profiles.length === 1 ? '' : 's'}:`)
console.log('')
// Display each profile with details
profiles.forEach((profile, index) => {
console.log(`${index + 1}. 📁 ${profile.name}`)
if (profile.description) {
console.log(` Description: ${profile.description}`)
}
console.log(` Created: ${new Date(profile.created).toLocaleString()}`)
if (profile.lastModified && profile.lastModified !== profile.created) {
console.log(` Modified: ${new Date(profile.lastModified).toLocaleString()}`)
}
console.log('')
})
console.log('🚀 Next steps:')
console.log(' • Create profile: hyperident create-profile <name>')
console.log(' • Delete profile: hyperident delete-profile <name>')
console.log(' • Get help: hyperident --help')
} catch (error) {
console.error('❌ Failed to list profiles:', error.message)
// Provide specific guidance based on error type
if (error.message.includes('Identity not found')) {
console.error('💡 Run "hyperident init" first to create an identity')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle delete-profile command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDeleteProfile(parsed) {
try {
const profileName = parsed.positional[0]
console.log(`🗑️ Deleting profile '${profileName}'...`)
// Delete the profile using IdentityManager
await IdentityManager.deleteProfile(profileName)
console.log('✅ Profile deleted successfully!')
console.log('')
console.log(`📋 Profile '${profileName}' has been removed`)
console.log('')
console.log('🚀 Next steps:')
console.log(' • List remaining profiles: hyperident list-profiles')
console.log(' • Create new profile: hyperident create-profile <name>')
console.log(' • Get help: hyperident --help')
} catch (error) {
console.error('❌ Failed to delete profile:', error.message)
// Provide specific guidance based on error type
if (error.message.includes('Identity not found')) {
console.error('💡 Run "hyperident init" first to create an identity')
} else if (error.message.includes('not found')) {
console.error('💡 Check available profiles with "hyperident list-profiles"')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle address command
* @param {Object} parsed - Parsed command arguments
*/
async function handleAddress(parsed) {
try {
const profileName = parsed.positional[0]
const useCached = parsed.flags.includes('--cached')
console.log(`🔍 Getting address for profile '${profileName}'...`)
// Check if identity is encrypted and prompt for passphrase if needed
let passphrase = null
if (!useCached) {
try {
const identityData = await IdentityManager.loadIdentity()
// If we get here without error, identity is not encrypted
} catch (error) {
if (error.message.includes('encrypted but no passphrase provided')) {
passphrase = await getPassphrase('🔐 Identity is encrypted. Please enter your passphrase.')
} else {
throw error
}
}
}
// Get the hyperswarm address for the profile
let address
if (useCached) {
address = await IdentityManager.getCachedProfileAddress(profileName, passphrase)
console.log('✅ Cached address retrieved successfully!')
} else {
address = await IdentityManager.getProfileAddress(profileName, passphrase)
console.log('✅ Address generated successfully!')
}
console.log('')
console.log('📋 Profile Address:')
console.log(` Profile: ${profileName}`)
console.log(` Address: ${address}`)
console.log(` Source: ${useCached ? 'Cached' : 'Generated'}`)
console.log('')
console.log('🔗 Usage:')
console.log(' This address can be used with hyperswarm applications')
console.log(' The same profile will always generate the same address')
console.log('')
console.log('🚀 Next steps:')
console.log(' • Use this address in hyperswarm applications')
console.log(' • Create more profiles: hyperident create-profile <name>')
console.log(' • List all profiles: hyperident list-profiles')
} catch (error) {
console.error('❌ Failed to get profile address:', error.message)
// Provide specific guidance based on error type
if (error.message.includes('Identity not found')) {
console.error('💡 Run "hyperident init" first to create an identity')
} else if (error.message.includes('not found')) {
console.error('💡 Check available profiles with "hyperident list-profiles"')
console.error(' Or create the profile: hyperident create-profile ' + parsed.positional[0])
} else if (error.message.includes('Invalid passphrase')) {
console.error('💡 Please check your passphrase and try again')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle refresh-addresses command
* @param {Object} parsed - Parsed command arguments
*/
async function handleRefreshAddresses(parsed) {
try {
console.log('🔄 Refreshing all cached addresses...')
// Check if identity is encrypted and prompt for passphrase if needed
let passphrase = null
try {
const identityData = await IdentityManager.loadIdentity()
// If we get here without error, identity is not encrypted
} catch (error) {
if (error.message.includes('encrypted but no passphrase provided')) {
passphrase = await getPassphrase('🔐 Identity is encrypted. Please enter your passphrase.')
} else {
throw error
}
}
// Refresh all addresses
await IdentityManager.refreshAllAddresses(passphrase)
console.log('✅ All addresses refreshed successfully!')
console.log('')
console.log('📋 All profile addresses have been updated in cache')
console.log('')
console.log('🚀 Next steps:')
console.log(' • List profiles: hyperident list-profiles')
console.log(' • Export addresses: hyperident export-addresses')
console.log(' • Get help: hyperident --help')
} catch (error) {
console.error('❌ Failed to refresh addresses:', error.message)
// Provide specific guidance based on error type
if (error.message.includes('Identity not found')) {
console.error('💡 Run "hyperident init" first to create an identity')
} else if (error.message.includes('Invalid passphrase')) {
console.error('💡 Please check your passphrase and try again')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle export-addresses command
* @param {Object} parsed - Parsed command arguments
*/
async function handleExportAddresses(parsed) {
try {
const outputFile = parsed.positional[0] || null
const useEnhanced = parsed.flags.includes('--enhanced')
console.log(`📤 Exporting addresses${useEnhanced ? ' (enhanced format)' : ''}...`)
// Check if identity is encrypted and prompt for passphrase if needed (for enhanced export)
let passphrase = null
if (useEnhanced) {
try {
const identityData = await IdentityManager.loadIdentity()
// If we get here without error, identity is not encrypted
} catch (error) {
if (error.message.includes('encrypted but no passphrase provided')) {
passphrase = await getPassphrase('🔐 Identity is encrypted. Please enter your passphrase.')
} else {
throw error
}
}
}
// Export addresses
const addressesJSON = useEnhanced
? await IdentityManager.exportAddressesEnhancedJSON(passphrase)
: await IdentityManager.exportAddressesJSON()
if (outputFile) {
// Write to file
const fs = await import('fs/promises')
await fs.writeFile(outputFile, addressesJSON)
console.log(`✅ Addresses exported to: ${outputFile}`)
} else {
// Output to console
console.log('✅ Addresses exported:')
console.log('')
console.log(addressesJSON)
}
console.log('')
if (useEnhanced) {
console.log('📋 Enhanced Export Features:')
console.log(' • Multiple format options (hex, buffer, base64)')
console.log(' • Environment variable templates')
console.log(' • Hyperswarm usage examples')
console.log(' • JavaScript integration code')
console.log('')
}
console.log('🚀 Next steps:')
console.log(' • Use addresses in external programs')
console.log(' • List profiles: hyperident list-profiles')
if (!useEnhanced) {
console.log(' • Try enhanced export: hyperident export-addresses --enhanced')
}
console.log(' • Get help: hyperident --help')
} catch (error) {
console.error('❌ Failed to export addresses:', error.message)
// Provide specific guidance based on error type
if (error.message.includes('Identity not found')) {
console.error('💡 Run "hyperident init" first to create an identity')
} else if (error.message.includes('Invalid passphrase')) {
console.error('💡 Please check your passphrase and try again')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle device list command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDeviceList(parsed) {
try {
console.log('📱 Device Information:')
// Check if device chain exists
const deviceExists = await IdentityManager.deviceChainExists()
if (!deviceExists) {
console.log('❌ No device chain found')
console.log('💡 This identity was created without device chain support')
console.log(' Create a new identity with: node index.js init --device-name "device-name"')
process.exit(1)
}
// Load device chain
const deviceChain = await IdentityManager.loadDeviceChain()
console.log('✅ Device chain found:')
console.log('')
console.log('📋 Device Details:')
console.log(` Name: ${deviceChain.metadata.name}`)
console.log(` Created: ${new Date(deviceChain.metadata.created).toLocaleString()}`)
console.log(` Public Key: ${deviceChain.metadata.publicKey}`)
console.log(` Version: ${deviceChain.metadata.version}`)
console.log('')
console.log('🚀 Next steps:')
console.log(' • Export for sync: node index.js device export')
console.log(' • Verify chain: node index.js device verify')
console.log(' • Get help: node index.js --help')
} catch (error) {
console.error('❌ Failed to list device information:', error.message)
if (error.message.includes('Identity not found')) {
console.error('💡 Run "node index.js init" first to create an identity')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle device export command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDeviceExport(parsed) {
try {
const outputFile = parsed.positional[0] || null
console.log('📤 Exporting device bundle...')
// Check if identity is encrypted and prompt for passphrase if needed
let passphrase = null
try {
const identityData = await IdentityManager.loadIdentity()
// If we get here without error, identity is not encrypted
} catch (error) {
if (error.message.includes('encrypted but no passphrase provided')) {
passphrase = await getPassphrase('🔐 Identity is encrypted. Please enter your passphrase.')
} else {
throw error
}
}
// Export device bundle
const bundle = await IdentityManager.exportDeviceBundle(passphrase)
const bundleJSON = JSON.stringify(bundle, null, 2)
if (outputFile) {
// Write to file
const fs = await import('fs/promises')
await fs.writeFile(outputFile, bundleJSON)
console.log(`✅ Device bundle exported to: ${outputFile}`)
} else {
// Output to console
console.log('✅ Device bundle exported:')
console.log('')
console.log(bundleJSON)
}
console.log('')
console.log('🔗 Usage:')
console.log(' Transfer this bundle to another device and import it with:')
console.log(' node index.js device import <bundle-file> --device-name "new-device"')
console.log('')
console.log('🚀 Next steps:')
console.log(' • Import on new device: node index.js device import <file> --device-name <name>')
console.log(' • Get help: node index.js --help')
} catch (error) {
console.error('❌ Failed to export device bundle:', error.message)
if (error.message.includes('Identity not found')) {
console.error('💡 Run "node index.js init" first to create an identity')
} else if (error.message.includes('Invalid passphrase')) {
console.error('💡 Please check your passphrase and try again')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle device import command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDeviceImport(parsed) {
try {
const bundleFile = parsed.positional[1] // positional[0] is "import", positional[1] is the file
if (!bundleFile) {
console.error('❌ Bundle file is required')
console.error('💡 Usage: node index.js device import <bundle-file> --device-name <name>')
process.exit(1)
}
// Extract device name from flags
let deviceName = null
const deviceNameIndex = parsed.flags.indexOf('--device-name')
if (deviceNameIndex !== -1 && deviceNameIndex + 1 < parsed.flags.length) {
deviceName = parsed.flags[deviceNameIndex + 1]
}
if (!deviceName) {
console.error('❌ Device name is required')
console.error('💡 Usage: node index.js device import <bundle-file> --device-name <name>')
process.exit(1)
}
console.log(`📥 Importing device bundle from: ${bundleFile}`)
console.log(`📱 New device name: ${deviceName}`)
// Check if identity already exists
if (IdentityManager.identityExists()) {
console.log('❌ Identity already exists!')
console.log('💡 If you want to import a new identity, please backup and remove the existing one first.')
console.log(' Identity location: ~/.hyperident/')
process.exit(1)
}
// Read bundle file
const fs = await import('fs/promises')
const bundleJSON = await fs.readFile(bundleFile, 'utf8')
const bundle = JSON.parse(bundleJSON)
// Check if we need a passphrase for the imported identity
let passphrase = null
if (bundle.identity && bundle.identity.encrypted === true) {
// Only prompt for passphrase if explicitly encrypted
passphrase = await getPassphrase('🔐 Imported identity is encrypted. Please enter the passphrase.')
}
// Import device bundle
await IdentityManager.importDeviceBundle(bundle, deviceName, passphrase)
console.log('✅ Device bundle imported successfully!')
console.log('')
console.log('📋 Import Details:')
console.log(` Device Name: ${deviceName}`)
console.log(` Identity Public Key: ${bundle.identity.publicKey}`)
console.log(` Profiles Imported: ${Object.keys(bundle.profiles || {}).length}`)
console.log('')
console.log('💾 Identity saved to: ~/.hyperident/')
console.log('')
console.log('🚀 Next steps:')
console.log(' • List profiles: node index.js list-profiles')
console.log(' • Verify device: node index.js device verify')
console.log(' • Get help: node index.js --help')
} catch (error) {
console.error('❌ Failed to import device bundle:', error.message)
if (error.message.includes('Invalid device bundle format')) {
console.error('💡 Check that the bundle file is valid and not corrupted')
} else if (error.message.includes('Invalid passphrase')) {
console.error('💡 Please check your passphrase and try again')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle device bootstrap command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDeviceBootstrap(parsed) {
try {
console.log('🚀 Bootstrapping as first node in network...')
// Check if identity exists
if (!IdentityManager.identityExists()) {
console.log('❌ No identity found!')
console.log('💡 Run "node index.js init --device-name <name>" first to create an identity')
process.exit(1)
}
// Check if device chain exists
const deviceExists = await IdentityManager.deviceChainExists()
if (!deviceExists) {
console.log('❌ No device chain found')
console.log('💡 This identity was created without device chain support')
console.log(' Create a new identity with: node index.js init --device-name "device-name"')
process.exit(1)
}
// Load device chain and identity info
const deviceChain = await IdentityManager.loadDeviceChain()
const identity = await IdentityManager.loadIdentity()
console.log('✅ Identity and device chain verified!')
console.log('')
console.log('📋 Bootstrap Details:')
console.log(` Identity: ${identity.publicKey}`)
console.log(` Device: ${deviceChain.metadata.name}`)
console.log(` Device Key: ${deviceChain.metadata.publicKey}`)
console.log(` Created: ${new Date(deviceChain.metadata.created).toLocaleString()}`)
console.log('')
console.log('🌐 Network Information:')
console.log(' You are now the first node in your identity mesh network')
console.log(' Other devices can sync with you using:')
console.log(' • Device sharing: node index.js device share')
console.log(' • Device export/import: node index.js device export')
console.log(' • Mesh networking: node index.js device mesh join')
console.log('')
console.log('🚀 Next steps:')
console.log(' • Create profiles: node index.js create-profile <name>')
console.log(' • Start mesh network: node index.js device create-network')
console.log(' • Share device: node index.js device share')
console.log(' • Export bundle: node index.js device export')
console.log(' • Get help: node index.js --help')
} catch (error) {
console.error('❌ Failed to bootstrap device:', error.message)
if (error.message.includes('Identity not found')) {
console.error('💡 Run "node index.js init" first to create an identity')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle device create-network command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDeviceCreateNetwork(parsed) {
try {
console.log('🌐 Creating and starting identity mesh network...')
// Check network availability
const networkAvailable = await DeviceMeshManager.isNetworkAvailable()
if (!networkAvailable) {
console.log('❌ Network dependencies not available')
console.log('💡 Install network dependencies with:')
console.log(' npm install hyperswarm hypercore-id-encoding b4a')
process.exit(1)
}
// Check if identity exists
if (!IdentityManager.identityExists()) {
console.log('❌ No identity found!')
console.log('💡 Run "node index.js init --device-name <name>" first to create an identity')
process.exit(1)
}
// Create device mesh manager
const meshManager = new DeviceMeshManager()
try {
// Join the mesh as the first node
await meshManager.joinMesh()
console.log('✅ Identity mesh network created and started!')
console.log('')
console.log('📋 Network Details:')
console.log(' Status: Active (First Node)')
console.log(' Discovery: Identity-based mesh discovery active')
console.log(' Sync: Real-time profile synchronization enabled')
console.log(' Role: Bootstrap/Seed Node')
console.log('')
console.log('🔗 Network Features:')
console.log(' • Automatic device discovery within identity')
console.log(' • Real-time profile synchronization')
console.log(' • Device status monitoring')
console.log(' • Conflict resolution for profile changes')
console.log('')
console.log('📱 Adding Other Devices:')
console.log(' Other devices can join this network using:')
console.log(' 1. Device sharing: node index.js device share')
console.log(' 2. Export/import: node index.js device export')
console.log(' 3. Direct mesh join: node index.js device mesh join')
console.log('')
console.log('🚀 Network is now running!')
console.log(' • Check status: node index.js device status')
console.log(' • Sync profiles: node index.js device sync')
console.log(' • Share device: node index.js device share')
console.log('')
console.log('💡 The network will continue running in the background.')
console.log(' Use Ctrl+C to stop or run "device mesh leave" to disconnect.')
// Keep the process alive for mesh operations
console.log('Press Ctrl+C to stop the network...')
await new Promise((resolve) => {
const cleanup = async () => {
console.log('\n🛑 Stopping mesh network...')
await meshManager.leaveMesh()
console.log('✅ Network stopped')
resolve()
}
process.on('SIGINT', cleanup)
process.on('SIGTERM', cleanup)
})
} catch (error) {
await meshManager.cleanup()
throw error
}
} catch (error) {
console.error('❌ Failed to create network:', error.message)
if (error.message.includes('Identity not found')) {
console.error('💡 Run "node index.js init" first to create an identity')
} else if (error.message.includes('Network dependencies not available')) {
console.error('💡 Install required dependencies:')
console.error(' npm install hyperswarm hypercore-id-encoding b4a')
} else if (error.message.includes('Already connected to mesh')) {
console.error('💡 Already connected to mesh. Use "device status" to check status.')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle device verify command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDeviceVerify(parsed) {
try {
console.log('🔍 Verifying device chain...')
// Check if identity is encrypted and prompt for passphrase if needed
let passphrase = null
try {
const identityData = await IdentityManager.loadIdentity()
// If we get here without error, identity is not encrypted
} catch (error) {
if (error.message.includes('encrypted but no passphrase provided')) {
passphrase = await getPassphrase('🔐 Identity is encrypted. Please enter your passphrase.')
} else {
throw error
}
}
// Verify device chain
const isValid = await IdentityManager.deviceChainExists(passphrase)
if (isValid) {
console.log('✅ Device chain verification successful!')
console.log('')
console.log('📋 Verification Results:')
console.log(' Device chain is valid and properly attested')
console.log(' Device is authorized for this identity')
console.log(' Proof chain links back to root identity')
console.log('')
console.log('🚀 Next steps:')
console.log(' • View device info: node index.js device list')
console.log(' • Export for sync: node index.js device export')
console.log(' • Get help: node index.js --help')
} else {
console.log('❌ Device chain verification failed!')
console.log('')
console.log('📋 Possible Issues:')
console.log(' • Device chain files are missing or corrupted')
console.log(' • Device is not properly attested')
console.log(' • Proof chain does not link to root identity')
console.log('')
console.log('💡 Try recreating the identity or importing a valid device bundle')
process.exit(1)
}
} catch (error) {
console.error('❌ Failed to verify device chain:', error.message)
if (error.message.includes('Identity not found')) {
console.error('💡 Run "node index.js init" first to create an identity')
} else if (error.message.includes('Invalid passphrase')) {
console.error('💡 Please check your passphrase and try again')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle sign command
* @param {Object} parsed - Parsed command arguments
*/
async function handleSign(parsed) {
try {
const filePath = parsed.positional[0]
if (!filePath) {
console.error('❌ File path is required')
console.error('💡 Usage: hyperident sign <file>')
process.exit(1)
}
console.log(`🔏 Signing file: ${filePath}`)
// Check if identity is encrypted and prompt for passphrase if needed
let passphrase = null
try {
const identityData = await IdentityManager.loadIdentity()
// If we get here without error, identity is not encrypted
} catch (error) {
if (error.message.includes('encrypted but no passphrase provided')) {
passphrase = await getPassphrase('🔐 Identity is encrypted. Please enter your passphrase.')
} else {
throw error
}
}
// Read file to sign
const fs = await import('fs/promises')
const fileData = await fs.readFile(filePath)
// Sign the file data
const signature = await IdentityManager.signData(fileData, passphrase)
// Create signature file
const signatureFile = filePath + '.sig'
const signatureData = {
version: '1.0.0',
algorithm: 'keet-identity-key',
file: filePath,
signature: signature.toString('hex'),
signed: new Date().toISOString()
}
await fs.writeFile(signatureFile, JSON.stringify(signatureData, null, 2))
console.log('✅ File signed successfully!')
console.log('')
console.log('📋 Signature Details:')
console.log(` File: ${filePath}`)
console.log(` Signature: ${signatureFile}`)
console.log(` Algorithm: ${signatureData.algorithm}`)
console.log(` Signed: ${signatureData.signed}`)
console.log('')
console.log('🚀 Next steps:')
console.log(` • Verify signature: hyperident verify ${signatureFile} ${filePath}`)
console.log(' • Share signature file with others for verification')
console.log(' • Get help: hyperident --help')
} catch (error) {
console.error('❌ Failed to sign file:', error.message)
if (error.message.includes('Identity not found')) {
console.error('💡 Run "hyperident init" first to create an identity')
} else if (error.message.includes('Invalid passphrase')) {
console.error('💡 Please check your passphrase and try again')
} else if (error.message.includes('ENOENT')) {
console.error('💡 Check that the file exists and is readable')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle verify command
* @param {Object} parsed - Parsed command arguments
*/
async function handleVerify(parsed) {
try {
const signatureFile = parsed.positional[0]
const originalFile = parsed.positional[1]
if (!signatureFile || !originalFile) {
console.error('❌ Both signature file and original file are required')
console.error('💡 Usage: hyperident verify <signature-file> <original-file>')
process.exit(1)
}
console.log(`🔍 Verifying signature...`)
console.log(` Signature: ${signatureFile}`)
console.log(` File: ${originalFile}`)
// Read signature file
const fs = await import('fs/promises')
const signatureJSON = await fs.readFile(signatureFile, 'utf8')
const signatureData = JSON.parse(signatureJSON)
// Read original file
const fileData = await fs.readFile(originalFile)
// Convert signature from hex
const signature = Buffer.from(signatureData.signature, 'hex')
// Verify signature
const verification = await IdentityManager.verifySignature(signature, fileData)
if (verification) {
console.log('✅ Signature verification successful!')
console.log('')
console.log('📋 Verification Results:')
console.log(` File: ${originalFile}`)
console.log(` Signature: ${signatureFile}`)
console.log(` Identity: ${verification.identityPublicKey.toString('hex')}`)
console.log(` Device: ${verification.devicePublicKey.toString('hex')}`)
console.log(` Algorithm: ${signatureData.algorithm}`)
console.log(` Signed: ${signatureData.signed}`)
console.log('')
console.log('🔒 This file was authentically signed by the specified identity')
} else {
console.log('❌ Signature verification failed!')
console.log('')
console.log('📋 Possible Issues:')
console.log(' • File has been modified since signing')
console.log(' • Signature file is corrupted or invalid')
console.log(' • Wrong file being verified against signature')
console.log('')
console.log('⚠️ Do not trust this file - signature is invalid')
process.exit(1)
}
} catch (error) {
console.error('❌ Failed to verify signature:', error.message)
if (error.message.includes('ENOENT')) {
console.error('💡 Check that both signature and original files exist')
} else if (error.message.includes('JSON')) {
console.error('💡 Signature file appears to be corrupted or invalid')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle device share command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDeviceShare(parsed) {
try {
// Extract timeout from flags
let timeoutMinutes = 5 // default
const timeoutIndex = parsed.flags.indexOf('--timeout')
if (timeoutIndex !== -1 && timeoutIndex + 1 < parsed.flags.length) {
const timeoutValue = parseInt(parsed.flags[timeoutIndex + 1])
if (!isNaN(timeoutValue) && timeoutValue > 0) {
timeoutMinutes = timeoutValue
}
}
console.log('🔗 Starting device sharing...')
console.log(`⏱️ Timeout: ${timeoutMinutes} minutes`)
// Check network availability
const networkAvailable = await DeviceLinkingManager.isNetworkAvailable()
if (!networkAvailable) {
console.log('❌ Network dependencies not available')
console.log('💡 Install network dependencies with:')
console.log(' npm install hyperswarm hypercore-id-encoding b4a')
process.exit(1)
}
// Create device linking manager
const linkingManager = new DeviceLinkingManager()
try {
// Start sharing
const sharingCode = await linkingManager.startSharing(timeoutMinutes)
console.log('✅ Device sharing started successfully!')
console.log('')
console.log('📋 Sharing Details:')
console.log(` Identity Public Key: ${sharingCode}`)
console.log(` Timeout: ${timeoutMinutes} minutes`)
console.log(` Status: Waiting for connections...`)
console.log('')
console.log('🔗 Usage:')
console.log(' On the new device, run:')
console.log(` node index.js device join ${sharingCode} --device-name "new-device"`)
console.log('')
console.log('⚠️ Security Notes:')
console.log(' • This code expires automatically after the timeout')
console.log(' • Only share this code with devices you trust')
console.log(' • The code is only valid for this session')
console.log('')
console.log('Press Ctrl+C to stop sharing...')
// Keep the process alive until timeout or Ctrl+C
await new Promise((resolve) => {
let cleanupCalled = false
const cleanup = async () => {
if (cleanupCalled) return
cleanupCalled = true
console.log('\n🛑 Stopping device sharing...')
// Remove event listeners to prevent multiple calls
process.removeListener('SIGINT', cleanup)
process.removeListener('SIGTERM', cleanup)
await linkingManager.stopSharing()
console.log('✅ Device sharing stopped')
resolve()
}
process.on('SIGINT', cleanup)
process.on('SIGTERM', cleanup)
// Auto-cleanup on timeout (handled by linkingManager)
setTimeout(() => {
if (!cleanupCalled) {
console.log('\n⏱️ Sharing session timed out')
cleanup()
}
}, timeoutMinutes * 60 * 1000 + 1000) // Add 1 second buffer
})
} catch (error) {
await linkingManager.cleanup()
throw error
}
} catch (error) {
console.error('❌ Failed to share device:', error.message)
if (error.message.includes('Identity not found')) {
console.error('💡 Run "node index.js init" first to create an identity')
} else if (error.message.includes('Network dependencies not available')) {
console.error('💡 Install required dependencies:')
console.error(' npm install hyperswarm hypercore-id-encoding b4a')
} else if (error.message.includes('Already sharing')) {
console.error('💡 Stop the current sharing session first')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle device join command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDeviceJoin(parsed) {
try {
const sharingCode = parsed.positional[1] // positional[0] is "join", positional[1] is the code
if (!sharingCode) {
console.error('❌ Sharing code is required')
console.error('💡 Usage: node index.js device join <code> --device-name <name>')
process.exit(1)
}
// Extract device name from flags
let deviceName = null
const deviceNameIndex = parsed.flags.indexOf('--device-name')
if (deviceNameIndex !== -1 && deviceNameIndex + 1 < parsed.flags.length) {
deviceName = parsed.flags[deviceNameIndex + 1]
}
if (!deviceName) {
console.error('❌ Device name is required')
console.error('💡 Usage: node index.js device join <code> --device-name <name>')
process.exit(1)
}
console.log('🔗 Joining shared device...')
console.log(`📱 Device name: ${deviceName}`)
console.log(`🔑 Sharing code: ${sharingCode}`)
// Check network availability
const networkAvailable = await DeviceLinkingManager.isNetworkAvailable()
if (!networkAvailable) {
console.log('❌ Network dependencies not available')
console.log('💡 Install network dependencies with:')
console.log(' npm install hyperswarm hypercore-id-encoding b4a')
process.exit(1)
}
// Check if identity already exists
if (IdentityManager.identityExists()) {
console.log('❌ Identity already exists!')
console.log('💡 If you want to join a new identity, please backup and remove the existing one first.')
console.log(' Identity location: ~/.hyperident/')
process.exit(1)
}
// Create device linking manager
const linkingManager = new DeviceLinkingManager()
try {
console.log('🔍 Searching for shared device...')
// Join the shared device
const success = await linkingManager.joinDevice(sharingCode, deviceName)
if (success) {
console.log('✅ Successfully joined shared device!')
console.log('')
console.log('📋 Join Details:')
console.log(` Device Name: ${deviceName}`)
console.log(` Sharing Code: ${sharingCode}`)
console.log('')
console.log('💾 Identity saved to: ~/.hyperident/')
console.log('')
console.log('🚀 Next steps:')
console.log(' • List profiles: node index.js list-profiles')
console.log(' • Verify device: node index.js device verify')
console.log(' • View device info: node index.js device list')
console.log(' • Get help: node index.js --help')
} else {
console.log('❌ Failed to join shared device')
console.log('💡 Check the sharing code and try again')
process.exit(1)
}
} catch (error) {
await linkingManager.cleanup()
throw error
}
} catch (error) {
console.error('❌ Failed to join device:', error.message)
if (error.message.includes('Identity already exists')) {
console.error('💡 Remove existing identity first or use a different device')
} else if (error.message.includes('Network dependencies not available')) {
console.error('💡 Install required dependencies:')
console.error(' npm install hyperswarm hypercore-id-encoding b4a')
} else if (error.message.includes('Connection timeout')) {
console.error('💡 Check the sharing code and ensure the sharing device is online')
} else if (error.message.includes('Invalid device bundle format')) {
console.error('💡 The shared device data appears to be corrupted')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle device status command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDeviceStatus(parsed) {
try {
console.log('📊 Device Mesh Status:')
// Check network availability
const networkAvailable = await DeviceMeshManager.isNetworkAvailable()
if (!networkAvailable) {
console.log('❌ Network dependencies not available')
console.log('💡 Install network dependencies with:')
console.log(' npm install hyperswarm hypercore-id-encoding b4a')
process.exit(1)
}
// Create device mesh manager
const meshManager = new DeviceMeshManager()
try {
// Get mesh status
const status = await meshManager.getMeshStatus()
console.log('✅ Mesh status retrieved successfully!')
console.log('')
console.log('📋 Mesh Details:')
console.log(` Connected to Mesh: ${status.connected ? 'Yes' : 'No'}`)
console.log(` Connected Devices: ${status.connectedDevices}`)
console.log(` Identity: ${status.identityPublicKey}`)
console.log(` This Device: ${status.thisDevicePublicKey}`)
console.log('')
if (status.devices && status.devices.length > 0) {
console.log('🔗 Connected Devices:')
status.devices.forEach((device, index) => {
console.log(` ${index + 1}. ${device.name || 'Unknown'}`)
console.log(` Public Key: ${device.publicKey}`)
console.log(` Status: ${device.status || 'Unknown'}`)
console.log(` Last Seen: ${device.lastSeen ? new Date(device.lastSeen).toLocaleString() : 'Never'}`)
console.log('')
})
} else {
console.log('📭 No other devices connected to mesh')
}
console.log('🚀 Next steps:')
console.log(' • Join mesh: node index.js device mesh join')
console.log(' • Sync profiles: node index.js device sync')
console.log(' • Get help: node index.js --help')
} catch (error) {
await meshManager.cleanup()
throw error
}
} catch (error) {
console.error('❌ Failed to get device status:', error.message)
if (error.message.includes('Identity not found')) {
console.error('💡 Run "node index.js init" first to create an identity')
} else if (error.message.includes('Network dependencies not available')) {
console.error('💡 Install required dependencies:')
console.error(' npm install hyperswarm hypercore-id-encoding b4a')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle device sync command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDeviceSync(parsed) {
try {
console.log('🔄 Syncing profiles across mesh...')
// Check network availability
const networkAvailable = await DeviceMeshManager.isNetworkAvailable()
if (!networkAvailable) {
console.log('❌ Network dependencies not available')
console.log('💡 Install network dependencies with:')
console.log(' npm install hyperswarm hypercore-id-encoding b4a')
process.exit(1)
}
// Create device mesh manager
const meshManager = new DeviceMeshManager()
try {
// Perform sync
const syncResult = await meshManager.syncProfiles()
console.log('✅ Profile sync completed successfully!')
console.log('')
console.log('📋 Sync Results:')
console.log(` Profiles Synced: ${syncResult.profilesSynced || 0}`)
console.log(` Conflicts Resolved: ${syncResult.conflictsResolved || 0}`)
console.log(` Devices Contacted: ${syncResult.devicesContacted || 0}`)
console.log('')
if (syncResult.changes && syncResult.changes.length > 0) {
console.log('📝 Changes Made:')
syncResult.changes.forEach((change, index) => {
console.log(` ${index + 1}. ${change.type}: ${change.profile}`)
console.log(` ${change.description}`)
})
console.log('')
}
console.log('🚀 Next steps:')
console.log(' • Check status: node index.js device status')
console.log(' • List profiles: node index.js list-profiles')
console.log(' • Get help: node index.js --help')
} catch (error) {
await meshManager.cleanup()
throw error
}
} catch (error) {
console.error('❌ Failed to sync profiles:', error.message)
if (error.message.includes('Identity not found')) {
console.error('💡 Run "node index.js init" first to create an identity')
} else if (error.message.includes('Network dependencies not available')) {
console.error('💡 Install required dependencies:')
console.error(' npm install hyperswarm hypercore-id-encoding b4a')
} else if (error.message.includes('Not connected to mesh')) {
console.error('💡 Join the mesh first: node index.js device mesh join')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle device mesh join command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDeviceMeshJoin(parsed) {
try {
console.log('🔗 Joining identity mesh network...')
// Check network availability
const networkAvailable = await DeviceMeshManager.isNetworkAvailable()
if (!networkAvailable) {
console.log('❌ Network dependencies not available')
console.log('💡 Install network dependencies with:')
console.log(' npm install hyperswarm hypercore-id-encoding b4a')
process.exit(1)
}
// Create device mesh manager
const meshManager = new DeviceMeshManager()
try {
// Join the mesh
await meshManager.joinMesh()
console.log('✅ Successfully joined identity mesh!')
console.log('')
console.log('📋 Mesh Details:')
console.log(' Status: Connected')
console.log(' Discovery: Identity-based mesh discovery active')
console.log(' Sync: Real-time profile synchronization enabled')
console.log('')
console.log('🔗 Features Available:')
console.log(' • Automatic device discovery within identity')
console.log(' • Real-time profile synchronization')
console.log(' • Device status monitoring')
console.log(' • Conflict resolution for profile changes')
console.log('')
console.log('🚀 Next steps:')
console.log(' • Check status: node index.js device status')
console.log(' • Sync profiles: node index.js device sync')
console.log(' • Leave mesh: node index.js device mesh leave')
console.log('')
console.log('💡 The mesh will continue running in the background.')
console.log(' Use Ctrl+C to stop or run "device mesh leave" to disconnect.')
// Keep the process alive for mesh operations
console.log('Press Ctrl+C to stop mesh networking...')
await new Promise((resolve) => {
const cleanup = async () => {
console.log('\n🛑 Leaving mesh network...')
await meshManager.leaveMesh()
console.log('✅ Left mesh network')
resolve()
}
process.on('SIGINT', cleanup)
process.on('SIGTERM', cleanup)
})
} catch (error) {
await meshManager.cleanup()
throw error
}
} catch (error) {
console.error('❌ Failed to join mesh:', error.message)
if (error.message.includes('Identity not found')) {
console.error('💡 Run "node index.js init" first to create an identity')
} else if (error.message.includes('Network dependencies not available')) {
console.error('💡 Install required dependencies:')
console.error(' npm install hyperswarm hypercore-id-encoding b4a')
} else if (error.message.includes('Already connected to mesh')) {
console.error('💡 Already connected to mesh. Use "device status" to check status.')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle device mesh leave command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDeviceMeshLeave(parsed) {
try {
console.log('🔌 Leaving identity mesh network...')
// Check network availability
const networkAvailable = await DeviceMeshManager.isNetworkAvailable()
if (!networkAvailable) {
console.log('❌ Network dependencies not available')
console.log('💡 Install network dependencies with:')
console.log(' npm install hyperswarm hypercore-id-encoding b4a')
process.exit(1)
}
// Create device mesh manager
const meshManager = new DeviceMeshManager()
try {
// Leave the mesh
await meshManager.leaveMesh()
console.log('✅ Successfully left identity mesh!')
console.log('')
console.log('📋 Disconnection Details:')
console.log(' Status: Disconnected')
console.log(' Discovery: Stopped')
console.log(' Sync: Disabled')
console.log('')
console.log('🚀 Next steps:')
console.log(' • Join mesh again: node index.js device mesh join')
console.log(' • Check status: node index.js device status')
console.log(' • Get help: node index.js --help')
} catch (error) {
await meshManager.cleanup()
throw error
}
} catch (error) {
console.error('❌ Failed to leave mesh:', error.message)
if (error.message.includes('Identity not found')) {
console.error('💡 Run "node index.js init" first to create an identity')
} else if (error.message.includes('Network dependencies not available')) {
console.error('💡 Install required dependencies:')
console.error(' npm install hyperswarm hypercore-id-encoding b4a')
} else if (error.message.includes('Not connected to mesh')) {
console.error('💡 Not currently connected to mesh')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle device revoke command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDeviceRevoke(parsed) {
try {
const deviceId = parsed.positional[1] // positional[0] is "revoke", positional[1] is the device ID
if (!deviceId) {
console.error('❌ Device ID is required')
console.error('💡 Usage: node index.js device revoke <device-id>')
process.exit(1)
}
console.log(`🚫 Revoking device: ${deviceId}`)
// Check if identity is encrypted and prompt for passphrase if needed
let passphrase = null
try {
const identityData = await IdentityManager.loadIdentity()
// If we get here without error, identity is not encrypted
} catch (error) {
if (error.message.includes('encrypted but no passphrase provided')) {
passphrase = await getPassphrase('🔐 Identity is encrypted. Please enter your passphrase.')
} else {
throw error
}
}
// Revoke the device
await IdentityManager.revokeDevice(deviceId, passphrase)
console.log('✅ Device revoked successfully!')
console.log('')
console.log('📋 Revocation Details:')
console.log(` Device ID: ${deviceId}`)
console.log(` Status: Revoked`)
console.log(` Revoked: ${new Date().toISOString()}`)
console.log('')
console.log('⚠️ Security Notes:')
console.log(' • The revoked device can no longer sign data for this identity')
console.log(' • Existing signatures from this device remain valid')
console.log(' • The device cannot join the mesh network')
console.log(' • Profile sync will exclude the revoked device')
console.log('')
console.log('🚀 Next steps:')
console.log(' • Check device status: node index.js device status')
console.log(' • Sync profiles: node index.js device sync')
console.log(' • Get help: node index.js --help')
} catch (error) {
console.error('❌ Failed to revoke device:', error.message)
if (error.message.includes('Identity not found')) {
console.error('💡 Run "node index.js init" first to create an identity')
} else if (error.message.includes('Invalid passphrase')) {
console.error('💡 Please check your passphrase and try again')
} else if (error.message.includes('Device not found')) {
console.error('💡 Check the device ID and try again')
} else if (error.message.includes('Cannot revoke own device')) {
console.error('💡 You cannot revoke the device you are currently using')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle export-signing-config command
* @param {Object} parsed - Parsed command arguments
*/
async function handleExportSigningConfig(parsed) {
try {
console.log('📤 Exporting signing configuration...')
// Check if identity is encrypted and prompt for passphrase if needed
let passphrase = null
try {
const identityData = await IdentityManager.loadIdentity()
// If we get here without error, identity is not encrypted
} catch (error) {
if (error.message.includes('encrypted but no passphrase provided')) {
passphrase = await getPassphrase('🔐 Identity is encrypted. Please enter your passphrase.')
} else {
throw error
}
}
// Export signing configuration
const signingConfig = await IdentityManager.exportSigningConfig(passphrase)
const configJSON = JSON.stringify(signingConfig, null, 2)
console.log('✅ Signing configuration exported:')
console.log('')
console.log(configJSON)
console.log('')
console.log('📋 Configuration Details:')
console.log(' • identityPublicKey: Root identity public key')
console.log(' • devicePublicKey: This device\'s public key')
console.log(' • deviceSecretKey: This device\'s secret key (keep secure!)')
console.log(' • bootstrapProof: Proof linking device to identity')
console.log('')
console.log('🔒 Security Warning:')
console.log(' This configuration contains your device\'s secret key!')
console.log(' Only share with trusted applications that need signing capability.')
console.log(' Consider using environment variables or secure config files.')
console.log('')
console.log('🚀 Usage:')
console.log(' Applications can use this config with keet-identity-key to:')
console.log(' • Sign data with IdentityKey.attestData()')
console.log(' • Verify signatures with IdentityKey.verify()')
} catch (error) {
console.error('❌ Failed to export signing configuration:', error.message)
if (error.message.includes('Identity not found')) {
console.error('💡 Run "hyperident init" first to create an identity')
} else if (error.message.includes('Invalid passphrase')) {
console.error('💡 Please check your passphrase and try again')
} else if (error.message.includes('Device chain is not valid')) {
console.error('💡 Run "hyperident device verify" to check device chain')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle debug inspect command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDebugInspect(parsed) {
try {
console.log('🔍 Inspecting identity and system state...')
// Check if passphrase is provided via flag
let passphrase = null
if (parsed.flags.includes('--passphrase')) {
passphrase = await getPassphrase('🔐 Enter passphrase for encrypted identity.')
}
// Run identity inspection
const inspection = await DebugManager.inspectIdentity(passphrase)
console.log('✅ Identity inspection completed!')
console.log('')
console.log('📋 Inspection Results:')
console.log(JSON.stringify(inspection, null, 2))
// Provide summary
console.log('')
console.log('📊 Summary:')
console.log(` Identity Found: ${inspection.identity ? 'Yes' : 'No'}`)
console.log(` Device Chain: ${inspection.deviceChain ? 'Yes' : 'No'}`)
console.log(` Profiles: ${inspection.profiles ? Object.keys(inspection.profiles).length : 0}`)
console.log(` Errors: ${inspection.errors.length}`)
if (inspection.errors.length > 0) {
console.log('')
console.log('⚠️ Issues Found:')
inspection.errors.forEach((error, index) => {
console.log(` ${index + 1}. ${error}`)
})
}
console.log('')
console.log('🚀 Next steps:')
console.log(' • Run health check: hyperident debug health')
console.log(' • Check network: hyperident debug network')
console.log(' • Get help: hyperident --help')
} catch (error) {
console.error('❌ Failed to inspect identity:', error.message)
if (error.message.includes('Invalid passphrase')) {
console.error('💡 Please check your passphrase and try again')
}
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle debug health command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDebugHealth(parsed) {
try {
console.log('🏥 Running comprehensive health check...')
// Run health check
const healthCheck = await DebugManager.healthCheck()
console.log('✅ Health check completed!')
console.log('')
console.log('📋 Health Check Results:')
console.log(JSON.stringify(healthCheck, null, 2))
// Provide summary
console.log('')
console.log('📊 Health Summary:')
console.log(` Overall Status: ${healthCheck.status}`)
console.log(` Identity Health: ${healthCheck.identity?.status || 'Unknown'}`)
console.log(` Device Health: ${healthCheck.device?.status || 'Unknown'}`)
console.log(` Files Health: ${healthCheck.files?.status || 'Unknown'}`)
console.log(` System Health: ${healthCheck.system?.status || 'Unknown'}`)
if (healthCheck.issues && healthCheck.issues.length > 0) {
console.log('')
console.log('⚠️ Health Issues:')
healthCheck.issues.forEach((issue, index) => {
console.log(` ${index + 1}. ${issue.severity}: ${issue.message}`)
})
}
console.log('')
console.log('🚀 Next steps:')
console.log(' • Inspect identity: hyperident debug inspect')
console.log(' • Check trust: hyperident debug trust')
console.log(' • Get help: hyperident --help')
} catch (error) {
console.error('❌ Failed to run health check:', error.message)
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle debug network command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDebugNetwork(parsed) {
try {
console.log('🌐 Running network diagnostics...')
// Run network diagnostics
const networkDiag = await DebugManager.runNetworkDiagnostics()
console.log('✅ Network diagnostics completed!')
console.log('')
console.log('📋 Network Diagnostics Results:')
console.log(JSON.stringify(networkDiag, null, 2))
// Provide summary
console.log('')
console.log('📊 Network Summary:')
console.log(` Dependencies: ${networkDiag.dependencies?.status || 'Unknown'}`)
console.log(` Connectivity: ${networkDiag.connectivity?.status || 'Unknown'}`)
console.log(` DHT Status: ${networkDiag.dht?.status || 'Unknown'}`)
console.log(` Swarm Status: ${networkDiag.swarm?.status || 'Unknown'}`)
if (networkDiag.issues && networkDiag.issues.length > 0) {
console.log('')
console.log('⚠️ Network Issues:')
networkDiag.issues.forEach((issue, index) => {
console.log(` ${index + 1}. ${issue.severity}: ${issue.message}`)
})
}
console.log('')
console.log('🚀 Next steps:')
console.log(' • Check system info: hyperident debug system')
console.log(' • Run health check: hyperident debug health')
console.log(' • Get help: hyperident --help')
} catch (error) {
console.error('❌ Failed to run network diagnostics:', error.message)
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle debug trust command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDebugTrust(parsed) {
try {
console.log('🔐 Verifying trust relationships...')
// Run trust verification
const trustVerification = await DebugManager.verifyTrust()
console.log('✅ Trust verification completed!')
console.log('')
console.log('📋 Trust Verification Results:')
console.log(JSON.stringify(trustVerification, null, 2))
// Provide summary
console.log('')
console.log('📊 Trust Summary:')
console.log(` Identity Trust: ${trustVerification.identity?.status || 'Unknown'}`)
console.log(` Device Trust: ${trustVerification.device?.status || 'Unknown'}`)
console.log(` Chain Trust: ${trustVerification.chain?.status || 'Unknown'}`)
console.log(` Signature Trust: ${trustVerification.signatures?.status || 'Unknown'}`)
if (trustVerification.issues && trustVerification.issues.length > 0) {
console.log('')
console.log('⚠️ Trust Issues:')
trustVerification.issues.forEach((issue, index) => {
console.log(` ${index + 1}. ${issue.severity}: ${issue.message}`)
})
}
console.log('')
console.log('🚀 Next steps:')
console.log(' • Inspect identity: hyperident debug inspect')
console.log(' • Run health check: hyperident debug health')
console.log(' • Get help: hyperident --help')
} catch (error) {
console.error('❌ Failed to verify trust:', error.message)
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Handle debug system command
* @param {Object} parsed - Parsed command arguments
*/
async function handleDebugSystem(parsed) {
try {
console.log('💻 Gathering system information...')
// Get system information
const systemInfo = await DebugManager.getSystemInfo()
console.log('✅ System information gathered!')
console.log('')
console.log('📋 System Information:')
console.log(JSON.stringify(systemInfo, null, 2))
// Provide summary
console.log('')
console.log('📊 System Summary:')
console.log(` Platform: ${systemInfo.node?.platform || 'Unknown'}`)
console.log(` Node.js: ${systemInfo.node?.version || 'Unknown'}`)
console.log(` Architecture: ${systemInfo.node?.arch || 'Unknown'}`)
console.log(` Memory: ${systemInfo.memory ? `${Math.round(systemInfo.memory.heapUsed / 1024 / 1024)}MB used / ${Math.round(systemInfo.memory.heapTotal / 1024 / 1024)}MB total` : 'Unknown'}`)
console.log(` Hyperident: ${systemInfo.hyperident?.version || 'Unknown'}`)
if (systemInfo.dependencies) {
console.log('')
console.log('📦 Dependencies:')
Object.entries(systemInfo.dependencies).forEach(([name, info]) => {
console.log(` ${name}: ${info.status} ${info.version ? `(${info.version})` : ''}`)
})
}
console.log('')
console.log('🚀 Next steps:')
console.log(' • Run network diagnostics: hyperident debug network')
console.log(' • Run health check: hyperident debug health')
console.log(' • Get help: hyperident --help')
} catch (error) {
console.error('❌ Failed to gather system information:', error.message)
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
/**
* Helper function to get passphrase input
* @param {string} prompt - Prompt message
* @returns {Promise<string>} Entered passphrase
*/
async function getPassphrase(prompt) {
// Import readline for passphrase input
const readline = await import('readline')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
// Helper function to get hidden input
const getHiddenInput = (promptText) => {
return new Promise((resolve) => {
process.stdout.write(promptText + '\nEnter passphrase: ')
process.stdin.setRawMode(true)
process.stdin.resume()
process.stdin.setEncoding('utf8')
let input = ''
const onData = (char) => {
if (char === '\u0003') { // Ctrl+C
process.exit(1)
} else if (char === '\r' || char === '\n') {
process.stdin.setRawMode(false)
process.stdin.pause()
process.stdin.removeListener('data', onData)
process.stdout.write('\n')
resolve(input)
} else if (char === '\u007f') { // Backspace
if (input.length > 0) {
input = input.slice(0, -1)
process.stdout.write('\b \b')
}
} else {
input += char
process.stdout.write('*')
}
}
process.stdin.on('data', onData)
})
}
const passphrase = await getHiddenInput(prompt)
rl.close()
return passphrase
}
/**
* Parse command line arguments
* @param {string[]} argv - Process arguments
* @returns {Object} Parsed arguments
*/
function parseArgs(argv) {
const args = argv.slice(2) // Remove 'node' and script name
if (args.length === 0) {
return { command: 'help', flags: [], positional: [], raw: [] }
}
const command = args[0]
const flags = []
const positional = []
for (let i = 1; i < args.length; i++) {
const arg = args[i]
if (arg.startsWith('--') || arg.startsWith('-')) {
flags.push(arg)
// Check if this flag expects a value (like --device-name or --timeout)
if ((arg === '--device-name' || arg === '--timeout') && i + 1 < args.length && !args[i + 1].startsWith('--')) {
// Add the value as the next flag for easy extraction
flags.push(args[i + 1])
i++ // Skip the next argument since we consumed it
}
} else {
positional.push(arg)
}
}
return {
command,
flags,
positional,
raw: args
}
}
/**
* Validate command arguments
* @param {Object} parsed - Parsed arguments
* @returns {Object} Validation result
*/
function validateArgs(parsed) {
const { command, flags, positional } = parsed
switch (command) {
case 'init':
// Validate init command
const validInitFlags = ['--with-passphrase', '--device-name']
// Filter out device name values when validating flags
const deviceNameIndex = flags.indexOf('--device-name')
let flagsToValidate = [...flags]
if (deviceNameIndex !== -1 && deviceNameIndex + 1 < flags.length) {
// Remove the device name value from validation
flagsToValidate.splice(deviceNameIndex + 1, 1)
}
const invalidFlags = flagsToValidate.filter(flag => !validInitFlags.includes(flag))
if (invalidFlags.length > 0) {
return { valid: false, error: `Invalid flag(s) for init: ${invalidFlags.join(', ')}` }
}
// Check if --device-name has a value
if (deviceNameIndex !== -1) {
if (deviceNameIndex + 1 >= flags.length || flags[deviceNameIndex + 1].startsWith('--')) {
return { valid: false, error: '--device-name flag requires a value' }
}
}
if (positional.length > 0) {
return { valid: false, error: 'Init command does not accept positional arguments' }
}
break
case 'create-profile':
// Validate create-profile command
if (positional.length === 0) {
return { valid: false, error: 'Profile name is required for create-profile command' }
}
if (positional.length > 2) {
return { valid: false, error: 'Too many arguments for create-profile command' }
}
if (flags.length > 0) {
return { valid: false, error: 'Create-profile command does not accept flags' }
}
break
case 'list-profiles':
// Validate list-profiles command
if (positional.length > 0) {
return { valid: false, error: 'List-profiles command does not accept arguments' }
}
if (flags.length > 0) {
return { valid: false, error: 'List-profiles command does not accept flags' }
}
break
case 'delete-profile':
// Validate delete-profile command
if (positional.length === 0) {
return { valid: false, error: 'Profile name is required for delete-profile command' }
}
if (positional.length > 1) {
return { valid: false, error: 'Too many arguments for delete-profile command' }
}
if (flags.length > 0) {
return { valid: false, error: 'Delete-profile command does not accept flags' }
}
break
case 'address':
// Validate address command
if (positional.length === 0) {
return { valid: false, error: 'Profile name is required for address command' }
}
if (positional.length > 1) {
return { valid: false, error: 'Too many arguments for address command' }
}
const validAddressFlags = ['--cached']
const invalidAddressFlags = flags.filter(flag => !validAddressFlags.includes(flag))
if (invalidAddressFlags.length > 0) {
return { valid: false, error: `Invalid flag(s) for address: ${invalidAddressFlags.join(', ')}` }
}
break
case 'refresh-addresses':
// Validate refresh-addresses command
if (positional.length > 0) {
return { valid: false, error: 'Refresh-addresses command does not accept arguments' }
}
if (flags.length > 0) {
return { valid: false, error: 'Refresh-addresses command does not accept flags' }
}
break
case 'export-addresses':
// Validate export-addresses command
if (positional.length > 1) {
return { valid: false, error: 'Too many arguments for export-addresses command' }
}
const validExportFlags = ['--enhanced']
const invalidExportFlags = flags.filter(flag => !validExportFlags.includes(flag))
if (invalidExportFlags.length > 0) {
return { valid: false, error: `Invalid flag(s) for export-addresses: ${invalidExportFlags.join(', ')}` }
}
break
case 'sign':
// Validate sign command
if (positional.length === 0) {
return { valid: false, error: 'File path is required for sign command' }
}
if (positional.length > 1) {
return { valid: false, error: 'Too many arguments for sign command' }
}
if (flags.length > 0) {
return { valid: false, error: 'Sign command does not accept flags' }
}
break
case 'verify':
// Validate verify command
if (positional.length < 2) {
return { valid: false, error: 'Both signature file and original file are required for verify command' }
}
if (positional.length > 2) {
return { valid: false, error: 'Too many arguments for verify command' }
}
if (flags.length > 0) {
return { valid: false, error: 'Verify command does not accept flags' }
}
break
case 'export-signing-config':
// Validate export-signing-config command
if (positional.length > 0) {
return { valid: false, error: 'Export-signing-config command does not accept arguments' }
}
if (flags.length > 0) {
return { valid: false, error: 'Export-signing-config command does not accept flags' }
}
break
case 'device':
// Validate device command
if (positional.length === 0) {
return { valid: false, error: 'Device subcommand is required (list, export, import, verify)' }
}
const deviceSubcommand = positional[0]
const validDeviceSubcommands = ['list', 'export', 'import', 'verify', 'bootstrap', 'create-network', 'share', 'join', 'status', 'sync', 'mesh', 'revoke']
if (!validDeviceSubcommands.includes(deviceSubcommand)) {
return { valid: false, error: `Invalid device subcommand: ${deviceSubcommand}` }
}
// Validate device mesh specifically
if (deviceSubcommand === 'mesh') {
if (positional.length < 2) {
return { valid: false, error: 'Device mesh requires a subcommand (join, leave)' }
}
const meshSubcommand = positional[1]
const validMeshSubcommands = ['join', 'leave']
if (!validMeshSubcommands.includes(meshSubcommand)) {
return { valid: false, error: `Invalid mesh subcommand: ${meshSubcommand}` }
}
}
// Validate device revoke specifically
if (deviceSubcommand === 'revoke') {
if (positional.length < 2) {
return { valid: false, error: 'Device revoke requires a device ID' }
}
}
// Validate device import specifically
if (deviceSubcommand === 'import') {
if (positional.length < 2) {
return { valid: false, error: 'Device import requires a bundle file' }
}
// Check for --device-name flag
const deviceNameIndex = flags.indexOf('--device-name')
if (deviceNameIndex === -1) {
return { valid: false, error: 'Device import requires --device-name flag' }
}
if (deviceNameIndex + 1 >= flags.length || flags[deviceNameIndex + 1].startsWith('--')) {
return { valid: false, error: '--device-name flag requires a value' }
}
}
// Validate device join specifically
if (deviceSubcommand === 'join') {
if (positional.length < 2) {
return { valid: false, error: 'Device join requires a sharing code' }
}
// Check for --device-name flag
const deviceNameIndex = flags.indexOf('--device-name')
if (deviceNameIndex === -1) {
return { valid: false, error: 'Device join requires --device-name flag' }
}
if (deviceNameIndex + 1 >= flags.length || flags[deviceNameIndex + 1].startsWith('--')) {
return { valid: false, error: '--device-name flag requires a value' }
}
}
// Validate device share specifically
if (deviceSubcommand === 'share') {
// Check for --timeout flag if present
const timeoutIndex = flags.indexOf('--timeout')
if (timeoutIndex !== -1) {
if (timeoutIndex + 1 >= flags.length || flags[timeoutIndex + 1].startsWith('--')) {
return { valid: false, error: '--timeout flag requires a value' }
}
const timeoutValue = parseInt(flags[timeoutIndex + 1])
if (isNaN(timeoutValue) || timeoutValue <= 0) {
return { valid: false, error: '--timeout value must be a positive number' }
}
}
}
break
case 'debug':
// Validate debug command
if (positional.length === 0) {
return { valid: false, error: 'Debug subcommand is required (inspect, health, network, trust, system)' }
}
const debugSubcommand = positional[0]
const validDebugSubcommands = ['inspect', 'health', 'network', 'trust', 'system']
if (!validDebugSubcommands.includes(debugSubcommand)) {
return { valid: false, error: `Invalid debug subcommand: ${debugSubcommand}` }
}
// Validate debug inspect specifically
if (debugSubcommand === 'inspect') {
const validInspectFlags = ['--passphrase']
const invalidInspectFlags = flags.filter(flag => !validInspectFlags.includes(flag))
if (invalidInspectFlags.length > 0) {
return { valid: false, error: `Invalid flag(s) for debug inspect: ${invalidInspectFlags.join(', ')}` }
}
} else {
// Other debug commands don't accept flags
if (flags.length > 0) {
return { valid: false, error: `Debug ${debugSubcommand} command does not accept flags` }
}
}
// Debug commands don't accept additional positional arguments
if (positional.length > 1) {
return { valid: false, error: `Too many arguments for debug ${debugSubcommand} command` }
}
break
case 'help':
case '--help':
case '-h':
case 'version':
case '--version':
case '-v':
// These commands are always valid
break
default:
return { valid: false, error: `Unknown command: ${command}` }
}
return { valid: true }
}
/**
* Display error with helpful suggestions
* @param {string} error - Error message
* @param {string} command - Command that failed
*/
function displayError(error, command = null) {
console.error(`❌ Error: ${error}`)
// Provide helpful suggestions based on the error
if (command) {
switch (command) {
case 'create-profile':
console.error('💡 Usage: hyperident create-profile <name> [description]')
console.error(' Example: hyperident create-profile work "Work profile"')
break
case 'delete-profile':
console.error('💡 Usage: hyperident delete-profile <name>')
console.error(' Example: hyperident delete-profile work')
break
case 'init':
console.error('💡 Usage: hyperident init [--with-passphrase]')
break
default:
console.error('💡 Use "hyperident --help" for usage information')
}
} else {
console.error('💡 Use "hyperident --help" for usage information')
}
}
// Background OTA via pgh — content-addressed store, NO Hyperdrive/blobs.
// `pgh app update <name>` resolves the latest release from package.json's
// pgh.upgrade, fetches any new tree closure into the content store, and
// ATOMICALLY materializes it into the install dir (scratch build + swap),
// safe to run while a command executes. This replaces the former pear-runtime
// Hyperdrive updater, which kept an unbounded append-only blob corestore under
// ~/.config/hyperident/pear-runtime (it grew on every release and — because
// initPearRuntime was called unconditionally — on every CLI invocation).
async function startBackgroundUpdate () {
try {
const fs = (await import('fs')).default
const path = (await import('path')).default
const os = (await import('os')).default
const appDir = path.dirname(new URL(import.meta.url).pathname)
const pkg = JSON.parse(fs.readFileSync(path.join(appDir, 'package.json'), 'utf-8'))
const upgrade = pkg.pgh && pkg.pgh.upgrade
if (!upgrade) return
const appName = (pkg.pgh && pkg.pgh.bin) || pkg.name || 'hyperident'
// Resolve the pgh CLI: prefer ~/.local/bin/pgh, else the store-installed
// bare runtime + entry. Bail if pgh isn't on this machine (e.g. dev source).
let argv = null
const launcher = path.join(os.homedir(), '.local', 'bin', 'pgh')
if (fs.existsSync(launcher)) argv = [launcher]
else {
const storeRoot = process.env.PGH_STORE_ROOT || path.join(os.homedir(), '.local', 'share', 'store')
const bare = path.join(storeRoot, 'apps', 'pgh', 'runtime', 'bare')
const entry = path.join(storeRoot, 'apps', 'pgh', 'bin', 'pgh.bare.js')
if (fs.existsSync(bare) && fs.existsSync(entry)) argv = [bare, entry]
}
if (!argv) return
// Cross-runtime spawn via the imports-map bridge: child_process → bare-subprocess
// under bare (deployed installs), node's child_process under node (dev). Both
// provide spawn(); failure here is caught and OTA is silently skipped.
const subprocess = (await import('child_process')).default || (await import('child_process'))
const proc = subprocess.spawn(argv[0], [...argv.slice(1), 'app', 'update', appName], {
stdio: 'ignore',
detached: true
})
proc.on('error', () => {})
if (proc.unref) proc.unref()
} catch {}
}
/**
* Main CLI entry point
*/
async function main() {
// Start background OTA via pgh (content-addressed, non-blocking, best-effort).
// Gated so it never fires on `--no-updates`. Unlike the old pear-runtime path,
// this no longer balloons a corestore — and it only spawns when pgh + a
// pgh.upgrade link are both present (i.e. a real pgh install).
if (!process.argv.includes('--no-updates')) startBackgroundUpdate()
try {
const parsed = parseArgs(process.argv)
// Validate arguments before processing
const validation = validateArgs(parsed)
if (!validation.valid) {
displayError(validation.error, parsed.command)
process.exit(1)
}
switch (parsed.command) {
case 'init':
await handleInit(parsed)
break
case 'create-profile':
await handleCreateProfile(parsed)
break
case 'list-profiles':
await handleListProfiles(parsed)
break
case 'delete-profile':
await handleDeleteProfile(parsed)
break
case 'address':
await handleAddress(parsed)
break
case 'refresh-addresses':
await handleRefreshAddresses(parsed)
break
case 'export-addresses':
await handleExportAddresses(parsed)
break
case 'sign':
await handleSign(parsed)
break
case 'verify':
await handleVerify(parsed)
break
case 'export-signing-config':
await handleExportSigningConfig(parsed)
break
case 'debug':
// Handle debug subcommands
const debugSubcommand = parsed.positional[0]
switch (debugSubcommand) {
case 'inspect':
await handleDebugInspect(parsed)
break
case 'health':
await handleDebugHealth(parsed)
break
case 'network':
await handleDebugNetwork(parsed)
break
case 'trust':
await handleDebugTrust(parsed)
break
case 'system':
await handleDebugSystem(parsed)
break
default:
displayError(`Unknown debug subcommand: ${debugSubcommand || '(none)'}`)
console.error('💡 Available debug commands: inspect, health, network, trust, system')
process.exit(1)
}
break
case 'device':
// Handle device subcommands
const deviceSubcommand = parsed.positional[0]
switch (deviceSubcommand) {
case 'list':
await handleDeviceList(parsed)
break
case 'export':
await handleDeviceExport(parsed)
break
case 'import':
await handleDeviceImport(parsed)
break
case 'verify':
await handleDeviceVerify(parsed)
break
case 'bootstrap':
await handleDeviceBootstrap(parsed)
break
case 'create-network':
await handleDeviceCreateNetwork(parsed)
break
case 'share':
await handleDeviceShare(parsed)
break
case 'join':
await handleDeviceJoin(parsed)
break
case 'status':
await handleDeviceStatus(parsed)
break
case 'sync':
await handleDeviceSync(parsed)
break
case 'mesh':
// Handle mesh subcommands
const meshSubcommand = parsed.positional[1]
switch (meshSubcommand) {
case 'join':
await handleDeviceMeshJoin(parsed)
break
case 'leave':
await handleDeviceMeshLeave(parsed)
break
default:
displayError(`Unknown mesh subcommand: ${meshSubcommand || '(none)'}`)
console.error('💡 Available mesh commands: join, leave')
process.exit(1)
}
break
case 'revoke':
await handleDeviceRevoke(parsed)
break
default:
displayError(`Unknown device subcommand: ${deviceSubcommand || '(none)'}`)
console.error('💡 Available device commands: list, export, import, verify, share, join, status, sync, mesh, revoke')
process.exit(1)
}
break
case '--help':
case '-h':
case 'help':
showHelp()
break
case '--version':
case '-v':
case 'version':
showVersion()
break
default:
displayError(`Unknown command: ${parsed.command}`)
process.exit(1)
}
} catch (error) {
console.error('❌ Error:', error.message)
if (process.env.DEBUG) {
console.error(error.stack)
}
process.exit(2)
}
}
// Run CLI if this file is executed directly
const argv = typeof process !== 'undefined' ? process.argv : (typeof Bare !== 'undefined' ? Bare.argv : [])
if (argv[1] && import.meta.url === `file://${argv[1]}`) {
main()
} else if (typeof Bare !== 'undefined') {
main()
}
export { main, parseArgs, validateArgs, displayError, showHelp, showVersion, handleInit, handleCreateProfile, handleListProfiles, handleDeleteProfile, handleAddress }