Files
Shade/packages/shade-cli/src/cli.ts

133 lines
3.9 KiB
TypeScript
Raw Normal View History

#!/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 <command> [args]
Commands:
init [name] Scaffold a new Shade project
--template <name> Template to use (default: bun-server)
--prekey-server <url> 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 <address> Establish a session with a peer
peer list List active sessions
peer verify <address> <fingerprint>
Check a peer's fingerprint matches
peer remove <address> 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<void> {
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<typeof parseInitArgs> = {};
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();