@asvanevik/mcli 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -0
- package/bin/mcli.js +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +258 -0
- package/dist/format.d.ts +33 -0
- package/dist/format.js +137 -0
- package/dist/lib.d.ts +77 -0
- package/dist/lib.js +226 -0
- package/dist/registry.d.ts +10 -0
- package/dist/registry.js +46 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.js +1 -0
- package/package.json +39 -0
- package/registry/tools.json +14767 -0
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# mcli
|
|
2
|
+
|
|
3
|
+
A CLI for discovering and comparing CLI tools — optimized for AI agents.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
Package managers tell you *how* to install tools. mcli tells you *which* tools are best for your use case, with structured metadata about agent-friendliness.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Discovery** — Search and compare CLI tools across categories
|
|
12
|
+
- **Agent Scores** — Rate tools on JSON output, idempotency, auth complexity
|
|
13
|
+
- **Verification Tiers** — Know if a tool is officially verified, community-vetted, or unverified
|
|
14
|
+
- **Install Commands** — Get the right install command for your platform
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx mcli search cloud
|
|
20
|
+
npx mcli info hcloud
|
|
21
|
+
npx mcli compare aws gcloud hcloud
|
|
22
|
+
npx mcli install gh
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
| Command | Description |
|
|
28
|
+
|---------|-------------|
|
|
29
|
+
| `search <query>` | Search for CLI tools |
|
|
30
|
+
| `info <slug>` | Show detailed info about a tool |
|
|
31
|
+
| `compare <slug> [slug...]` | Compare multiple tools side-by-side |
|
|
32
|
+
| `install <slug>` | Show install commands for a tool |
|
|
33
|
+
| `list` | List all tools |
|
|
34
|
+
| `list --agent-friendly` | List tools sorted by agent score |
|
|
35
|
+
| `categories` | List all categories |
|
|
36
|
+
|
|
37
|
+
## Agent Score
|
|
38
|
+
|
|
39
|
+
Tools are rated 1-10 on agent-friendliness based on:
|
|
40
|
+
|
|
41
|
+
- **JSON Output** — Parseable structured output
|
|
42
|
+
- **Idempotency** — Same command, same result
|
|
43
|
+
- **Auth Simplicity** — Env vars vs interactive flows
|
|
44
|
+
- **Error Clarity** — Actionable error messages
|
|
45
|
+
- **Streaming** — Handles long-running operations
|
|
46
|
+
|
|
47
|
+
## Verification Tiers
|
|
48
|
+
|
|
49
|
+
- ✓ **Verified** — Traced to official vendor source
|
|
50
|
+
- ○ **Community** — Submitted and reviewed by community
|
|
51
|
+
- ? **Unverified** — Auto-indexed, use with caution
|
|
52
|
+
|
|
53
|
+
## Roadmap
|
|
54
|
+
|
|
55
|
+
- [ ] Agent reviews — Let AI agents rate tools based on real usage
|
|
56
|
+
- [ ] PR reputation — Agents earn trust by contributing to CLI repos
|
|
57
|
+
- [ ] Web interface — Browse the registry online
|
|
58
|
+
- [ ] Skill integration — Link to skills.sh for usage instructions
|
|
59
|
+
|
|
60
|
+
## Development
|
|
61
|
+
|
|
62
|
+
This project follows **test-driven development (TDD)**. Tests must pass before merging.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm test # Run tests
|
|
66
|
+
npm run test:watch # Watch mode
|
|
67
|
+
npm run test:coverage # Coverage report
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Contributing
|
|
71
|
+
|
|
72
|
+
PRs welcome! To add a tool, edit `registry/tools.json`. All tools are validated by tests.
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT
|
package/bin/mcli.js
ADDED
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { searchTools, findTool, getCategories, sortByAgentScore, tierBadge, filterByMinScore, filterByCategory } from './lib.js';
|
|
5
|
+
import { loadRegistry, RegistryError } from './registry.js';
|
|
6
|
+
// Parse --flag and --flag=value from args
|
|
7
|
+
function parseFlag(args, flag) {
|
|
8
|
+
for (const arg of args) {
|
|
9
|
+
if (arg === flag)
|
|
10
|
+
return 'true';
|
|
11
|
+
if (arg.startsWith(`${flag}=`))
|
|
12
|
+
return arg.slice(flag.length + 1);
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
function parseNumericFlag(args, flag) {
|
|
17
|
+
const val = parseFlag(args, flag);
|
|
18
|
+
if (val === null)
|
|
19
|
+
return null;
|
|
20
|
+
const num = parseInt(val, 10);
|
|
21
|
+
return isNaN(num) ? null : num;
|
|
22
|
+
}
|
|
23
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const registryPath = join(__dirname, '..', 'registry', 'tools.json');
|
|
25
|
+
function scoreColor(score) {
|
|
26
|
+
if (score >= 8)
|
|
27
|
+
return '\x1b[32m'; // green
|
|
28
|
+
if (score >= 5)
|
|
29
|
+
return '\x1b[33m'; // yellow
|
|
30
|
+
return '\x1b[31m'; // red
|
|
31
|
+
}
|
|
32
|
+
function reset() {
|
|
33
|
+
return '\x1b[0m';
|
|
34
|
+
}
|
|
35
|
+
function printTool(tool, verbose = false) {
|
|
36
|
+
const badge = tierBadge(tool.tier);
|
|
37
|
+
const color = scoreColor(tool.agentScore);
|
|
38
|
+
console.log(`${badge} ${tool.name} (${tool.slug}) - Agent Score: ${color}${tool.agentScore}/10${reset()}`);
|
|
39
|
+
console.log(` ${tool.description}`);
|
|
40
|
+
if (verbose) {
|
|
41
|
+
console.log(` Vendor: ${tool.vendor.name} (${tool.vendor.domain})`);
|
|
42
|
+
console.log(` Categories: ${tool.categories.join(', ')}`);
|
|
43
|
+
// Show agent score breakdown if available
|
|
44
|
+
if (tool.agentScores) {
|
|
45
|
+
console.log(` Agent Scores (1-5 each):`);
|
|
46
|
+
console.log(` JSON Output: ${tool.agentScores.jsonOutput}/5`);
|
|
47
|
+
console.log(` Non-Interactive: ${tool.agentScores.nonInteractive}/5`);
|
|
48
|
+
console.log(` Token Efficiency:${tool.agentScores.tokenEfficiency}/5`);
|
|
49
|
+
console.log(` Safety Features: ${tool.agentScores.safetyFeatures}/5`);
|
|
50
|
+
console.log(` Pipeline Friend: ${tool.agentScores.pipelineFriendly}/5`);
|
|
51
|
+
}
|
|
52
|
+
console.log(` Install:`);
|
|
53
|
+
for (const [method, cmd] of Object.entries(tool.install)) {
|
|
54
|
+
if (cmd)
|
|
55
|
+
console.log(` ${method}: ${cmd}`);
|
|
56
|
+
}
|
|
57
|
+
console.log(` Capabilities:`);
|
|
58
|
+
console.log(` JSON output: ${tool.capabilities.jsonOutput ? 'yes' : 'no'}`);
|
|
59
|
+
console.log(` Idempotent: ${tool.capabilities.idempotent ? 'yes' : 'no'}`);
|
|
60
|
+
console.log(` Interactive: ${tool.capabilities.interactive ? 'yes' : 'no'}`);
|
|
61
|
+
if (tool.capabilities.auth.length > 0) {
|
|
62
|
+
console.log(` Auth: ${tool.capabilities.auth.join(', ')}`);
|
|
63
|
+
}
|
|
64
|
+
if (tool.repo)
|
|
65
|
+
console.log(` Repo: ${tool.repo}`);
|
|
66
|
+
if (tool.docs)
|
|
67
|
+
console.log(` Docs: ${tool.docs}`);
|
|
68
|
+
}
|
|
69
|
+
console.log();
|
|
70
|
+
}
|
|
71
|
+
// search function moved to lib.ts as searchTools
|
|
72
|
+
function compare(registry, slugs) {
|
|
73
|
+
const tools = slugs.map(slug => registry.tools.find(t => t.slug === slug)).filter((t) => t !== undefined);
|
|
74
|
+
if (tools.length === 0) {
|
|
75
|
+
console.log('No tools found');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Header
|
|
79
|
+
console.log('Tool'.padEnd(20) + tools.map(t => t.slug.padEnd(15)).join(''));
|
|
80
|
+
console.log('-'.repeat(20 + tools.length * 15));
|
|
81
|
+
// Rows
|
|
82
|
+
console.log('Agent Score'.padEnd(20) + tools.map(t => `${t.agentScore}/10`.padEnd(15)).join(''));
|
|
83
|
+
console.log('JSON Output'.padEnd(20) + tools.map(t => (t.capabilities.jsonOutput ? 'yes' : 'no').padEnd(15)).join(''));
|
|
84
|
+
console.log('Idempotent'.padEnd(20) + tools.map(t => (t.capabilities.idempotent ? 'yes' : 'no').padEnd(15)).join(''));
|
|
85
|
+
console.log('Interactive'.padEnd(20) + tools.map(t => (t.capabilities.interactive ? 'yes' : 'no').padEnd(15)).join(''));
|
|
86
|
+
console.log('Verified'.padEnd(20) + tools.map(t => (t.tier === 'verified' ? '✓' : '○').padEnd(15)).join(''));
|
|
87
|
+
console.log('Install (brew)'.padEnd(20) + tools.map(t => (t.install.brew || '-').padEnd(15)).join(''));
|
|
88
|
+
}
|
|
89
|
+
function installCmd(tool) {
|
|
90
|
+
console.log(`# Install ${tool.name}\n`);
|
|
91
|
+
if (tool.install.brew)
|
|
92
|
+
console.log(`# macOS (Homebrew)\nbrew install ${tool.install.brew}\n`);
|
|
93
|
+
if (tool.install.apt)
|
|
94
|
+
console.log(`# Debian/Ubuntu\nsudo apt install ${tool.install.apt}\n`);
|
|
95
|
+
if (tool.install.npm)
|
|
96
|
+
console.log(`# npm\nnpm install -g ${tool.install.npm}\n`);
|
|
97
|
+
if (tool.install.cargo)
|
|
98
|
+
console.log(`# Cargo (Rust)\ncargo install ${tool.install.cargo}\n`);
|
|
99
|
+
if (tool.install.go)
|
|
100
|
+
console.log(`# Go\ngo install ${tool.install.go}\n`);
|
|
101
|
+
if (tool.install.binary)
|
|
102
|
+
console.log(`# Binary download\n${tool.install.binary}\n`);
|
|
103
|
+
if (tool.install.script)
|
|
104
|
+
console.log(`# Install script\n${tool.install.script}\n`);
|
|
105
|
+
}
|
|
106
|
+
function main() {
|
|
107
|
+
const args = process.argv.slice(2);
|
|
108
|
+
const command = args[0];
|
|
109
|
+
if (!command || command === 'help' || command === '--help') {
|
|
110
|
+
console.log(`
|
|
111
|
+
mcli - A CLI for discovering CLI tools
|
|
112
|
+
|
|
113
|
+
Commands:
|
|
114
|
+
search <query> Search for CLI tools
|
|
115
|
+
info <slug> Show detailed info about a tool
|
|
116
|
+
compare <slug> [slug...] Compare multiple tools
|
|
117
|
+
install <slug> Show install commands for a tool
|
|
118
|
+
list List all tools
|
|
119
|
+
categories List all categories
|
|
120
|
+
|
|
121
|
+
Filters (for search and list):
|
|
122
|
+
--min-score=N Only show tools with agent score >= N
|
|
123
|
+
--category=NAME Filter by category
|
|
124
|
+
--agent-friendly Sort by agent score (highest first)
|
|
125
|
+
|
|
126
|
+
Tier Badges:
|
|
127
|
+
✓ = Verified (traced to official vendor source)
|
|
128
|
+
○ = Community (reviewed but not vendor-verified)
|
|
129
|
+
? = Unverified (auto-indexed, use with caution)
|
|
130
|
+
|
|
131
|
+
Examples:
|
|
132
|
+
mcli search cloud
|
|
133
|
+
mcli search git --category=git
|
|
134
|
+
mcli list --agent-friendly --min-score=8
|
|
135
|
+
mcli info gh
|
|
136
|
+
mcli compare aws gcloud hcloud
|
|
137
|
+
`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
let registry;
|
|
141
|
+
try {
|
|
142
|
+
registry = loadRegistry(registryPath);
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
if (err instanceof RegistryError) {
|
|
146
|
+
console.error(`Error: ${err.message}`);
|
|
147
|
+
if (err.cause) {
|
|
148
|
+
console.error(` Caused by: ${err.cause.message}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.error('Unexpected error loading registry:', err);
|
|
153
|
+
}
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
switch (command) {
|
|
157
|
+
case 'search': {
|
|
158
|
+
// Extract query (non-flag args after command)
|
|
159
|
+
const queryParts = args.slice(1).filter(a => !a.startsWith('--'));
|
|
160
|
+
const query = queryParts.join(' ');
|
|
161
|
+
if (!query) {
|
|
162
|
+
console.log('Usage: mcli search <query> [--min-score=N] [--category=NAME]');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
let results = searchTools(registry, query);
|
|
166
|
+
// Apply filters
|
|
167
|
+
const minScore = parseNumericFlag(args, '--min-score');
|
|
168
|
+
if (minScore !== null) {
|
|
169
|
+
results = filterByMinScore(results, minScore);
|
|
170
|
+
}
|
|
171
|
+
const category = parseFlag(args, '--category');
|
|
172
|
+
if (category && category !== 'true') {
|
|
173
|
+
results = filterByCategory(results, category);
|
|
174
|
+
}
|
|
175
|
+
// Sort by score if requested
|
|
176
|
+
if (args.includes('--agent-friendly')) {
|
|
177
|
+
results = sortByAgentScore(results);
|
|
178
|
+
}
|
|
179
|
+
if (results.length === 0) {
|
|
180
|
+
console.log('No tools found');
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
results.forEach(t => printTool(t));
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case 'info': {
|
|
188
|
+
const slug = args[1];
|
|
189
|
+
if (!slug) {
|
|
190
|
+
console.log('Usage: mcli info <slug>');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const tool = findTool(registry, slug);
|
|
194
|
+
if (!tool) {
|
|
195
|
+
console.log(`Tool not found: ${slug}`);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
printTool(tool, true);
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case 'compare': {
|
|
203
|
+
const slugs = args.slice(1);
|
|
204
|
+
if (slugs.length < 2) {
|
|
205
|
+
console.log('Usage: mcli compare <slug> <slug> [slug...]');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
compare(registry, slugs);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case 'install': {
|
|
212
|
+
const slug = args[1];
|
|
213
|
+
if (!slug) {
|
|
214
|
+
console.log('Usage: mcli install <slug>');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const tool = findTool(registry, slug);
|
|
218
|
+
if (!tool) {
|
|
219
|
+
console.log(`Tool not found: ${slug}`);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
installCmd(tool);
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
case 'list': {
|
|
227
|
+
let tools = [...registry.tools];
|
|
228
|
+
// Apply filters
|
|
229
|
+
const minScore = parseNumericFlag(args, '--min-score');
|
|
230
|
+
if (minScore !== null) {
|
|
231
|
+
tools = filterByMinScore(tools, minScore);
|
|
232
|
+
}
|
|
233
|
+
const category = parseFlag(args, '--category');
|
|
234
|
+
if (category && category !== 'true') {
|
|
235
|
+
tools = filterByCategory(tools, category);
|
|
236
|
+
}
|
|
237
|
+
// Sort by score if requested
|
|
238
|
+
if (args.includes('--agent-friendly')) {
|
|
239
|
+
tools = sortByAgentScore(tools);
|
|
240
|
+
}
|
|
241
|
+
if (tools.length === 0) {
|
|
242
|
+
console.log('No tools found matching filters');
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
tools.forEach(t => printTool(t));
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case 'categories': {
|
|
250
|
+
const cats = getCategories(registry);
|
|
251
|
+
console.log('Categories:', cats.join(', '));
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
default:
|
|
255
|
+
console.log(`Unknown command: ${command}\nRun 'mcli help' for usage.`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
main();
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatting helpers for CLI output.
|
|
3
|
+
* Extracted for testability.
|
|
4
|
+
*/
|
|
5
|
+
import type { CliTool } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* ANSI color code for agent score.
|
|
8
|
+
*/
|
|
9
|
+
export declare function scoreColor(score: number): string;
|
|
10
|
+
/**
|
|
11
|
+
* ANSI reset code.
|
|
12
|
+
*/
|
|
13
|
+
export declare function reset(): string;
|
|
14
|
+
/**
|
|
15
|
+
* Format a tool's header line.
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatToolHeader(tool: CliTool): string;
|
|
18
|
+
/**
|
|
19
|
+
* Format a tool for brief display.
|
|
20
|
+
*/
|
|
21
|
+
export declare function formatToolBrief(tool: CliTool): string;
|
|
22
|
+
/**
|
|
23
|
+
* Format a tool for detailed display.
|
|
24
|
+
*/
|
|
25
|
+
export declare function formatToolDetailed(tool: CliTool): string;
|
|
26
|
+
/**
|
|
27
|
+
* Format a comparison table for multiple tools.
|
|
28
|
+
*/
|
|
29
|
+
export declare function formatCompareTable(tools: CliTool[]): string;
|
|
30
|
+
/**
|
|
31
|
+
* Format install commands for a tool.
|
|
32
|
+
*/
|
|
33
|
+
export declare function formatInstallCommands(tool: CliTool): string;
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { tierBadge, compareTools } from './lib.js';
|
|
2
|
+
/**
|
|
3
|
+
* ANSI color code for agent score.
|
|
4
|
+
*/
|
|
5
|
+
export function scoreColor(score) {
|
|
6
|
+
if (score >= 8)
|
|
7
|
+
return '\x1b[32m'; // green
|
|
8
|
+
if (score >= 5)
|
|
9
|
+
return '\x1b[33m'; // yellow
|
|
10
|
+
return '\x1b[31m'; // red
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* ANSI reset code.
|
|
14
|
+
*/
|
|
15
|
+
export function reset() {
|
|
16
|
+
return '\x1b[0m';
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Format a tool's header line.
|
|
20
|
+
*/
|
|
21
|
+
export function formatToolHeader(tool) {
|
|
22
|
+
const badge = tierBadge(tool.tier);
|
|
23
|
+
const color = scoreColor(tool.agentScore);
|
|
24
|
+
return `${badge} ${tool.name} (${tool.slug}) - Agent Score: ${color}${tool.agentScore}/10${reset()}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Format a tool for brief display.
|
|
28
|
+
*/
|
|
29
|
+
export function formatToolBrief(tool) {
|
|
30
|
+
return `${formatToolHeader(tool)}\n ${tool.description}`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Format a tool for detailed display.
|
|
34
|
+
*/
|
|
35
|
+
export function formatToolDetailed(tool) {
|
|
36
|
+
const lines = [formatToolHeader(tool), ` ${tool.description}`];
|
|
37
|
+
lines.push(` Vendor: ${tool.vendor.name} (${tool.vendor.domain})`);
|
|
38
|
+
lines.push(` Categories: ${tool.categories.join(', ')}`);
|
|
39
|
+
lines.push(` Install:`);
|
|
40
|
+
for (const [method, cmd] of Object.entries(tool.install)) {
|
|
41
|
+
if (cmd)
|
|
42
|
+
lines.push(` ${method}: ${cmd}`);
|
|
43
|
+
}
|
|
44
|
+
lines.push(` Capabilities:`);
|
|
45
|
+
lines.push(` JSON output: ${tool.capabilities.jsonOutput ? 'yes' : 'no'}`);
|
|
46
|
+
lines.push(` Idempotent: ${tool.capabilities.idempotent ? 'yes' : 'no'}`);
|
|
47
|
+
lines.push(` Interactive: ${tool.capabilities.interactive ? 'yes' : 'no'}`);
|
|
48
|
+
if (tool.capabilities.auth.length > 0) {
|
|
49
|
+
lines.push(` Auth: ${tool.capabilities.auth.join(', ')}`);
|
|
50
|
+
}
|
|
51
|
+
if (tool.repo)
|
|
52
|
+
lines.push(` Repo: ${tool.repo}`);
|
|
53
|
+
if (tool.docs)
|
|
54
|
+
lines.push(` Docs: ${tool.docs}`);
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Format a comparison table for multiple tools.
|
|
59
|
+
*/
|
|
60
|
+
export function formatCompareTable(tools) {
|
|
61
|
+
if (tools.length === 0) {
|
|
62
|
+
return 'No tools found';
|
|
63
|
+
}
|
|
64
|
+
const rows = compareTools(tools);
|
|
65
|
+
const lines = [];
|
|
66
|
+
// Header
|
|
67
|
+
lines.push('Tool'.padEnd(20) + tools.map(t => t.slug.padEnd(15)).join(''));
|
|
68
|
+
lines.push('-'.repeat(20 + tools.length * 15));
|
|
69
|
+
// Format values
|
|
70
|
+
const formatValue = (v) => {
|
|
71
|
+
if (typeof v === 'boolean')
|
|
72
|
+
return v ? 'yes' : 'no';
|
|
73
|
+
if (v === 'verified')
|
|
74
|
+
return '✓';
|
|
75
|
+
if (v === 'community')
|
|
76
|
+
return '○';
|
|
77
|
+
if (v === 'unverified')
|
|
78
|
+
return '?';
|
|
79
|
+
return String(v);
|
|
80
|
+
};
|
|
81
|
+
for (const row of rows) {
|
|
82
|
+
const label = row.field === 'Agent Score' ? 'Agent Score' :
|
|
83
|
+
row.field === 'JSON Output' ? 'JSON Output' :
|
|
84
|
+
row.field === 'Idempotent' ? 'Idempotent' :
|
|
85
|
+
row.field === 'Interactive' ? 'Interactive' :
|
|
86
|
+
row.field === 'Streaming' ? 'Streaming' :
|
|
87
|
+
row.field === 'Tier' ? 'Verified' : row.field;
|
|
88
|
+
const values = row.values.map(v => {
|
|
89
|
+
const formatted = formatValue(v);
|
|
90
|
+
return (row.field === 'Agent Score' ? `${formatted}/10` : formatted).padEnd(15);
|
|
91
|
+
}).join('');
|
|
92
|
+
lines.push(label.padEnd(20) + values);
|
|
93
|
+
}
|
|
94
|
+
return lines.join('\n');
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Format install commands for a tool.
|
|
98
|
+
*/
|
|
99
|
+
export function formatInstallCommands(tool) {
|
|
100
|
+
const lines = [`# Install ${tool.name}`, ''];
|
|
101
|
+
if (tool.install.brew) {
|
|
102
|
+
lines.push('# macOS (Homebrew)');
|
|
103
|
+
lines.push(`brew install ${tool.install.brew}`);
|
|
104
|
+
lines.push('');
|
|
105
|
+
}
|
|
106
|
+
if (tool.install.apt) {
|
|
107
|
+
lines.push('# Debian/Ubuntu');
|
|
108
|
+
lines.push(`sudo apt install ${tool.install.apt}`);
|
|
109
|
+
lines.push('');
|
|
110
|
+
}
|
|
111
|
+
if (tool.install.npm) {
|
|
112
|
+
lines.push('# npm');
|
|
113
|
+
lines.push(`npm install -g ${tool.install.npm}`);
|
|
114
|
+
lines.push('');
|
|
115
|
+
}
|
|
116
|
+
if (tool.install.cargo) {
|
|
117
|
+
lines.push('# Cargo (Rust)');
|
|
118
|
+
lines.push(`cargo install ${tool.install.cargo}`);
|
|
119
|
+
lines.push('');
|
|
120
|
+
}
|
|
121
|
+
if (tool.install.go) {
|
|
122
|
+
lines.push('# Go');
|
|
123
|
+
lines.push(`go install ${tool.install.go}`);
|
|
124
|
+
lines.push('');
|
|
125
|
+
}
|
|
126
|
+
if (tool.install.binary) {
|
|
127
|
+
lines.push('# Binary download');
|
|
128
|
+
lines.push(tool.install.binary);
|
|
129
|
+
lines.push('');
|
|
130
|
+
}
|
|
131
|
+
if (tool.install.script) {
|
|
132
|
+
lines.push('# Install script');
|
|
133
|
+
lines.push(tool.install.script);
|
|
134
|
+
lines.push('');
|
|
135
|
+
}
|
|
136
|
+
return lines.join('\n');
|
|
137
|
+
}
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core library functions for mcli.
|
|
3
|
+
* Extracted from CLI for testability.
|
|
4
|
+
*/
|
|
5
|
+
import type { Registry, CliTool, AgentScores } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Compute overall agentScore from detailed agentScores.
|
|
8
|
+
* Weighted average: json(3) + nonInteractive(3) + tokenEfficiency(2) + safety(1) + pipeline(1)
|
|
9
|
+
* Returns 1-10 scale.
|
|
10
|
+
*/
|
|
11
|
+
export declare function computeAgentScore(scores: AgentScores): number;
|
|
12
|
+
/**
|
|
13
|
+
* Validate agentScores object.
|
|
14
|
+
*/
|
|
15
|
+
export declare function validateAgentScores(scores: unknown): {
|
|
16
|
+
valid: boolean;
|
|
17
|
+
errors: string[];
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Search tools by query string.
|
|
21
|
+
* Matches against slug, name, description, and categories.
|
|
22
|
+
*/
|
|
23
|
+
export declare function searchTools(registry: Registry, query: string): CliTool[];
|
|
24
|
+
/**
|
|
25
|
+
* Find a tool by exact slug match.
|
|
26
|
+
*/
|
|
27
|
+
export declare function findTool(registry: Registry, slug: string): CliTool | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Get all unique categories from the registry.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getCategories(registry: Registry): string[];
|
|
32
|
+
/**
|
|
33
|
+
* Sort tools by agent score (descending).
|
|
34
|
+
*/
|
|
35
|
+
export declare function sortByAgentScore(tools: CliTool[]): CliTool[];
|
|
36
|
+
/**
|
|
37
|
+
* Filter tools by minimum agent score.
|
|
38
|
+
*/
|
|
39
|
+
export declare function filterByMinScore(tools: CliTool[], minScore: number): CliTool[];
|
|
40
|
+
/**
|
|
41
|
+
* Filter tools by category.
|
|
42
|
+
*/
|
|
43
|
+
export declare function filterByCategory(tools: CliTool[], category: string): CliTool[];
|
|
44
|
+
/**
|
|
45
|
+
* Filter tools by verification tier.
|
|
46
|
+
*/
|
|
47
|
+
export declare function filterByTier(tools: CliTool[], tier: CliTool['tier']): CliTool[];
|
|
48
|
+
/**
|
|
49
|
+
* Get tier badge character.
|
|
50
|
+
*/
|
|
51
|
+
export declare function tierBadge(tier: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* Validate a tool object has required fields.
|
|
54
|
+
*/
|
|
55
|
+
export declare function validateTool(tool: unknown): {
|
|
56
|
+
valid: boolean;
|
|
57
|
+
errors: string[];
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Validate entire registry.
|
|
61
|
+
*/
|
|
62
|
+
export declare function validateRegistry(registry: unknown): {
|
|
63
|
+
valid: boolean;
|
|
64
|
+
errors: string[];
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Generate install command string for a tool and platform.
|
|
68
|
+
*/
|
|
69
|
+
export declare function getInstallCommand(tool: CliTool, platform: keyof CliTool['install']): string | null;
|
|
70
|
+
/**
|
|
71
|
+
* Compare tools and return a comparison matrix.
|
|
72
|
+
*/
|
|
73
|
+
export interface ComparisonRow {
|
|
74
|
+
field: string;
|
|
75
|
+
values: (string | boolean | number)[];
|
|
76
|
+
}
|
|
77
|
+
export declare function compareTools(tools: CliTool[]): ComparisonRow[];
|