uapi-sdk
Version:
CLI client for Library Navigator API
225 lines • 7.6 kB
JavaScript
import { Command } from 'commander';
import chalk from 'chalk';
import axios from 'axios';
import ora from 'ora';
import dotenv from 'dotenv';
import * as fs from 'fs';
// Load environment variables
dotenv.config();
// Define API paths and configuration
const BASE_URL = process.env.API_BASE_URL || 'https://lymanlabs--library-navigator-api-run-api.modal.run';
const API_PATHS = {
status: '/api/status',
list_domains: '/api/domains',
navigate: '/api/navigate',
view: '/api/view',
search: '/api/search'
};
// API client class
class LibraryNavigatorClient {
baseUrl;
constructor(baseUrl = BASE_URL) {
this.baseUrl = baseUrl.replace(/\/$/, ''); // Remove trailing slash if present
}
async getStatus() {
return this.makeRequest('GET', 'status');
}
async listDomains() {
return this.makeRequest('GET', 'list_domains');
}
async navigateDirectory(path, query) {
const payload = { path };
if (query) {
payload.query = query;
}
return this.makeRequest('POST', 'navigate', payload);
}
async viewFile(filePath) {
const payload = { file_path: filePath };
return this.makeRequest('POST', 'view', payload);
}
async searchFiles(query, library, topK = 5) {
const payload = { query, top_k: topK };
if (library) {
payload.library = library;
}
return this.makeRequest('POST', 'search', payload);
}
async makeRequest(method, pathKey, payload) {
const url = `${this.baseUrl}${API_PATHS[pathKey]}`;
try {
let response;
if (method.toUpperCase() === 'GET') {
response = await axios.get(url, { timeout: 30000 });
}
else if (method.toUpperCase() === 'POST') {
response = await axios.post(url, payload, { timeout: 30000 });
}
else {
throw new Error(`Unsupported HTTP method: ${method}`);
}
return response.data;
}
catch (error) {
if (error && typeof error === 'object') {
const axiosError = error;
if (axiosError.response) {
return { error: `Request failed with status ${axiosError.response.status}`, data: axiosError.response.data };
}
else if (axiosError.request) {
return { error: 'No response received from server' };
}
else if (axiosError.message) {
return { error: `Request error: ${axiosError.message}` };
}
}
return { error: `Unexpected error: ${error}` };
}
}
}
// Create CLI program
const program = new Command();
const client = new LibraryNavigatorClient();
program
.name('uapi')
.description('CLI for Library Navigator API')
.version('0.1.0');
// Status command
program
.command('status')
.description('Check API server status')
.action(async () => {
const spinner = ora('Checking API status...').start();
try {
const result = await client.getStatus();
spinner.succeed('Status check completed');
console.log(JSON.stringify(result, null, 2));
}
catch (error) {
spinner.fail(`Error: ${error}`);
}
});
// List domains command
program
.command('list')
.description('List all available domains')
.action(async () => {
const spinner = ora('Fetching domains...').start();
try {
const result = await client.listDomains();
spinner.succeed('Domains fetched successfully');
console.log(JSON.stringify(result, null, 2));
}
catch (error) {
spinner.fail(`Error: ${error}`);
}
});
// Navigate command
program
.command('nav')
.description('Navigate a directory and optionally get suggestions')
.argument('<path>', 'Directory path to navigate')
.option('-q, --query <query>', 'Query for directory navigation suggestions')
.action(async (path, options) => {
const spinner = ora(`Navigating ${path}...`).start();
try {
const result = await client.navigateDirectory(path, options.query);
spinner.succeed('Navigation completed');
console.log(JSON.stringify(result, null, 2));
}
catch (error) {
spinner.fail(`Error: ${error}`);
}
});
// View file command
program
.command('view')
.description('View the contents of a file')
.argument('<file_path>', 'Path to the file to view')
.action(async (filePath) => {
const spinner = ora(`Loading file ${filePath}...`).start();
try {
const result = await client.viewFile(filePath);
spinner.succeed('File loaded successfully');
if (result && 'content' in result && !('error' in result)) {
console.log(chalk.cyan(`\n--- Content of ${result.virtual_path || filePath} ---`));
console.log(result.content);
console.log(chalk.cyan('--- End of Content ---\n'));
}
else {
console.log(JSON.stringify(result, null, 2));
}
}
catch (error) {
spinner.fail(`Error: ${error}`);
}
});
// Search command
program
.command('search')
.description('Search for files matching a query')
.argument('<query>', 'Search query')
.option('-l, --lib <library>', 'Specific library to search in')
.option('-k, --topk <number>', 'Number of top results to return', '5')
.action(async (query, options) => {
const spinner = ora(`Searching for "${query}"...`).start();
try {
// Validate and parse topk parameter
let topK = 5;
if (options.topk) {
const parsedTopK = parseInt(options.topk);
if (isNaN(parsedTopK) || parsedTopK <= 0) {
spinner.fail(`Invalid value for --topk: ${options.topk}. Using default of 5.`);
topK = 5;
}
else if (parsedTopK > 100) {
spinner.warn('The requested number of results is too large. Limiting to 100.');
topK = 100;
}
else {
topK = parsedTopK;
}
}
const result = await client.searchFiles(query, options.lib, topK);
spinner.succeed('Search completed');
console.log(JSON.stringify(result, null, 2));
}
catch (error) {
spinner.fail(`Error: ${error}`);
}
});
// Set base URL command
program
.command('config')
.description('Set or view API base URL')
.option('-u, --url <url>', 'Set the API base URL')
.action((options) => {
if (options.url) {
// Validate URL format
try {
const url = new URL(options.url);
if (!url.protocol.startsWith('http')) {
console.log(chalk.red('Error: URL must use HTTP or HTTPS protocol'));
return;
}
// Create .env file with the new URL
const envContent = `API_BASE_URL=${options.url}`;
fs.writeFileSync('.env', envContent);
console.log(chalk.green(`API base URL set to: ${options.url}`));
}
catch (error) {
console.log(chalk.red(`Error: Invalid URL format - ${options.url}`));
}
}
else {
console.log(chalk.blue(`Current API base URL: ${BASE_URL}`));
}
});
// Parse command line arguments
program.parse();
// Display help if no arguments provided
if (process.argv.length <= 2) {
program.help();
}
//# sourceMappingURL=index.js.map