import { readFile, writeFile } from 'node:fs/promises' import { basename, resolve } from 'node:path' import { Command } from 'commander' import { cliOutput } from '@/shared/utils/cli-output' import type { BotOption } from './shared' import { getClient } from './shared' interface FileResult { id?: string ref?: string roomId?: string roomRef?: string files?: string[] created?: string downloaded?: string filename?: string contentType?: string size?: number error?: string } export async function uploadAction( space: string, path: string, options: BotOption & { text?: string; markdown?: boolean; parent?: string }, ): Promise { try { const client = await getClient(options) const filePath = resolve(path) const content = await readFile(filePath) const message = await client.uploadFile( space, { content: new Blob([content]), filename: basename(filePath) }, { text: options.text, markdown: options.markdown, parentId: options.parent }, ) return { id: message.id, ref: message.ref, roomId: message.roomId, roomRef: message.roomRef, files: message.files, created: message.created, } } catch (error) { return { error: (error as Error).message } } } export async function downloadAction( contentRef: string, output: string | undefined, options: BotOption, ): Promise { try { const client = await getClient(options) const { data, filename, contentType } = await client.downloadContent(contentRef) // When no explicit output is given, confine the server-provided name to cwd. const outputPath = output ? resolve(output) : resolve(process.cwd(), basename(filename)) await writeFile(outputPath, Buffer.from(data)) return { downloaded: outputPath, filename, contentType, size: data.byteLength, } } catch (error) { return { error: (error as Error).message } } } export const fileCommand = new Command('file') .description('File commands') .addCommand( new Command('upload') .description('Upload a local file to a space') .argument('', 'Space/Room ID') .argument('', 'Local file path') .option('--text ', 'Optional message to send with the file') .option('--markdown', 'Treat --text as markdown') .option('--parent ', 'Reply within a thread (parent message ID)') .option('--bot ', 'Use specific bot') .option('--pretty', 'Pretty print JSON output') .action( async ( space: string, path: string, opts: BotOption & { text?: string; markdown?: boolean; parent?: string }, ) => { cliOutput(await uploadAction(space, path, opts), opts.pretty) }, ), ) .addCommand( new Command('download') .description('Download a file attachment by content URL or ID') .argument('', 'File content URL (from message.files) or content ID') .argument('[output]', 'Output path (defaults to original filename)') .option('--bot ', 'Use specific bot') .option('--pretty', 'Pretty print JSON output') .action(async (content: string, output: string | undefined, opts: BotOption) => { cliOutput(await downloadAction(content, output, opts), opts.pretty) }), )