186 lines
6.5 KiB
TypeScript
Executable file
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();
|