WIP: A simple cli for daily tangled use cases and AI integration. This is for my personal use right now, but happy if others get mileage from it! :)
at main 96 lines 2.7 kB view raw
1import * as fs from 'node:fs/promises'; 2import * as process from 'node:process'; 3 4/** 5 * Read body content from various sources following GitHub CLI patterns 6 * 7 * @param bodyString - Direct body text from --body flag 8 * @param bodyFilePath - File path or '-' for stdin 9 * @returns Body content or undefined if no input provided 10 * @throws Error if both bodyString and bodyFilePath are provided 11 * @throws Error if file doesn't exist or cannot be read 12 */ 13export async function readBodyInput( 14 bodyString?: string, 15 bodyFilePath?: string 16): Promise<string | undefined> { 17 // Error if both are provided 18 if (bodyString !== undefined && bodyFilePath !== undefined) { 19 throw new Error('Cannot specify both --body and --body-file. Choose one input method.'); 20 } 21 22 // Direct string input (including empty string) 23 if (bodyString !== undefined) { 24 return bodyString; 25 } 26 27 // File or stdin input 28 if (bodyFilePath) { 29 // Read from stdin 30 if (bodyFilePath === '-') { 31 return await readFromStdin(); 32 } 33 34 // Read from file 35 try { 36 const stats = await fs.stat(bodyFilePath); 37 38 if (stats.isDirectory()) { 39 throw new Error(`'${bodyFilePath}' is a directory, not a file`); 40 } 41 42 const content = await fs.readFile(bodyFilePath, 'utf-8'); 43 return content; 44 } catch (error) { 45 if (error instanceof Error) { 46 // Re-throw our custom directory error 47 if (error.message.includes('is a directory')) { 48 throw error; 49 } 50 51 // Handle ENOENT (file not found) 52 if ('code' in error && error.code === 'ENOENT') { 53 throw new Error(`File not found: ${bodyFilePath}`); 54 } 55 56 // Handle EACCES (permission denied) 57 if ('code' in error && error.code === 'EACCES') { 58 throw new Error(`Permission denied: ${bodyFilePath}`); 59 } 60 61 throw new Error(`Failed to read file '${bodyFilePath}': ${error.message}`); 62 } 63 64 throw new Error(`Failed to read file '${bodyFilePath}': Unknown error`); 65 } 66 } 67 68 // No input provided 69 return undefined; 70} 71 72/** 73 * Read content from stdin 74 * @returns Content from stdin as string 75 */ 76async function readFromStdin(): Promise<string> { 77 return new Promise((resolve, reject) => { 78 const chunks: Buffer[] = []; 79 80 process.stdin.on('data', (chunk: Buffer) => { 81 chunks.push(chunk); 82 }); 83 84 process.stdin.on('end', () => { 85 const content = Buffer.concat(chunks).toString('utf-8'); 86 resolve(content); 87 }); 88 89 process.stdin.on('error', (error: Error) => { 90 reject(new Error(`Failed to read from stdin: ${error.message}`)); 91 }); 92 93 // Resume stdin in case it's paused 94 process.stdin.resume(); 95 }); 96}