avocavo
Version:
Avocavo CLI - Nutrition analysis made simple. Get accurate USDA nutrition data with secure authentication.
263 lines (215 loc) ⢠7.79 kB
JavaScript
const axios = require('axios');
const chalk = require('chalk');
const ora = require('ora');
const Table = require('cli-table3');
const inquirer = require('inquirer');
class KeyManager {
constructor(auth, baseUrl = 'https://app.avocavo.app') {
this.auth = auth;
this.baseUrl = baseUrl;
}
async getAuthHeaders() {
const jwt = await this.auth.getJwtToken();
if (!jwt) {
throw new Error('Not logged in. Run: avocavo login');
}
return {
'Authorization': `Bearer ${jwt}`,
'Content-Type': 'application/json'
};
}
async list() {
const spinner = ora('Fetching API keys...').start();
try {
const headers = await this.getAuthHeaders();
const response = await axios.get(`${this.baseUrl}/api/keys`, { headers });
spinner.stop();
if (!response.data.keys || response.data.keys.length === 0) {
console.log(chalk.yellow('š No API keys found.'));
console.log(chalk.cyan('š” Create one with: avocavo keys create'));
return [];
}
const table = new Table({
head: ['Name', 'Key', 'Tier', 'Usage', 'Created'],
colWidths: [20, 20, 12, 15, 20]
});
const userInfo = this.auth.getUserInfo();
const currentKey = await this.auth.getApiKey();
response.data.keys.forEach(key => {
const isActive = currentKey && currentKey.includes(key.api_key.slice(0, -3));
const keyDisplay = isActive ? chalk.green(`${key.api_key} ā`) : key.api_key;
table.push([
key.key_name,
keyDisplay,
key.tier,
`${key.monthly_usage}/${key.monthly_limit}`,
new Date(key.created_at).toLocaleDateString()
]);
});
console.log(chalk.cyan('\nš Your API Keys:'));
console.log(table.toString());
return response.data.keys;
} catch (error) {
spinner.fail('Failed to fetch API keys');
if (error.response?.status === 401) {
console.error(chalk.red('ā Session expired. Please login again.'));
} else {
console.error(chalk.red(`ā Error: ${error.message}`));
}
throw error;
}
}
async create(options = {}) {
let spinner;
try {
const headers = await this.getAuthHeaders();
// If no name provided via command line, ask interactively
let keyName = options.name;
let description = options.description;
if (!keyName || keyName === 'CLI Key') {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Enter a name for your API key:',
default: 'CLI Key',
validate: input => input.trim() ? true : 'Key name cannot be empty'
},
{
type: 'input',
name: 'description',
message: 'Enter a description (optional):',
default: description || 'Created via CLI'
}
]);
keyName = answers.name;
description = answers.description;
}
spinner = ora('Creating API key...').start();
const data = {
key_name: keyName,
description: description,
environment: options.environment || 'development'
};
const response = await axios.post(`${this.baseUrl}/api/keys`, data, { headers });
spinner.succeed('API key created successfully!');
const key = response.data.key;
console.log(chalk.green('\nā
New API Key Created:'));
console.log(chalk.cyan(` Name: ${key.key_name}`));
console.log(chalk.yellow(` Key: ${key.api_key}`));
console.log(chalk.gray('\nā ļø Save this key securely - you won\'t be able to see it again!'));
// Ask if user wants to use this key now
const { useNow } = await inquirer.prompt([{
type: 'confirm',
name: 'useNow',
message: 'Use this API key now?',
default: true
}]);
if (useNow) {
await this.switch(key.api_key);
}
return key;
} catch (error) {
if (spinner) {
spinner.fail('Failed to create API key');
}
console.error(chalk.red(`ā Error: ${error.response?.data?.error || error.message}`));
throw error;
}
}
async switch(apiKey) {
try {
// If no key provided, show interactive selection
if (!apiKey) {
const keys = await this.list();
if (keys.length === 0) return;
const choices = keys.map(k => ({
name: `${k.key_name} (${k.tier}) - ${k.api_key}`,
value: k.id // Pass the ID instead of the whole object
}));
const { selectedKeyId } = await inquirer.prompt([{
type: 'list',
name: 'selectedKeyId',
message: 'Select an API key to use:',
choices
}]);
// Now reveal the full API key
const headers = await this.getAuthHeaders();
const response = await axios.post(
`${this.baseUrl}/api/keys/${selectedKeyId}/reveal`,
{},
{ headers }
);
apiKey = response.data.api_key;
}
// Reveal the full key if it's masked
if (apiKey.includes('...')) {
const spinner = ora('Retrieving full API key...').start();
try {
const headers = await this.getAuthHeaders();
const keyId = apiKey.match(/\d+/)?.[0]; // Extract ID if present
if (!keyId) {
spinner.fail('Invalid key format');
return;
}
const response = await axios.post(
`${this.baseUrl}/api/keys/${keyId}/reveal`,
{},
{ headers }
);
apiKey = response.data.api_key;
spinner.succeed('API key retrieved');
} catch (error) {
spinner.fail('Failed to retrieve API key');
throw error;
}
}
// Store the API key
const userInfo = this.auth.getUserInfo();
await this.auth.storeApiKeySecurely(userInfo.email, apiKey);
console.log(chalk.green('ā
Switched to API key successfully!'));
console.log(chalk.cyan(`š Active key: ${apiKey.substring(0, 12)}...`));
} catch (error) {
console.error(chalk.red(`ā Error: ${error.message}`));
throw error;
}
}
async delete(keyId) {
try {
if (!keyId) {
const keys = await this.list();
if (keys.length === 0) return;
const choices = keys.map(k => ({
name: `${k.key_name} (${k.tier}) - ${k.api_key}`,
value: k.id
}));
const { selectedId } = await inquirer.prompt([{
type: 'list',
name: 'selectedId',
message: 'Select an API key to delete:',
choices
}]);
keyId = selectedId;
}
// Confirm deletion
const { confirm } = await inquirer.prompt([{
type: 'confirm',
name: 'confirm',
message: 'Are you sure you want to delete this API key?',
default: false
}]);
if (!confirm) {
console.log(chalk.yellow('ā Deletion cancelled'));
return;
}
const spinner = ora('Deleting API key...').start();
const headers = await this.getAuthHeaders();
await axios.delete(`${this.baseUrl}/api/keys/${keyId}`, { headers });
spinner.succeed('API key deleted successfully!');
} catch (error) {
console.error(chalk.red(`ā Error: ${error.message}`));
throw error;
}
}
}
module.exports = KeyManager;