#!/usr/bin/env bun import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import { execSync } from 'child_process'; const CONFIG_FILE = path.join(os.homedir(), '.note.conf'); interface Config { TOKEN?: string; REPO?: string; URL?: string; } // --- Check dependencies --- function checkDependency(cmd: string): boolean { try { execSync(`command -v ${cmd}`, { stdio: 'ignore' }); return true; } catch { return false; } } function checkDependencies(): void { const deps = ['curl', 'jq']; for (const cmd of deps) { if (!checkDependency(cmd)) { console.error(`❌ Missing dependency: ${cmd} (please install it)`); process.exit(1); } } } // --- Config management --- function createDefaultConfig(): void { const defaultConfig = `TOKEN= REPO= URL=https://cld-dkr-makuro-seafile.wibudev.com/api2 `; fs.writeFileSync(CONFIG_FILE, defaultConfig); } function editConfig(): void { if (!fs.existsSync(CONFIG_FILE)) { createDefaultConfig(); } const editor = process.env.EDITOR || 'vim'; try { execSync(`${editor} "${CONFIG_FILE}"`, { stdio: 'inherit' }); } catch (error) { console.error('❌ Failed to open editor'); process.exit(1); } } function loadConfig(): Config { if (!fs.existsSync(CONFIG_FILE)) { console.error(`⚠️ Config file not found at ${CONFIG_FILE}`); console.error('Run: node note.ts config to create/edit it.'); process.exit(1); } const configContent = fs.readFileSync(CONFIG_FILE, 'utf8'); const config: Config = {}; for (const line of configContent.split('\n')) { const trimmed = line.trim(); if (trimmed && !trimmed.startsWith('#')) { const [key, ...valueParts] = trimmed.split('='); if (key && valueParts.length > 0) { const value = valueParts.join('='); config[key as keyof Config] = value; } } } if (!config.TOKEN || !config.REPO) { console.error(`❌ Config invalid. Please set TOKEN=... and REPO=... inside ${CONFIG_FILE}`); process.exit(1); } // Set default URL if not provided if (!config.URL) { console.error(`❌ Config invalid. Please set URL=... inside ${CONFIG_FILE}`); process.exit(1); } return config; } // --- HTTP helpers --- function curlCommand(config: Config, options: string): string { try { return execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" ${options}`, { encoding: 'utf8' }); } catch (error) { console.error('❌ Request failed'); process.exit(1); } } function curlCommandSilent(config: Config, options: string): void { try { execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" ${options} >/dev/null`, { stdio: 'ignore' }); } catch (error) { console.error('❌ Request failed'); process.exit(1); } } // --- Commands --- function listFiles(config: Config): void { const response = curlCommand(config, `"${config.URL}/${config.REPO}/dir/?p=/"`); try { const files = JSON.parse(response); for (const file of files) { console.log(file.name); } } catch (error) { console.error('❌ Failed to parse response'); process.exit(1); } } function catFile(config: Config, fileName: string): void { const downloadUrlResponse = curlCommand(config, `"${config.URL}/${config.REPO}/file/?p=/${fileName}"`); const downloadUrl = downloadUrlResponse.replace(/"/g, ''); const content = curlCommand(config, `"${downloadUrl}"`); console.log(content); } function uploadFile(config: Config, localFile: string, remoteFile?: string): void { if (!fs.existsSync(localFile)) { console.error(`❌ File not found: ${localFile}`); process.exit(1); } const remoteName = remoteFile || path.basename(localFile); const uploadUrlResponse = curlCommand(config, `"${config.URL}/${config.REPO}/upload-link/?p=/"`); const uploadUrl = uploadUrlResponse.replace(/"/g, ''); try { execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" \ -F "file=@${localFile}" \ -F "filename=${remoteName}" \ -F "parent_dir=/" \ "${uploadUrl}" >/dev/null`, { stdio: 'ignore' }); console.log(`✅ Uploaded ${localFile} → ${remoteName}`); } catch (error) { console.error('❌ Upload failed'); process.exit(1); } } function removeFile(config: Config, fileName: string): void { curlCommandSilent(config, `-X DELETE "${config.URL}/${config.REPO}/file/?p=/${fileName}"`); console.log(`🗑️ Removed ${fileName}`); } function moveFile(config: Config, oldName: string, newName: string): void { try { execSync(`curl -s -X POST -H "Authorization: Token ${config.TOKEN}" \ -d "operation=rename" \ -d "newname=${newName}" \ "${config.URL}/${config.REPO}/file/?p=/${oldName}" >/dev/null`, { stdio: 'ignore' }); console.log(`✏️ Renamed ${oldName} → ${newName}`); } catch (error) { console.error('❌ Rename failed'); process.exit(1); } } function downloadFile(config: Config, remoteFile: string, localFile?: string): void { const localName = localFile || remoteFile; const downloadUrlResponse = curlCommand(config, `"${config.URL}/${config.REPO}/file/?p=/${remoteFile}"`); const downloadUrl = downloadUrlResponse.replace(/"/g, ''); try { execSync(`curl -s -H "Authorization: Token ${config.TOKEN}" "${downloadUrl}" -o "${localName}"`, { stdio: 'ignore' }); console.log(`⬇️ Downloaded ${remoteFile} → ${localName}`); } catch (error) { console.error('❌ Download failed'); process.exit(1); } } function showHelp(): void { console.log(`note - simple CLI for Seafile notes Usage: node note.ts ls List files node note.ts cat Show file content node note.ts cp [remote] Upload file node note.ts rm Remove file node note.ts mv Rename/move file node note.ts get [local] Download file node note.ts config Edit config (~/.note.conf) Config (~/.note.conf): TOKEN=your_seafile_token REPO=repos/ URL=your_seafile_url/api2`); } // --- Main --- function not3(): void { const args = process.argv.slice(2); const cmd = args[0] || 'help'; // Handle config command without dependency check if (cmd === 'config') { editConfig(); return; } // Check dependencies for other commands checkDependencies(); // Load config for other commands const config = loadConfig(); switch (cmd) { case 'ls': listFiles(config); break; case 'cat': if (!args[1]) { console.error('Usage: node note.ts cat '); process.exit(1); } catFile(config, args[1]); break; case 'cp': if (!args[1]) { console.error('Usage: node note.ts cp [remote_file]'); process.exit(1); } uploadFile(config, args[1], args[2]); break; case 'rm': if (!args[1]) { console.error('Usage: node note.ts rm '); process.exit(1); } removeFile(config, args[1]); break; case 'mv': if (!args[1] || !args[2]) { console.error('Usage: node note.ts mv '); process.exit(1); } moveFile(config, args[1], args[2]); break; case 'get': if (!args[1]) { console.error('Usage: node note.ts get [local_file]'); process.exit(1); } downloadFile(config, args[1], args[2]); break; case 'help': default: showHelp(); break; } }