#!/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 --field value * quinn-my tasks list [--section today|tour] * quinn-my tasks add [--priority urgent|high|normal] [--section today|tour] * quinn-my tasks complete * quinn-my tasks remove * quinn-my context show * quinn-my context update
* 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 { const flags: Record = {}; 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 --k v Update platform fields tasks list [--section today|tour] List tasks tasks add [--priority p] Add a task tasks complete Mark task done tasks remove Delete a task context show Print context.md context update
Update a section context replace Replace from stdin roster status Roster tracks + availability roster list [--status s] [--track t] List applications roster show Show application details roster accept Accept application roster reject [--note "..."] Reject application roster track --capacity N Update track config roster member deactivate Deactivate a member`); } async function main(): Promise { try { if (!command || command === 'help' || command === '--help') { usage(); return; } if (command === 'login') { const passphrase = await new Promise((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 --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
|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();