@agentneeds/need 0.1.0
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 +79 -0
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.js +53 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/report.d.ts +4 -0
- package/dist/commands/report.js +28 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/search.d.ts +1 -0
- package/dist/commands/search.js +15 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/serve.d.ts +1 -0
- package/dist/commands/serve.js +135 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +56 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-client.d.ts +12 -0
- package/dist/lib/api-client.js +37 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/formatter.d.ts +2 -0
- package/dist/lib/formatter.js +27 -0
- package/dist/lib/formatter.js.map +1 -0
- package/dist/lib/types.d.ts +22 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# need
|
|
2
|
+
|
|
3
|
+
> Discover the right CLI tool for any task using plain English.
|
|
4
|
+
|
|
5
|
+
Semantic search across 6,000+ CLI tools. Works standalone or as an MCP server for AI coding agents.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @agentneeds/need
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or run directly: `npx @agentneeds/need "compress png images"`
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
### Search
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
need convert pdf to png
|
|
21
|
+
need compress video files
|
|
22
|
+
need find duplicate files
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Install
|
|
26
|
+
|
|
27
|
+
Search and install interactively:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
need install "compress png images"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Report
|
|
34
|
+
|
|
35
|
+
Help improve results by reporting whether a tool worked:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
need report jq --success
|
|
39
|
+
need report sometool --fail
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### MCP setup
|
|
43
|
+
|
|
44
|
+
Configure `need` as an MCP server for your AI tools:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
need setup
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Supports **Claude Code** and **Cursor**. After setup, your AI agent can discover, install, and report on tools autonomously.
|
|
51
|
+
|
|
52
|
+
### MCP server
|
|
53
|
+
|
|
54
|
+
Run the MCP server directly (used by `need setup` under the hood):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
need serve
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Exposes three tools:
|
|
61
|
+
- `search_tools` — semantic search across CLI tools
|
|
62
|
+
- `install_tool` — install with a security allowlist (brew, apt, npm, pip, cargo only)
|
|
63
|
+
- `report_tool_usage` — report success or failure to improve rankings
|
|
64
|
+
|
|
65
|
+
## How it works
|
|
66
|
+
|
|
67
|
+
Queries are converted to embeddings and matched against a vector database of CLI tools using pgvector. Results are ranked by semantic similarity combined with community success/failure signals.
|
|
68
|
+
|
|
69
|
+
No API keys required. No accounts. Just install and search.
|
|
70
|
+
|
|
71
|
+
## Links
|
|
72
|
+
|
|
73
|
+
- [GitHub](https://github.com/tuckerschreiber/need)
|
|
74
|
+
- [Website](https://agentneed.dev)
|
|
75
|
+
- [npm](https://www.npmjs.com/package/@agentneeds/need)
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function installCommand(query: string): Promise<void>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { NeedApiClient } from '../lib/api-client.js';
|
|
4
|
+
import { formatResults } from '../lib/formatter.js';
|
|
5
|
+
export async function installCommand(query) {
|
|
6
|
+
const client = new NeedApiClient();
|
|
7
|
+
const { results } = await client.search(query, 5);
|
|
8
|
+
if (results.length === 0) {
|
|
9
|
+
console.log(formatResults(results));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
console.log(formatResults(results));
|
|
13
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
14
|
+
const answer = await new Promise((resolve) => {
|
|
15
|
+
rl.question(` Install which tool? [1-${results.length}/n] `, (ans) => {
|
|
16
|
+
resolve(ans.trim());
|
|
17
|
+
rl.close();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
if (!answer || answer.toLowerCase() === 'n') {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const index = parseInt(answer, 10);
|
|
24
|
+
if (isNaN(index) || index < 1 || index > results.length) {
|
|
25
|
+
console.error(`\n Invalid selection: ${answer}\n`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const tool = results[index - 1];
|
|
29
|
+
console.log(`\n Running: ${tool.install_command}\n`);
|
|
30
|
+
try {
|
|
31
|
+
execSync(tool.install_command, { stdio: 'inherit' });
|
|
32
|
+
console.log(`\n Installed ${tool.name} successfully.\n`);
|
|
33
|
+
if (tool.usage_examples && tool.usage_examples.length > 0) {
|
|
34
|
+
console.log(' Try this:');
|
|
35
|
+
tool.usage_examples.slice(0, 2).forEach((ex) => {
|
|
36
|
+
console.log(` ${ex.command} # ${ex.description}`);
|
|
37
|
+
});
|
|
38
|
+
console.log('');
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
await client.reportSignal(tool.id, true, query);
|
|
42
|
+
}
|
|
43
|
+
catch { /* best-effort */ }
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
console.error(`\n Failed to install ${tool.name}.\n`);
|
|
47
|
+
try {
|
|
48
|
+
await client.reportSignal(tool.id, false, query);
|
|
49
|
+
}
|
|
50
|
+
catch { /* best-effort */ }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NeedApiClient } from '../lib/api-client.js';
|
|
2
|
+
export async function reportCommand(toolName, opts) {
|
|
3
|
+
if (!opts.success && !opts.fail) {
|
|
4
|
+
console.error('\n Error: Please specify --success or --fail\n');
|
|
5
|
+
process.exit(1);
|
|
6
|
+
}
|
|
7
|
+
const success = !!opts.success;
|
|
8
|
+
const client = new NeedApiClient();
|
|
9
|
+
try {
|
|
10
|
+
const { results } = await client.search(toolName, 10);
|
|
11
|
+
if (results.length === 0) {
|
|
12
|
+
console.error(`\n Error: No tool found matching "${toolName}"\n`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
// Prefer exact match (case-insensitive), fall back to first result
|
|
16
|
+
const exact = results.find((r) => r.name.toLowerCase() === toolName.toLowerCase());
|
|
17
|
+
const tool = exact ?? results[0];
|
|
18
|
+
await client.reportSignal(tool.id, success, toolName);
|
|
19
|
+
const outcome = success ? 'success' : 'failure';
|
|
20
|
+
console.log(`\n Reported ${outcome} for "${tool.name}". Thanks for the feedback!\n`);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
24
|
+
console.error(`\n Error: ${message}\n`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/commands/report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAgB,EAChB,IAA2C;IAE3C,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACjE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;IAEnC,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAEtD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,KAAK,CAAC,sCAAsC,QAAQ,KAAK,CAAC,CAAC;YACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,mEAAmE;QACnE,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CACxB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC,WAAW,EAAE,CACvD,CAAC;QACF,MAAM,IAAI,GAAiB,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;QAE/C,MAAM,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAEtD,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,gBAAgB,OAAO,SAAS,IAAI,CAAC,IAAI,+BAA+B,CAAC,CAAC;IACxF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;QACrE,OAAO,CAAC,KAAK,CAAC,cAAc,OAAO,IAAI,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function searchCommand(query: string): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NeedApiClient } from '../lib/api-client.js';
|
|
2
|
+
import { formatResults } from '../lib/formatter.js';
|
|
3
|
+
export async function searchCommand(query) {
|
|
4
|
+
const client = new NeedApiClient();
|
|
5
|
+
try {
|
|
6
|
+
const { results } = await client.search(query);
|
|
7
|
+
console.log(formatResults(results));
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
11
|
+
console.error(`\n Error: ${message}\n`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAa;IAC/C,MAAM,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;IAEnC,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC/C,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;QACrE,OAAO,CAAC,KAAK,CAAC,cAAc,OAAO,IAAI,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function serveCommand(): Promise<void>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { NeedApiClient } from '../lib/api-client.js';
|
|
6
|
+
export async function serveCommand() {
|
|
7
|
+
const server = new McpServer({
|
|
8
|
+
name: 'need',
|
|
9
|
+
version: '0.1.0',
|
|
10
|
+
});
|
|
11
|
+
const client = new NeedApiClient();
|
|
12
|
+
server.tool('search_tools', 'Search for CLI tools by describing what you need in plain English', {
|
|
13
|
+
query: z.string().describe('What you need the tool to do, in plain English'),
|
|
14
|
+
limit: z.number().optional().default(5).describe('Max results to return'),
|
|
15
|
+
}, async ({ query, limit }) => {
|
|
16
|
+
try {
|
|
17
|
+
const { results } = await client.search(query, limit);
|
|
18
|
+
if (results.length === 0) {
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: 'text', text: 'No tools found for that query. Try different words.' }],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const text = results
|
|
24
|
+
.map((t, i) => {
|
|
25
|
+
let entry = `${i + 1}. **${t.name}** (id: ${t.id}) — ${t.short_description || t.description}\n Install: \`${t.install_command}\``;
|
|
26
|
+
if (t.usage_examples && t.usage_examples.length > 0) {
|
|
27
|
+
const examples = t.usage_examples
|
|
28
|
+
.slice(0, 3)
|
|
29
|
+
.map((ex) => ` - \`${ex.command}\` — ${ex.description}`)
|
|
30
|
+
.join('\n');
|
|
31
|
+
entry += '\n Usage:\n' + examples;
|
|
32
|
+
}
|
|
33
|
+
entry += `\n Success rate: ${Math.round(t.success_rate * 100)}% (${t.use_count} uses)`;
|
|
34
|
+
return entry;
|
|
35
|
+
})
|
|
36
|
+
.join('\n\n');
|
|
37
|
+
const footer = '\n\n---\n\u{1F4A1} Use report_tool_usage with the tool id to report whether a tool worked.';
|
|
38
|
+
return { content: [{ type: 'text', text: text + footer }] };
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
42
|
+
return { content: [{ type: 'text', text: `Error searching: ${message}` }], isError: true };
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
server.tool('report_tool_usage', 'Report whether a discovered tool worked for your task. Include the command you ran for better future recommendations.', {
|
|
46
|
+
tool_id: z.number().describe('The tool ID from search results'),
|
|
47
|
+
success: z.boolean().describe('Whether the tool worked'),
|
|
48
|
+
query_text: z.string().optional().describe('The original search query'),
|
|
49
|
+
command_ran: z.string().optional().describe('The actual command that was executed'),
|
|
50
|
+
context: z.string().optional().describe('What task you were trying to accomplish'),
|
|
51
|
+
}, async ({ tool_id, success, query_text, command_ran, context }) => {
|
|
52
|
+
try {
|
|
53
|
+
await client.reportSignal(tool_id, success, query_text, command_ran, context);
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: 'text', text: `Signal recorded: ${success ? 'success' : 'failure'} for tool ${tool_id}` }],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
60
|
+
return { content: [{ type: 'text', text: `Error reporting: ${message}` }], isError: true };
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
const ALLOWED_COMMANDS = {
|
|
64
|
+
'brew': ['install'],
|
|
65
|
+
'apt': ['install'],
|
|
66
|
+
'apt-get': ['install'],
|
|
67
|
+
'npm': ['install'],
|
|
68
|
+
'pip': ['install'],
|
|
69
|
+
'pip3': ['install'],
|
|
70
|
+
'cargo': ['install'],
|
|
71
|
+
};
|
|
72
|
+
function parseAndValidateCommand(command) {
|
|
73
|
+
const parts = command.split(/\s+/).filter(Boolean);
|
|
74
|
+
if (parts.length < 2)
|
|
75
|
+
return null;
|
|
76
|
+
const [bin, subcommand, ...rest] = parts;
|
|
77
|
+
const allowedSubs = ALLOWED_COMMANDS[bin];
|
|
78
|
+
if (!allowedSubs || !allowedSubs.includes(subcommand))
|
|
79
|
+
return null;
|
|
80
|
+
// Reject shell metacharacters in any argument
|
|
81
|
+
const dangerous = /[;&|`$()><\n\\]/;
|
|
82
|
+
if (rest.some((arg) => dangerous.test(arg)))
|
|
83
|
+
return null;
|
|
84
|
+
return { bin, args: [subcommand, ...rest] };
|
|
85
|
+
}
|
|
86
|
+
server.tool('install_tool', 'Install a CLI tool using a package manager. Only allows safe install commands.', {
|
|
87
|
+
command: z.string().describe('The install command to run (e.g. "brew install jq")'),
|
|
88
|
+
tool_id: z.number().optional().describe('Tool ID from search results, for auto-reporting success/failure'),
|
|
89
|
+
}, async ({ command, tool_id }) => {
|
|
90
|
+
const parsed = parseAndValidateCommand(command);
|
|
91
|
+
if (!parsed) {
|
|
92
|
+
return {
|
|
93
|
+
content: [
|
|
94
|
+
{
|
|
95
|
+
type: 'text',
|
|
96
|
+
text: `Error: command not allowed. Must be a simple install command using: ${Object.keys(ALLOWED_COMMANDS).join(', ')}`,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
isError: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const output = execFileSync(parsed.bin, parsed.args, { encoding: 'utf-8', timeout: 120000 });
|
|
104
|
+
if (tool_id !== undefined) {
|
|
105
|
+
try {
|
|
106
|
+
await client.reportSignal(tool_id, true);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// best-effort reporting
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
content: [{ type: 'text', text: output || 'Install completed successfully.' }],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
if (tool_id !== undefined) {
|
|
118
|
+
try {
|
|
119
|
+
await client.reportSignal(tool_id, false);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// best-effort reporting
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
126
|
+
return {
|
|
127
|
+
content: [{ type: 'text', text: `Install failed: ${message}` }],
|
|
128
|
+
isError: true,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
const transport = new StdioServerTransport();
|
|
133
|
+
await server.connect(transport);
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=serve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function setupCommand(): Promise<void>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
const MCP_CONFIG = { command: 'npx', args: ['@agentneeds/need', 'serve'] };
|
|
5
|
+
function ensureDir(dirPath) {
|
|
6
|
+
if (!fs.existsSync(dirPath)) {
|
|
7
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function configureMcpServer(filePath, key) {
|
|
11
|
+
let config;
|
|
12
|
+
if (fs.existsSync(filePath)) {
|
|
13
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
14
|
+
config = JSON.parse(raw);
|
|
15
|
+
if (config.mcpServers && key in config.mcpServers) {
|
|
16
|
+
return 'already configured';
|
|
17
|
+
}
|
|
18
|
+
if (!config.mcpServers) {
|
|
19
|
+
config.mcpServers = {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
config = { mcpServers: {} };
|
|
24
|
+
}
|
|
25
|
+
config.mcpServers[key] = MCP_CONFIG;
|
|
26
|
+
ensureDir(path.dirname(filePath));
|
|
27
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
28
|
+
return 'configured';
|
|
29
|
+
}
|
|
30
|
+
export async function setupCommand() {
|
|
31
|
+
const home = os.homedir();
|
|
32
|
+
const results = [];
|
|
33
|
+
// Claude Code
|
|
34
|
+
const claudeConfigPath = path.join(home, '.claude.json');
|
|
35
|
+
const claudeResult = configureMcpServer(claudeConfigPath, 'need');
|
|
36
|
+
results.push({ name: 'Claude Code', result: claudeResult });
|
|
37
|
+
// Cursor
|
|
38
|
+
const cursorDir = path.join(home, '.cursor');
|
|
39
|
+
if (fs.existsSync(cursorDir) && fs.statSync(cursorDir).isDirectory()) {
|
|
40
|
+
const cursorConfigPath = path.join(cursorDir, 'mcp.json');
|
|
41
|
+
const cursorResult = configureMcpServer(cursorConfigPath, 'need');
|
|
42
|
+
results.push({ name: 'Cursor', result: cursorResult });
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
results.push({ name: 'Cursor', result: 'skipped', reason: 'not installed' });
|
|
46
|
+
}
|
|
47
|
+
for (const { name, result, reason } of results) {
|
|
48
|
+
if (result === 'skipped') {
|
|
49
|
+
console.log(`\u2013 ${name} \u2014 skipped (${reason})`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log(`\u2713 ${name} \u2014 ${result}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import { searchCommand } from './commands/search.js';
|
|
4
|
+
import { reportCommand } from './commands/report.js';
|
|
5
|
+
program
|
|
6
|
+
.name('need')
|
|
7
|
+
.description('Discover the right CLI tool for any task')
|
|
8
|
+
.version('0.1.0')
|
|
9
|
+
.argument('[query...]', 'What you need (in plain English)')
|
|
10
|
+
.action(async (queryParts) => {
|
|
11
|
+
if (queryParts.length === 0) {
|
|
12
|
+
program.help();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const query = queryParts.join(' ');
|
|
16
|
+
await searchCommand(query);
|
|
17
|
+
});
|
|
18
|
+
program
|
|
19
|
+
.command('report <tool>')
|
|
20
|
+
.description('Report whether a tool worked')
|
|
21
|
+
.option('--success', 'The tool worked')
|
|
22
|
+
.option('--fail', 'The tool did not work')
|
|
23
|
+
.action(async (tool, opts) => {
|
|
24
|
+
await reportCommand(tool, opts);
|
|
25
|
+
});
|
|
26
|
+
program
|
|
27
|
+
.command('serve')
|
|
28
|
+
.description('Run as MCP server (for Claude Code, Cursor, etc.)')
|
|
29
|
+
.action(async () => {
|
|
30
|
+
const { serveCommand } = await import('./commands/serve.js');
|
|
31
|
+
await serveCommand();
|
|
32
|
+
});
|
|
33
|
+
program
|
|
34
|
+
.command('setup')
|
|
35
|
+
.description('Configure need as MCP server for your AI tools')
|
|
36
|
+
.action(async () => {
|
|
37
|
+
const { setupCommand } = await import('./commands/setup.js');
|
|
38
|
+
await setupCommand();
|
|
39
|
+
});
|
|
40
|
+
program
|
|
41
|
+
.command('install <query...>')
|
|
42
|
+
.description('Search and install a CLI tool interactively')
|
|
43
|
+
.action(async (queryParts) => {
|
|
44
|
+
const { installCommand } = await import('./commands/install.js');
|
|
45
|
+
const query = queryParts.join(' ');
|
|
46
|
+
await installCommand(query);
|
|
47
|
+
});
|
|
48
|
+
program.parse();
|
|
49
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO;KACJ,IAAI,CAAC,MAAM,CAAC;KACZ,WAAW,CAAC,0CAA0C,CAAC;KACvD,OAAO,CAAC,OAAO,CAAC;KAChB,QAAQ,CAAC,YAAY,EAAE,kCAAkC,CAAC;KAC1D,MAAM,CAAC,KAAK,EAAE,UAAoB,EAAE,EAAE;IACrC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,IAAI,EAAE,CAAC;QACf,OAAO;IACT,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,eAAe,CAAC;KACxB,WAAW,CAAC,8BAA8B,CAAC;KAC3C,MAAM,CAAC,WAAW,EAAE,iBAAiB,CAAC;KACtC,MAAM,CAAC,QAAQ,EAAE,uBAAuB,CAAC;KACzC,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,IAA2C,EAAE,EAAE;IAC1E,MAAM,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAClC,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,mDAAmD,CAAC;KAChE,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;IAC7D,MAAM,YAAY,EAAE,CAAC;AACvB,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,gDAAgD,CAAC;KAC7D,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;IAC7D,MAAM,YAAY,EAAE,CAAC;AACvB,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,oBAAoB,CAAC;KAC7B,WAAW,CAAC,6CAA6C,CAAC;KAC1D,MAAM,CAAC,KAAK,EAAE,UAAoB,EAAE,EAAE;IACrC,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,cAAc,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SearchResult } from './types.js';
|
|
2
|
+
interface SearchResponse {
|
|
3
|
+
results: SearchResult[];
|
|
4
|
+
query: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class NeedApiClient {
|
|
7
|
+
private baseUrl;
|
|
8
|
+
constructor(baseUrl?: string);
|
|
9
|
+
search(query: string, limit?: number): Promise<SearchResponse>;
|
|
10
|
+
reportSignal(toolId: number, success: boolean, queryText?: string, commandRan?: string, context?: string): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const DEFAULT_API_URL = 'https://need-api.schreibertucbiz.workers.dev';
|
|
2
|
+
export class NeedApiClient {
|
|
3
|
+
baseUrl;
|
|
4
|
+
constructor(baseUrl) {
|
|
5
|
+
this.baseUrl = baseUrl ?? process.env.NEED_API_URL ?? DEFAULT_API_URL;
|
|
6
|
+
}
|
|
7
|
+
async search(query, limit = 10) {
|
|
8
|
+
const url = new URL('/search', this.baseUrl);
|
|
9
|
+
url.searchParams.set('q', query);
|
|
10
|
+
url.searchParams.set('limit', String(limit));
|
|
11
|
+
const res = await fetch(url.toString());
|
|
12
|
+
if (!res.ok) {
|
|
13
|
+
const body = await res.text();
|
|
14
|
+
throw new Error(`API error (${res.status}): ${body}`);
|
|
15
|
+
}
|
|
16
|
+
return res.json();
|
|
17
|
+
}
|
|
18
|
+
async reportSignal(toolId, success, queryText, commandRan, context) {
|
|
19
|
+
const res = await fetch(new URL('/signal', this.baseUrl).toString(), {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: { 'Content-Type': 'application/json' },
|
|
22
|
+
body: JSON.stringify({
|
|
23
|
+
tool_id: toolId,
|
|
24
|
+
success,
|
|
25
|
+
query_text: queryText,
|
|
26
|
+
agent_type: 'cli',
|
|
27
|
+
command_ran: commandRan,
|
|
28
|
+
context: context,
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const body = await res.text();
|
|
33
|
+
throw new Error(`Signal error (${res.status}): ${body}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"AAEA,MAAM,eAAe,GAAG,8CAA8C,CAAC;AAOvE,MAAM,OAAO,aAAa;IAChB,OAAO,CAAS;IAExB,YAAY,OAAgB;QAC1B,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,eAAe,CAAC;IACxE,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,KAAK,GAAG,EAAE;QACpC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC7C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACjC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAE7C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,cAAc,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,GAAG,CAAC,IAAI,EAA6B,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,MAAc,EAAE,OAAgB,EAAE,SAAkB,EAAE,UAAmB,EAAE,OAAgB;QAC5G,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YACnE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,OAAO,EAAE,MAAM;gBACf,OAAO;gBACP,UAAU,EAAE,SAAS;gBACrB,UAAU,EAAE,KAAK;gBACjB,WAAW,EAAE,UAAU;gBACvB,OAAO,EAAE,OAAO;aACjB,CAAC;SACH,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function formatResults(results) {
|
|
2
|
+
if (results.length === 0) {
|
|
3
|
+
return '\n No tools found for that query. Try different words.\n';
|
|
4
|
+
}
|
|
5
|
+
const lines = results.map((tool, i) => {
|
|
6
|
+
const num = ` ${i + 1}.`;
|
|
7
|
+
const name = tool.name;
|
|
8
|
+
const rate = `${Math.round(tool.success_rate * 100)}% success`;
|
|
9
|
+
const uses = tool.use_count > 0 ? ` ${formatCount(tool.use_count)} uses` : '';
|
|
10
|
+
let block = `${num} ${name}\n ${tool.install_command} ${rate}${uses}`;
|
|
11
|
+
if (tool.usage_examples && tool.usage_examples.length > 0) {
|
|
12
|
+
const examples = tool.usage_examples
|
|
13
|
+
.slice(0, 3)
|
|
14
|
+
.map((ex) => ` ${ex.command} # ${ex.description}`)
|
|
15
|
+
.join('\n');
|
|
16
|
+
block += '\n Usage:\n' + examples;
|
|
17
|
+
}
|
|
18
|
+
return block;
|
|
19
|
+
});
|
|
20
|
+
return '\n' + lines.join('\n\n') + '\n';
|
|
21
|
+
}
|
|
22
|
+
function formatCount(n) {
|
|
23
|
+
if (n >= 1000)
|
|
24
|
+
return `${(n / 1000).toFixed(1)}k`;
|
|
25
|
+
return String(n);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=formatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatter.js","sourceRoot":"","sources":["../../src/lib/formatter.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,aAAa,CAAC,OAAqB;IACjD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,2DAA2D,CAAC;IACrE,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;QACpC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACvB,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,GAAG,GAAG,CAAC,WAAW,CAAC;QAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAE/E,IAAI,KAAK,GAAG,GAAG,GAAG,IAAI,IAAI,UAAU,IAAI,CAAC,eAAe,OAAO,IAAI,GAAG,IAAI,EAAE,CAAC;QAE7E,IAAI,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc;iBACjC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;iBACX,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,OAAO,OAAO,EAAE,CAAC,WAAW,EAAE,CAAC;iBACxD,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,KAAK,IAAI,iBAAiB,GAAG,QAAQ,CAAC;QACxC,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,OAAO,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;AAC1C,CAAC;AAED,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,CAAC,IAAI,IAAI;QAAE,OAAO,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAClD,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface UsageExample {
|
|
2
|
+
description: string;
|
|
3
|
+
command: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ToolResult {
|
|
6
|
+
id: number;
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
short_description: string | null;
|
|
10
|
+
install_command: string;
|
|
11
|
+
package_manager: string;
|
|
12
|
+
platform: string[];
|
|
13
|
+
category: string | null;
|
|
14
|
+
source_url: string | null;
|
|
15
|
+
binaries: string[];
|
|
16
|
+
usage_examples: UsageExample[];
|
|
17
|
+
similarity: number;
|
|
18
|
+
success_rate: number;
|
|
19
|
+
use_count: number;
|
|
20
|
+
}
|
|
21
|
+
/** Alias used by api-client */
|
|
22
|
+
export type SearchResult = ToolResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentneeds/need",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Discover the right CLI tool for any task using plain English",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": ["cli", "tools", "mcp", "ai", "discovery"],
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=18"
|
|
10
|
+
},
|
|
11
|
+
"files": ["dist", "README.md"],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/tuckerschreiber/need.git",
|
|
15
|
+
"directory": "cli"
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"need": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "NODE_OPTIONS='--max-old-space-size=4096' tsc",
|
|
22
|
+
"dev": "tsc --watch",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
29
|
+
"commander": "^13.0.0",
|
|
30
|
+
"zod": "^3.25 || ^4.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^22.0.0",
|
|
34
|
+
"typescript": "^5.7.0",
|
|
35
|
+
"vitest": "^3.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|