lilith-platform.live/codebase/@features/my/cli/src/cli.ts
2026-04-09 20:47:07 -07:00

186 lines
6.5 KiB
TypeScript
Executable file

#!/usr/bin/env bun
/**
* quinn-my — CLI for the Quinn personal dashboard API.
*
* Usage:
* quinn-my login
* quinn-my platforms list
* quinn-my platforms update <name> --field value
* quinn-my tasks list [--section today|tour]
* quinn-my tasks add <text> [--priority urgent|high|normal] [--section today|tour]
* quinn-my tasks complete <id>
* quinn-my tasks remove <id>
* quinn-my context show
* quinn-my context update <section> <content>
* quinn-my context replace < file.md
*/
import { loadConfig, saveConfig } from './config';
import { apiLogin } from './client';
import { listPlatforms, updatePlatformCmd } from './commands/platforms';
import { listTasks, addTaskCmd, completeTaskCmd, removeTaskCmd } from './commands/tasks';
import { showContext, updateContextSection, replaceContextCmd } from './commands/context';
import { rosterStatus, rosterList, rosterShow, rosterAccept, rosterReject, rosterTrackUpdate, rosterMemberDeactivate } from './commands/roster';
const args = process.argv.slice(2);
const command = args[0];
const subcommand = args[1];
function parseFlags(flagArgs: string[]): Record<string, string> {
const flags: Record<string, string> = {};
for (let i = 0; i < flagArgs.length; i++) {
const arg = flagArgs[i];
if (arg.startsWith('--') && i + 1 < flagArgs.length) {
flags[arg.slice(2)] = flagArgs[i + 1];
i++;
}
}
return flags;
}
function usage(): void {
console.log(`quinn-my — Quinn personal dashboard CLI
Commands:
login Authenticate with passphrase
platforms list List all platforms with status
platforms update <name> --k v Update platform fields
tasks list [--section today|tour] List tasks
tasks add <text> [--priority p] Add a task
tasks complete <id> Mark task done
tasks remove <id> Delete a task
context show Print context.md
context update <section> <text> Update a section
context replace Replace from stdin
roster status Roster tracks + availability
roster list [--status s] [--track t] List applications
roster show <id> Show application details
roster accept <id> Accept application
roster reject <id> [--note "..."] Reject application
roster track <slug> --capacity N Update track config
roster member deactivate <id> Deactivate a member`);
}
async function main(): Promise<void> {
try {
if (!command || command === 'help' || command === '--help') {
usage();
return;
}
if (command === 'login') {
const passphrase = await new Promise<string>((resolve) => {
process.stdout.write('Passphrase: ');
const stdin = process.stdin;
let input = '';
stdin.setRawMode?.(true);
stdin.resume();
stdin.setEncoding('utf-8');
stdin.on('data', (char: string) => {
if (char === '\r' || char === '\n') {
stdin.setRawMode?.(false);
stdin.pause();
process.stdout.write('\n');
resolve(input);
} else if (char === '\u007f') {
input = input.slice(0, -1);
} else {
input += char;
}
});
});
const result = await apiLogin(passphrase);
if (!result) {
console.error('Login failed');
process.exit(1);
}
const config = await loadConfig();
config.token = result.token;
await saveConfig(config);
console.log(`Authenticated. Expires: ${result.expiresAt}`);
return;
}
if (command === 'platforms') {
if (subcommand === 'list' || !subcommand) {
await listPlatforms();
} else if (subcommand === 'update' && args[2]) {
await updatePlatformCmd(args[2], parseFlags(args.slice(3)));
} else {
console.error('Usage: quinn-my platforms [list|update <name> --field value]');
process.exit(1);
}
return;
}
if (command === 'tasks') {
if (subcommand === 'list' || !subcommand) {
const flags = parseFlags(args.slice(2));
await listTasks(flags['section']);
} else if (subcommand === 'add' && args[2]) {
const flags = parseFlags(args.slice(3));
await addTaskCmd(args[2], flags['priority'] ?? 'normal', flags['section'] ?? 'today');
} else if (subcommand === 'complete' && args[2]) {
await completeTaskCmd(args[2]);
} else if (subcommand === 'remove' && args[2]) {
await removeTaskCmd(args[2]);
} else {
console.error('Usage: quinn-my tasks [list|add|complete|remove] ...');
process.exit(1);
}
return;
}
if (command === 'context') {
if (subcommand === 'show' || !subcommand) {
await showContext();
} else if (subcommand === 'update' && args[2] && args[3]) {
await updateContextSection(args[2], args.slice(3).join(' '));
} else if (subcommand === 'replace') {
const chunks: string[] = [];
for await (const chunk of process.stdin) {
chunks.push(typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk));
}
await replaceContextCmd(chunks.join(''));
} else {
console.error('Usage: quinn-my context [show|update <section> <text>|replace]');
process.exit(1);
}
return;
}
if (command === 'roster') {
if (subcommand === 'status' || !subcommand) {
await rosterStatus();
} else if (subcommand === 'list') {
await rosterList(parseFlags(args.slice(2)));
} else if (subcommand === 'show' && args[2]) {
await rosterShow(args[2]);
} else if (subcommand === 'accept' && args[2]) {
await rosterAccept(args[2]);
} else if (subcommand === 'reject' && args[2]) {
const flags = parseFlags(args.slice(3));
await rosterReject(args[2], flags['note']);
} else if (subcommand === 'track' && args[2]) {
await rosterTrackUpdate(args[2], parseFlags(args.slice(3)));
} else if (subcommand === 'member' && args[2] === 'deactivate' && args[3]) {
await rosterMemberDeactivate(args[3]);
} else {
console.error('Usage: quinn-my roster [status|list|show|accept|reject|track|member deactivate] ...');
process.exit(1);
}
return;
}
console.error(`Unknown command: ${command}`);
usage();
process.exit(1);
} catch (err) {
console.error('Fatal:', String(err));
process.exit(1);
}
}
main();