Skip to content

Commit

Permalink
Checkpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
michaellatman committed Dec 6, 2024
1 parent cb56555 commit 11ac798
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 126 deletions.
45 changes: 10 additions & 35 deletions src/installed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,24 @@ import inquirer from 'inquirer';
import chalk from 'chalk';
import { readConfig } from './utils/config.js';
import { displayPackageDetailsWithActions } from './utils/display.js';
import { uninstallPackage } from './utils/package-management.js';
import { Package } from './types/index.js';
import { readFileSync } from 'fs';
import { join } from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { uninstallPackage, resolvePackages, ResolvedPackage } from './utils/package-management.js';
import AutocompletePrompt from 'inquirer-autocomplete-prompt';
import fuzzy from 'fuzzy';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const packageListPath = join(__dirname, '../packages/package-list.json');

inquirer.registerPrompt('autocomplete', AutocompletePrompt);

export async function listInstalledPackages(): Promise<void> {
// Read full package list
const allPackages: Package[] = JSON.parse(readFileSync(packageListPath, 'utf-8'));
// Get all packages with their resolved status
const allPackages = resolvePackages();

// Get installed packages from config
const config = readConfig();
const installedServers = config.mcpServers || {};
const serverNames = Object.keys(installedServers);
// Filter for only installed packages
const installedPackages = allPackages.filter(pkg => pkg.isInstalled);

if (serverNames.length === 0) {
if (installedPackages.length === 0) {
console.log(chalk.yellow('\nNo MCP servers are currently installed.'));
return;
}

// Filter for only installed packages
const installedPackages = allPackages.filter(pkg =>
serverNames.includes(pkg.name.replace(/\//g, '-'))
);

console.log(chalk.bold.cyan('\n📦 Installed Packages'));
console.log(chalk.gray(`Found ${installedPackages.length} installed packages\n`));

Expand All @@ -48,7 +32,7 @@ export async function listInstalledPackages(): Promise<void> {
short: pkg.name
}));

const answer = await inquirer.prompt<{ selectedPackage: Package }>([
const answer = await inquirer.prompt<{ selectedPackage: ResolvedPackage }>([
{
type: 'autocomplete',
name: 'selectedPackage',
Expand All @@ -66,23 +50,14 @@ export async function listInstalledPackages(): Promise<void> {
}
]);

const displayPackages = answer.selectedPackage ? [answer.selectedPackage] : installedPackages;

if (displayPackages.length === 0) {
console.log(chalk.yellow('\nNo packages found matching your search.'));
if (!answer.selectedPackage) {
return;
}

console.log(chalk.bold.white(`\nShowing ${displayPackages.length} package(s):`));
displayPackages.forEach(displayPackageDetailsWithActions);

if (displayPackages.length === 1) {
const pkg = displayPackages[0];
await handleSelectedPackage(pkg);
}
await handleSelectedPackage(answer.selectedPackage);
}

async function handleSelectedPackage(pkg: Package) {
async function handleSelectedPackage(pkg: ResolvedPackage) {
const action = await displayPackageDetailsWithActions(pkg);

switch (action) {
Expand Down
99 changes: 38 additions & 61 deletions src/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,90 +5,66 @@ import inquirer from 'inquirer';
import fuzzy from 'fuzzy';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { Package } from './types/index.js';
import { displayPackageDetailsWithActions } from './utils/display.js';
import { installPackage, uninstallPackage, isPackageInstalled } from './utils/package-management.js';
import { createInterface } from 'readline';
import Table from 'cli-table3'; // Import cli-table3
import stringWidth from 'string-width'; // Import string-width
import AutocompletePrompt from 'inquirer-autocomplete-prompt'; // Import autocomplete prompt
import { installPackage, uninstallPackage, resolvePackages, ResolvedPackage } from './utils/package-management.js';
import AutocompletePrompt from 'inquirer-autocomplete-prompt';

// Register the autocomplete prompt
inquirer.registerPrompt('autocomplete', AutocompletePrompt);

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const packageListPath = path.join(__dirname, '../packages/package-list.json');
export async function list() {
let packages: Package[];
try {
const data = fs.readFileSync(packageListPath, 'utf8');
packages = JSON.parse(data).map((pkg: Package) => ({
...pkg,
isInstalled: isPackageInstalled(pkg.name)
}));
if (!Array.isArray(packages)) {
throw new Error('Package list is not an array');
}
const packages = resolvePackages();
console.log(chalk.bold.cyan('\n📦 Available Packages'));
console.log(chalk.gray(`Found ${packages.length} packages\n`));
} catch (error) {
console.error(chalk.red(`Error loading package list from ${packageListPath}`));
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
process.exit(1);
}

// Prepare choices for inquirer using table-like format
const choices = packages.map((pkg, index) => ({
name: `${pkg.isInstalled ? '✓ ' : ' '}${pkg.name.padEnd(22)}${
pkg.description.length > 47 ? `${pkg.description.slice(0, 44)}...` : pkg.description.padEnd(49)
}${pkg.vendor.padEnd(19)}${pkg.license.padEnd(14)}`,
value: pkg,
short: pkg.name
}));

const answer = await inquirer.prompt<{ selectedPackage: Package }>([
{
type: 'autocomplete',
name: 'selectedPackage',
message: 'Search and select a package:',
source: async (_answersSoFar: any, input: string) => {
if (!input) return choices;

return fuzzy
.filter(input.toLowerCase(), choices, {
extract: (choice) => `${choice.value.name} ${choice.value.description} ${choice.value.vendor}`.toLowerCase()
})
.map(result => result.original);
},
pageSize: 10
}
]);
// Prepare choices for inquirer using table-like format
const choices = packages.map((pkg) => ({
name: `${pkg.isInstalled ? '✓ ' : ' '}${pkg.name.padEnd(22)}${
pkg.description.length > 47 ? `${pkg.description.slice(0, 44)}...` : pkg.description.padEnd(49)
}${pkg.vendor.padEnd(19)}${pkg.license.padEnd(14)}`,
value: pkg,
short: pkg.name
}));

const displayPackages = answer.selectedPackage ? [answer.selectedPackage] : packages;
const answer = await inquirer.prompt<{ selectedPackage: ResolvedPackage }>([
{
type: 'autocomplete',
name: 'selectedPackage',
message: 'Search and select a package:',
source: async (_answersSoFar: any, input: string) => {
if (!input) return choices;

if (displayPackages.length === 0) {
console.log(chalk.yellow('\nNo packages found matching your search.'));
return;
}
return fuzzy
.filter(input.toLowerCase(), choices, {
extract: (choice) => `${choice.value.name} ${choice.value.description} ${choice.value.vendor}`.toLowerCase()
})
.map(result => result.original);
},
pageSize: 10
}
]);

console.log(chalk.bold.white(`\nShowing ${displayPackages.length} package(s):`));
displayPackages.forEach(displayPackageDetailsWithActions);
if (!answer.selectedPackage) {
return;
}

if (displayPackages.length === 1) {
const pkg = displayPackages[0];
await handleSelectedPackage(pkg);
await handleSelectedPackage(answer.selectedPackage);
} catch (error) {
console.error(chalk.red('Error loading package list:'));
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
process.exit(1);
}
}

async function handleSelectedPackage(pkg: Package) {
async function handleSelectedPackage(pkg: ResolvedPackage) {
const action = await displayPackageDetailsWithActions(pkg);

switch (action) {
case 'install':
console.log(chalk.cyan(`\nPreparing to install ${pkg.name}...`));
await installPackage(pkg);
pkg.isInstalled = true;
break;
case 'uninstall':
const { confirmUninstall } = await inquirer.prompt<{ confirmUninstall: boolean }>([
Expand All @@ -103,6 +79,7 @@ async function handleSelectedPackage(pkg: Package) {
if (confirmUninstall) {
await uninstallPackage(pkg.name);
console.log(chalk.green(`Successfully uninstalled ${pkg.name}`));
pkg.isInstalled = false;
} else {
console.log('Uninstallation cancelled.');
}
Expand Down
20 changes: 6 additions & 14 deletions src/uninstall.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
import inquirer from 'inquirer';
import chalk from 'chalk';
import { readFileSync } from 'fs';
import { join } from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { Package } from './types/index.js';
import { uninstallPackage } from './utils/package-management.js';
import { uninstallPackage, resolvePackages, ResolvedPackage } from './utils/package-management.js';
import { displayPackageDetailsWithActions } from './utils/display.js';
import { list } from './list.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const packageListPath = join(__dirname, '../packages/package-list.json');

export async function uninstall(packageName?: string): Promise<void> {
try {
const packageList: Package[] = JSON.parse(readFileSync(packageListPath, 'utf-8'));
let selectedPackage: Package | undefined;
const packageList = resolvePackages();
let selectedPackage: ResolvedPackage | undefined;

if (packageName) {
selectedPackage = packageList.find(p => p.name === packageName);
Expand All @@ -26,15 +18,15 @@ export async function uninstall(packageName?: string): Promise<void> {
}
} else {
// Use same selection interface as list command
const choices = packageList.map((pkg, index) => ({
const choices = packageList.map((pkg) => ({
name: `${pkg.name.padEnd(24)}${
pkg.description.length > 47 ? `${pkg.description.slice(0, 44)}...` : pkg.description.padEnd(49)
}${pkg.vendor.padEnd(19)}${pkg.license.padEnd(14)}`,
value: pkg,
short: pkg.name
}));

const { selectedPkg } = await inquirer.prompt<{ selectedPkg: Package }>([
const { selectedPkg } = await inquirer.prompt<{ selectedPkg: ResolvedPackage }>([
{
type: 'autocomplete',
name: 'selectedPkg',
Expand Down Expand Up @@ -62,7 +54,7 @@ export async function uninstall(packageName?: string): Promise<void> {
}
}

async function handlePackageAction(action: string, pkg: Package) {
async function handlePackageAction(action: string, pkg: ResolvedPackage) {
switch (action) {
case 'install':
// Import and call install function
Expand Down
6 changes: 4 additions & 2 deletions src/utils/display.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import chalk from 'chalk';
import { Package } from '../types/index.js';
import { ResolvedPackage } from './package-management.js';
import inquirer from 'inquirer';

export async function displayPackageDetailsWithActions(pkg: Package): Promise<'install' | 'uninstall' | 'open' | 'back' | 'exit'> {
export async function displayPackageDetailsWithActions(pkg: ResolvedPackage): Promise<'install' | 'uninstall' | 'open' | 'back' | 'exit'> {
console.log('\n' + chalk.bold.cyan('Package Details:'));
console.log(chalk.bold('Name: ') + pkg.name);
console.log(chalk.bold('Description: ') + pkg.description);
Expand All @@ -11,6 +11,8 @@ export async function displayPackageDetailsWithActions(pkg: Package): Promise<'i
console.log(chalk.bold('Runtime: ') + (pkg.runtime || 'node'));
console.log(chalk.bold('Source: ') + (pkg.sourceUrl || 'Not available'));
console.log(chalk.bold('Homepage: ') + (pkg.homepage || 'Not available'));
console.log(chalk.bold('Status: ') + (pkg.isInstalled ? chalk.green('Installed') : 'Not installed') +
(pkg.isVerified ? '' : chalk.yellow(' (Unverified package)')));

const choices = [
{ name: pkg.isInstalled ? '🔄 Reinstall this package' : '📦 Install this package', value: 'install' },
Expand Down
Loading

0 comments on commit 11ac798

Please sign in to comment.