Skip to content

Commit fd8cfb4

Browse files
authored
Merge pull request #7 from fleetbase/feat/search-extensions-command
feat: Add flb search command to list and search available extensions
2 parents c5281c5 + 3b3a263 commit fd8cfb4

4 files changed

Lines changed: 200 additions & 3 deletions

File tree

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ FLB (Fleetbase CLI) is a command-line interface tool designed for managing Fleet
1111
- Automatically convert `composer.json` to `package.json` for PHP packages
1212
- Scaffold new Fleetbase extensions
1313
- Set registry token to a Fleetbase instance
14+
- Search and list available extensions from the registry
1415
- Install and Uninstall extensions
1516
- Flexible registry configuration
1617

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

251+
### Searching for Extensions
252+
253+
Search and list all available extensions from the Fleetbase registry. Supports keyword search, category filtering, and multiple output formats.
254+
255+
```bash
256+
flb search [query]
257+
```
258+
259+
Alias: `flb list-extensions`
260+
261+
**Options:**
262+
- `[query]`: (Optional) Search keyword to filter by name, slug, subtitle, description, or tags
263+
- `-c, --category <category>`: Filter by category name or slug
264+
- `-f, --free`: Show only free extensions
265+
- `--json`: Output results as raw JSON (useful for scripting)
266+
- `--simple`: Output one extension per line as tab-separated values: `slug`, `name`, `version`, `price`
267+
- `-h, --host <host>`: API host to fetch extensions from (default: `https://api.fleetbase.io`)
268+
269+
**Examples:**
270+
```bash
271+
# List all available extensions
272+
flb search
273+
274+
# Search by keyword
275+
flb search routing
276+
flb search "route optimization"
277+
278+
# Filter by category
279+
flb search --category telematics
280+
281+
# Show only free extensions
282+
flb search --free
283+
284+
# Combine filters
285+
flb search --free --category telematics
286+
287+
# JSON output for scripting
288+
flb search --json
289+
flb search routing --json | jq '.[].slug'
290+
291+
# Simple tab-separated output
292+
flb search --simple
293+
294+
# Search against a self-hosted instance
295+
flb search --host https://api.myfleetbase.com
296+
flb search routing --host http://localhost:8000
297+
```
298+
250299
### Installing a Extension
251300

252301
To install a extension, use:

index.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const maxBuffer = 1024 * 1024 * 50; // 50MB
1616
const defaultRegistry = 'https://registry.fleetbase.io';
1717
const packageLookupApi = 'https://api.fleetbase.io/~registry/v1/lookup';
1818
const bundleUploadApi = 'https://api.fleetbase.io/~registry/v1/bundle-upload';
19+
const extensionsListApi = 'https://api.fleetbase.io/~registry/v1/extensions';
1920
const starterExtensionRepo = 'https://github.com/fleetbase/starter-extension.git';
2021

2122
function publishPackage (packagePath, registry, options = {}) {
@@ -1293,6 +1294,142 @@ function loginCommand (options) {
12931294
}
12941295
}
12951296

1297+
// Helper: ANSI colour utilities (chalk v5 is ESM-only; use raw ANSI codes in this CJS file)
1298+
const ansi = {
1299+
reset: '\x1b[0m',
1300+
bold: '\x1b[1m',
1301+
dim: '\x1b[2m',
1302+
green: '\x1b[32m',
1303+
yellow: '\x1b[33m',
1304+
cyan: '\x1b[36m',
1305+
white: '\x1b[37m',
1306+
brightWhite: '\x1b[97m',
1307+
colorize: (code, text) => `${code}${text}\x1b[0m`,
1308+
};
1309+
1310+
// Helper: format extension list for terminal display
1311+
function displayExtensionsTable(extensions) {
1312+
const count = extensions.length;
1313+
console.log(ansi.colorize(ansi.bold + ansi.brightWhite, `Found ${count} extension${count !== 1 ? 's' : ''}:\n`));
1314+
1315+
extensions.forEach((ext, index) => {
1316+
const rawPrice = ext.on_sale ? ext.sale_price : ext.price;
1317+
const formattedPrice = (rawPrice / 100).toFixed(2);
1318+
const price = ext.payment_required
1319+
? ansi.colorize(ansi.yellow, `$${formattedPrice} ${(ext.currency || 'USD').toUpperCase()}`)
1320+
: ansi.colorize(ansi.green, 'Free');
1321+
1322+
const installs = ansi.colorize(ansi.dim, `\u2193 ${ext.installs_count ?? 0}`);
1323+
const category = ext.category?.name
1324+
? ansi.colorize(ansi.cyan, `[${ext.category.name}]`)
1325+
: '';
1326+
const version = ansi.colorize(ansi.dim, `v${ext.version || '?'}`);
1327+
const publisher = ext.publisher?.name
1328+
? ansi.colorize(ansi.dim, `by ${ext.publisher.name}`)
1329+
: '';
1330+
1331+
console.log(`${ansi.colorize(ansi.bold + ansi.brightWhite, ext.name)} ${version} ${price} ${installs} ${category}`);
1332+
console.log(` ${ansi.colorize(ansi.dim, ext.slug)} ${publisher}`);
1333+
if (ext.subtitle) {
1334+
console.log(` ${ext.subtitle}`);
1335+
}
1336+
const installSlug = `fleetbase/${ext.slug}`;
1337+
console.log(` ${ansi.colorize(ansi.dim, 'Install:')} flb install ${installSlug} ${ansi.colorize(ansi.dim, `or flb install ${ext.id}`)}`);
1338+
1339+
if (index < extensions.length - 1) {
1340+
console.log('');
1341+
}
1342+
});
1343+
1344+
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'));
1345+
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.`));
1346+
console.log(ansi.colorize(ansi.dim, `Use ${ansi.colorize(ansi.white, 'flb search --json')} for machine-readable output.\n`));
1347+
}
1348+
1349+
// Command: search and list available extensions
1350+
async function searchExtensionsCommand(query, options) {
1351+
const host = options.host || 'https://api.fleetbase.io';
1352+
const apiHost = host.startsWith('http://') || host.startsWith('https://')
1353+
? host
1354+
: `https://${host}`;
1355+
const endpoint = `${apiHost}/~registry/v1/extensions`;
1356+
1357+
if (!options.json && !options.simple) {
1358+
console.log('\n\u{1F50D} Searching Fleetbase Extensions...\n');
1359+
}
1360+
1361+
try {
1362+
const response = await axios.get(endpoint);
1363+
let extensions = response.data;
1364+
1365+
if (!Array.isArray(extensions) || extensions.length === 0) {
1366+
console.log('No extensions found.');
1367+
return;
1368+
}
1369+
1370+
// Filter by search query (name, slug, subtitle, description, tags)
1371+
if (query) {
1372+
const q = query.toLowerCase();
1373+
extensions = extensions.filter(ext =>
1374+
ext.name?.toLowerCase().includes(q) ||
1375+
ext.slug?.toLowerCase().includes(q) ||
1376+
ext.subtitle?.toLowerCase().includes(q) ||
1377+
ext.description?.toLowerCase().includes(q) ||
1378+
(Array.isArray(ext.tags) && ext.tags.some(t => t.toLowerCase().includes(q)))
1379+
);
1380+
}
1381+
1382+
// Filter by category
1383+
if (options.category) {
1384+
const cat = options.category.toLowerCase();
1385+
extensions = extensions.filter(ext =>
1386+
ext.category?.slug?.toLowerCase().includes(cat) ||
1387+
ext.category?.name?.toLowerCase().includes(cat)
1388+
);
1389+
}
1390+
1391+
// Filter to free only
1392+
if (options.free) {
1393+
extensions = extensions.filter(ext => !ext.payment_required);
1394+
}
1395+
1396+
if (extensions.length === 0) {
1397+
const qualifier = query || options.category;
1398+
console.log(`No extensions found${qualifier ? ` matching "${qualifier}"` : ''}.`);
1399+
return;
1400+
}
1401+
1402+
// JSON output mode
1403+
if (options.json) {
1404+
console.log(JSON.stringify(extensions, null, 2));
1405+
return;
1406+
}
1407+
1408+
// Simple one-per-line output mode (for scripting)
1409+
if (options.simple) {
1410+
extensions.forEach(ext => {
1411+
const rawPrice = ext.on_sale ? ext.sale_price : ext.price;
1412+
const price = ext.payment_required ? `$${(rawPrice / 100).toFixed(2)}` : 'free';
1413+
console.log(`${ext.slug}\t${ext.name}\tv${ext.version || '?'}\t${price}`);
1414+
});
1415+
return;
1416+
}
1417+
1418+
// Default: formatted table output
1419+
displayExtensionsTable(extensions);
1420+
1421+
} catch (error) {
1422+
if (error.response) {
1423+
console.error(`\nSearch failed: ${error.response.status} ${error.response.statusText}`);
1424+
} else if (error.request) {
1425+
console.error('\nSearch failed: No response from server. Check your --host or network connection.');
1426+
} else {
1427+
console.error(`\nSearch failed: ${error.message}`);
1428+
}
1429+
process.exit(1);
1430+
}
1431+
}
1432+
12961433
program.name('flb').description('CLI tool for managing Fleetbase Extensions').version(`${packageJson.name} ${packageJson.version}`, '-v, --version', 'Output the current version');
12971434
program.option('-r, --registry [url]', 'Specify a fleetbase extension repository', defaultRegistry);
12981435

@@ -1333,6 +1470,17 @@ program
13331470
await installPackage(packageName, fleetbasePath);
13341471
});
13351472

1473+
program
1474+
.command('search [query]')
1475+
.alias('list-extensions')
1476+
.description('Search and list available Fleetbase extensions')
1477+
.option('-c, --category <category>', 'Filter by category name or slug')
1478+
.option('-f, --free', 'Show only free extensions')
1479+
.option('--json', 'Output results as raw JSON')
1480+
.option('--simple', 'Output one extension per line: slug, name, version, price (for scripting)')
1481+
.option('-h, --host <host>', 'API host to fetch extensions from (default: https://api.fleetbase.io)')
1482+
.action(searchExtensionsCommand);
1483+
13361484
program
13371485
.command('uninstall [packageName]')
13381486
.option('-p, --path <path>', 'Path of the Fleetbase instance to uninstall for')

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@fleetbase/cli",
3-
"version": "0.0.4",
3+
"version": "0.0.5",
44
"description": "CLI tool for managing Fleetbase Extensions",
55
"repository": "https://github.com/fleetbase/fleetbase",
66
"license": "AGPL-3.0-or-later",

0 commit comments

Comments
 (0)