133 lines
3.9 KiB
TypeScript
133 lines
3.9 KiB
TypeScript
|
|
#!/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();
|