Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ FLB (Fleetbase CLI) is a command-line interface tool designed for managing Fleet
- Automatically convert `composer.json` to `package.json` for PHP packages
- Scaffold new Fleetbase extensions
- Set registry token to a Fleetbase instance
- Search and list available extensions from the registry
- Install and Uninstall extensions
- Flexible registry configuration

Expand Down Expand Up @@ -247,6 +248,54 @@ flb scaffold
- `-n, --namespace`: The PHP Namespace of the extension to scaffold
- `-r, --repo`: The Repository URL of the extension to scaffold

### Searching for Extensions

Search and list all available extensions from the Fleetbase registry. Supports keyword search, category filtering, and multiple output formats.

```bash
flb search [query]
```

Alias: `flb list-extensions`

**Options:**
- `[query]`: (Optional) Search keyword to filter by name, slug, subtitle, description, or tags
- `-c, --category <category>`: Filter by category name or slug
- `-f, --free`: Show only free extensions
- `--json`: Output results as raw JSON (useful for scripting)
- `--simple`: Output one extension per line as tab-separated values: `slug`, `name`, `version`, `price`
- `-h, --host <host>`: API host to fetch extensions from (default: `https://api.fleetbase.io`)

**Examples:**
```bash
# List all available extensions
flb search

# Search by keyword
flb search routing
flb search "route optimization"

# Filter by category
flb search --category telematics

# Show only free extensions
flb search --free

# Combine filters
flb search --free --category telematics

# JSON output for scripting
flb search --json
flb search routing --json | jq '.[].slug'

# Simple tab-separated output
flb search --simple

# Search against a self-hosted instance
flb search --host https://api.myfleetbase.com
flb search routing --host http://localhost:8000
```

### Installing a Extension

To install a extension, use:
Expand Down
148 changes: 148 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const maxBuffer = 1024 * 1024 * 50; // 50MB
const defaultRegistry = 'https://registry.fleetbase.io';
const packageLookupApi = 'https://api.fleetbase.io/~registry/v1/lookup';
const bundleUploadApi = 'https://api.fleetbase.io/~registry/v1/bundle-upload';
const extensionsListApi = 'https://api.fleetbase.io/~registry/v1/extensions';
const starterExtensionRepo = 'https://github.com/fleetbase/starter-extension.git';

function publishPackage (packagePath, registry, options = {}) {
Expand Down Expand Up @@ -1293,6 +1294,142 @@ function loginCommand (options) {
}
}

// Helper: ANSI colour utilities (chalk v5 is ESM-only; use raw ANSI codes in this CJS file)
const ansi = {
reset: '\x1b[0m',
bold: '\x1b[1m',
dim: '\x1b[2m',
green: '\x1b[32m',
yellow: '\x1b[33m',
cyan: '\x1b[36m',
white: '\x1b[37m',
brightWhite: '\x1b[97m',
colorize: (code, text) => `${code}${text}\x1b[0m`,
};

// Helper: format extension list for terminal display
function displayExtensionsTable(extensions) {
const count = extensions.length;
console.log(ansi.colorize(ansi.bold + ansi.brightWhite, `Found ${count} extension${count !== 1 ? 's' : ''}:\n`));

extensions.forEach((ext, index) => {
const rawPrice = ext.on_sale ? ext.sale_price : ext.price;
const formattedPrice = (rawPrice / 100).toFixed(2);
const price = ext.payment_required
? ansi.colorize(ansi.yellow, `$${formattedPrice} ${(ext.currency || 'USD').toUpperCase()}`)
: ansi.colorize(ansi.green, 'Free');

const installs = ansi.colorize(ansi.dim, `\u2193 ${ext.installs_count ?? 0}`);
const category = ext.category?.name
? ansi.colorize(ansi.cyan, `[${ext.category.name}]`)
: '';
const version = ansi.colorize(ansi.dim, `v${ext.version || '?'}`);
const publisher = ext.publisher?.name
? ansi.colorize(ansi.dim, `by ${ext.publisher.name}`)
: '';

console.log(`${ansi.colorize(ansi.bold + ansi.brightWhite, ext.name)} ${version} ${price} ${installs} ${category}`);
console.log(` ${ansi.colorize(ansi.dim, ext.slug)} ${publisher}`);
if (ext.subtitle) {
console.log(` ${ext.subtitle}`);
}
const installSlug = `fleetbase/${ext.slug}`;
console.log(` ${ansi.colorize(ansi.dim, 'Install:')} flb install ${installSlug} ${ansi.colorize(ansi.dim, `or flb install ${ext.id}`)}`);

if (index < extensions.length - 1) {
console.log('');
}
});

console.log(ansi.colorize(ansi.dim, '\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'));
console.log(ansi.colorize(ansi.dim, `Use ${ansi.colorize(ansi.white, 'flb install fleetbase/<slug>')} or ${ansi.colorize(ansi.white, 'flb install <extension_id>')} to install an extension.`));
console.log(ansi.colorize(ansi.dim, `Use ${ansi.colorize(ansi.white, 'flb search --json')} for machine-readable output.\n`));
}

// Command: search and list available extensions
async function searchExtensionsCommand(query, options) {
const host = options.host || 'https://api.fleetbase.io';
const apiHost = host.startsWith('http://') || host.startsWith('https://')
? host
: `https://${host}`;
const endpoint = `${apiHost}/~registry/v1/extensions`;

if (!options.json && !options.simple) {
console.log('\n\u{1F50D} Searching Fleetbase Extensions...\n');
}

try {
const response = await axios.get(endpoint);
let extensions = response.data;

if (!Array.isArray(extensions) || extensions.length === 0) {
console.log('No extensions found.');
return;
}

// Filter by search query (name, slug, subtitle, description, tags)
if (query) {
const q = query.toLowerCase();
extensions = extensions.filter(ext =>
ext.name?.toLowerCase().includes(q) ||
ext.slug?.toLowerCase().includes(q) ||
ext.subtitle?.toLowerCase().includes(q) ||
ext.description?.toLowerCase().includes(q) ||
(Array.isArray(ext.tags) && ext.tags.some(t => t.toLowerCase().includes(q)))
);
}

// Filter by category
if (options.category) {
const cat = options.category.toLowerCase();
extensions = extensions.filter(ext =>
ext.category?.slug?.toLowerCase().includes(cat) ||
ext.category?.name?.toLowerCase().includes(cat)
);
}

// Filter to free only
if (options.free) {
extensions = extensions.filter(ext => !ext.payment_required);
}

if (extensions.length === 0) {
const qualifier = query || options.category;
console.log(`No extensions found${qualifier ? ` matching "${qualifier}"` : ''}.`);
return;
}

// JSON output mode
if (options.json) {
console.log(JSON.stringify(extensions, null, 2));
return;
}

// Simple one-per-line output mode (for scripting)
if (options.simple) {
extensions.forEach(ext => {
const rawPrice = ext.on_sale ? ext.sale_price : ext.price;
const price = ext.payment_required ? `$${(rawPrice / 100).toFixed(2)}` : 'free';
console.log(`${ext.slug}\t${ext.name}\tv${ext.version || '?'}\t${price}`);
});
return;
}

// Default: formatted table output
displayExtensionsTable(extensions);

} catch (error) {
if (error.response) {
console.error(`\nSearch failed: ${error.response.status} ${error.response.statusText}`);
} else if (error.request) {
console.error('\nSearch failed: No response from server. Check your --host or network connection.');
} else {
console.error(`\nSearch failed: ${error.message}`);
}
process.exit(1);
}
}

program.name('flb').description('CLI tool for managing Fleetbase Extensions').version(`${packageJson.name} ${packageJson.version}`, '-v, --version', 'Output the current version');
program.option('-r, --registry [url]', 'Specify a fleetbase extension repository', defaultRegistry);

Expand Down Expand Up @@ -1333,6 +1470,17 @@ program
await installPackage(packageName, fleetbasePath);
});

program
.command('search [query]')
.alias('list-extensions')
.description('Search and list available Fleetbase extensions')
.option('-c, --category <category>', 'Filter by category name or slug')
.option('-f, --free', 'Show only free extensions')
.option('--json', 'Output results as raw JSON')
.option('--simple', 'Output one extension per line: slug, name, version, price (for scripting)')
.option('-h, --host <host>', 'API host to fetch extensions from (default: https://api.fleetbase.io)')
.action(searchExtensionsCommand);

program
.command('uninstall [packageName]')
.option('-p, --path <path>', 'Path of the Fleetbase instance to uninstall for')
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fleetbase/cli",
"version": "0.0.4",
"version": "0.0.5",
"description": "CLI tool for managing Fleetbase Extensions",
"repository": "https://github.com/fleetbase/fleetbase",
"license": "AGPL-3.0-or-later",
Expand Down