#!/usr/bin/env bun import { initCommand, listTemplates } from './commands/init.js'; import { fingerprintCommand } from './commands/fingerprint.js'; import { publishCommand } from './commands/publish.js'; import { rotateCommand } from './commands/rotate.js'; import { peerAddCommand, peerListCommand, peerVerifyCommand, peerRemoveCommand, } from './commands/peer.js'; import { dashboardCommand } from './commands/dashboard.js'; import { doctorCommand } from './commands/doctor.js'; const VERSION = '0.1.0'; const HELP = ` Shade CLI v${VERSION} Usage: shade [args] Commands: init [name] Scaffold a new Shade project --template Template to use (default: bun-server) --prekey-server Override prekey server URL fingerprint [address] Print your own or a peer's fingerprint publish Re-upload your bundle to the prekey server rotate Rotate the signed prekey --identity Rotate the full identity (destructive) peer add
Establish a session with a peer peer list List active sessions peer verify
Check a peer's fingerprint matches peer remove
Delete a session dashboard Open the observer dashboard in the browser doctor Diagnose setup issues help Show this message Config: Reads .shaderc.json from cwd, or env vars: SHADE_PREKEY_SERVER, SHADE_DB_PATH, SHADE_OBSERVER_TOKEN, SHADE_OBSERVER_URL, SHADE_ADDRESS `; async function main(): Promise { const args = process.argv.slice(2); const cmd = args[0]; try { switch (cmd) { case 'init': { const options = parseInitArgs(args.slice(1)); await initCommand(options); break; } case 'fingerprint': await fingerprintCommand(args[1]); break; case 'publish': await publishCommand(); break; case 'rotate': await rotateCommand({ identity: args.includes('--identity') }); break; case 'peer': { const sub = args[1]; if (sub === 'add') await peerAddCommand(requireArg(args[2], 'address')); else if (sub === 'list') await peerListCommand(); else if (sub === 'verify') await peerVerifyCommand( requireArg(args[2], 'address'), args.slice(3).join(' '), ); else if (sub === 'remove') await peerRemoveCommand(requireArg(args[2], 'address')); else { console.error(`Unknown peer subcommand: ${sub}`); process.exit(1); } break; } case 'dashboard': await dashboardCommand(); break; case 'doctor': await doctorCommand(); break; case 'help': case '--help': case '-h': case undefined: console.log(HELP); console.log('\nAvailable templates:'); for (const name of listTemplates()) console.log(` ${name}`); break; case '--version': case '-v': console.log(VERSION); break; default: console.error(`Unknown command: ${cmd}`); console.log(HELP); process.exit(1); } } catch (err) { console.error(`\x1b[31mError:\x1b[0m ${(err as Error).message}`); process.exit(1); } } function parseInitArgs(args: string[]): { name?: string; template?: string; prekeyServer?: string; } { const options: ReturnType = {}; for (let i = 0; i < args.length; i++) { if (args[i] === '--template') options.template = args[++i]; else if (args[i] === '--prekey-server') options.prekeyServer = args[++i]; else if (!args[i]!.startsWith('--')) options.name = args[i]; } return options; } function requireArg(arg: string | undefined, name: string): string { if (!arg) { console.error(`Missing required argument: ${name}`); process.exit(1); } return arg; } main();