agenttool 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/index.js +299 -0
- package/package.json +16 -0
package/index.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* agenttool — CLI to search and check agent-readiness grades for software tools.
|
|
5
|
+
* Zero dependencies. Wraps the public agenttool.sh API.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const API_BASE = process.env.AGENTTOOL_API || 'https://agenttool.sh/api'
|
|
9
|
+
|
|
10
|
+
// ─── Helpers ───
|
|
11
|
+
|
|
12
|
+
async function api(path) {
|
|
13
|
+
const url = `${API_BASE}${path}`
|
|
14
|
+
const res = await fetch(url, {
|
|
15
|
+
headers: { 'User-Agent': 'agenttool-cli/0.1.0' },
|
|
16
|
+
signal: AbortSignal.timeout(15000),
|
|
17
|
+
})
|
|
18
|
+
if (!res.ok) {
|
|
19
|
+
const body = await res.text().catch(() => '')
|
|
20
|
+
throw new Error(`API ${res.status}: ${body || res.statusText}`)
|
|
21
|
+
}
|
|
22
|
+
return res.json()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function gradeColor(grade) {
|
|
26
|
+
const colors = { 'A+': '\x1b[32m', 'A': '\x1b[32m', 'B+': '\x1b[33m', 'B': '\x1b[33m', 'C+': '\x1b[33m', 'C': '\x1b[37m', 'D': '\x1b[31m', 'F': '\x1b[31m' }
|
|
27
|
+
return `${colors[grade] || ''}${grade}\x1b[0m`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function pad(str, len) {
|
|
31
|
+
return String(str).padEnd(len)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function truncate(str, max) {
|
|
35
|
+
if (!str) return ''
|
|
36
|
+
return str.length > max ? str.slice(0, max - 1) + '…' : str
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Commands ───
|
|
40
|
+
|
|
41
|
+
async function cmdSearch(query) {
|
|
42
|
+
if (!query) {
|
|
43
|
+
console.error('Usage: agenttool search <query>')
|
|
44
|
+
process.exit(1)
|
|
45
|
+
}
|
|
46
|
+
const { data } = await api(`/search?q=${encodeURIComponent(query)}&limit=20`)
|
|
47
|
+
if (!data?.length) {
|
|
48
|
+
console.log('No tools found.')
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
console.log(`\n ${pad('Tool', 28)} ${pad('Grade', 8)} ${pad('Score', 8)} Category`)
|
|
52
|
+
console.log(` ${'─'.repeat(28)} ${'─'.repeat(7)} ${'─'.repeat(7)} ${'─'.repeat(20)}`)
|
|
53
|
+
for (const t of data) {
|
|
54
|
+
console.log(` ${pad(truncate(t.name, 27), 28)} ${pad(gradeColor(t.agentGrade), 17)} ${pad(t.agentScore?.toFixed(1) || 'N/A', 8)} ${t.category || ''}`)
|
|
55
|
+
}
|
|
56
|
+
console.log(`\n ${data.length} result${data.length === 1 ? '' : 's'}`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function cmdGrade(slug) {
|
|
60
|
+
if (!slug) {
|
|
61
|
+
console.error('Usage: agenttool grade <tool-slug>')
|
|
62
|
+
process.exit(1)
|
|
63
|
+
}
|
|
64
|
+
const { data } = await api(`/tools/${encodeURIComponent(slug)}`)
|
|
65
|
+
if (!data) {
|
|
66
|
+
console.log('Tool not found.')
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
const t = data
|
|
70
|
+
|
|
71
|
+
console.log(`\n ${t.name}`)
|
|
72
|
+
console.log(` ${t.url}`)
|
|
73
|
+
console.log(` Grade: ${gradeColor(t.agentGrade)} Score: ${t.agentScore?.toFixed(1)}/10`)
|
|
74
|
+
if (t.category) console.log(` Category: ${t.category}`)
|
|
75
|
+
if (t.description) console.log(`\n ${truncate(t.description, 100)}`)
|
|
76
|
+
|
|
77
|
+
// Scores breakdown
|
|
78
|
+
const scores = t.scores || {}
|
|
79
|
+
const labels = {
|
|
80
|
+
tokenEfficiency: 'Token Efficiency',
|
|
81
|
+
access: 'Programmatic Access',
|
|
82
|
+
auth: 'Autonomous Auth',
|
|
83
|
+
speed: 'Speed & Throughput',
|
|
84
|
+
discoverability: 'Discoverability',
|
|
85
|
+
reliability: 'Reliability',
|
|
86
|
+
safety: 'Safety',
|
|
87
|
+
reactivity: 'Reactivity',
|
|
88
|
+
}
|
|
89
|
+
const scored = Object.entries(labels).filter(([k]) => scores[k] && !scores[k].na)
|
|
90
|
+
if (scored.length) {
|
|
91
|
+
console.log(`\n ${pad('Criterion', 24)} Score Evidence`)
|
|
92
|
+
console.log(` ${'─'.repeat(24)} ${'─'.repeat(5)} ${'─'.repeat(40)}`)
|
|
93
|
+
for (const [key, label] of scored) {
|
|
94
|
+
const s = scores[key]
|
|
95
|
+
console.log(` ${pad(label, 24)} ${pad(s.score + '/10', 6)} ${truncate(s.evidence, 40)}`)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Access methods
|
|
100
|
+
const am = t.accessMethods || {}
|
|
101
|
+
const methods = []
|
|
102
|
+
if (am.restApi) methods.push('REST API')
|
|
103
|
+
if (am.graphql) methods.push('GraphQL')
|
|
104
|
+
if (am.cli) methods.push('CLI')
|
|
105
|
+
if (am.mcpServer && am.mcpServer !== 'none') methods.push(`MCP (${am.mcpServer})`)
|
|
106
|
+
if (am.sdk?.length) methods.push(`SDKs: ${am.sdk.join(', ')}`)
|
|
107
|
+
if (methods.length) console.log(`\n Access: ${methods.join(' | ')}`)
|
|
108
|
+
|
|
109
|
+
// Biggest friction
|
|
110
|
+
if (t.scannerData?.biggestFriction) {
|
|
111
|
+
console.log(`\n Biggest friction: ${t.scannerData.biggestFriction}`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Links
|
|
115
|
+
const links = t.links || {}
|
|
116
|
+
const linkEntries = Object.entries(links).filter(([, v]) => v)
|
|
117
|
+
if (linkEntries.length) {
|
|
118
|
+
console.log(`\n Links:`)
|
|
119
|
+
for (const [key, url] of linkEntries) {
|
|
120
|
+
console.log(` ${key}: ${url}`)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
console.log()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function cmdCategories() {
|
|
127
|
+
const { data } = await api('/categories')
|
|
128
|
+
if (!data?.length) {
|
|
129
|
+
console.log('No categories found.')
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
console.log(`\n ${pad('Category', 28)} ${pad('Tools', 8)} Avg Score`)
|
|
133
|
+
console.log(` ${'─'.repeat(28)} ${'─'.repeat(7)} ${'─'.repeat(9)}`)
|
|
134
|
+
for (const c of data) {
|
|
135
|
+
console.log(` ${pad(c.name, 28)} ${pad(c.toolCount, 8)} ${c.avgScore?.toFixed(1) || 'N/A'}`)
|
|
136
|
+
}
|
|
137
|
+
console.log(`\n ${data.length} categories`)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function cmdList(opts) {
|
|
141
|
+
const params = new URLSearchParams()
|
|
142
|
+
if (opts.category) params.set('category', opts.category)
|
|
143
|
+
if (opts.grade) params.set('minGrade', opts.grade)
|
|
144
|
+
if (opts.mcp) params.set('hasMcp', 'true')
|
|
145
|
+
if (opts.api) params.set('hasApi', 'true')
|
|
146
|
+
if (opts.cli) params.set('hasCli', 'true')
|
|
147
|
+
if (opts.sort) params.set('sort', opts.sort)
|
|
148
|
+
params.set('limit', String(opts.limit || 20))
|
|
149
|
+
|
|
150
|
+
const qs = params.toString()
|
|
151
|
+
const { data, meta } = await api(`/tools${qs ? '?' + qs : ''}`)
|
|
152
|
+
if (!data?.length) {
|
|
153
|
+
console.log('No tools found.')
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
console.log(`\n ${pad('Tool', 28)} ${pad('Grade', 8)} ${pad('Score', 8)} Category`)
|
|
157
|
+
console.log(` ${'─'.repeat(28)} ${'─'.repeat(7)} ${'─'.repeat(7)} ${'─'.repeat(20)}`)
|
|
158
|
+
for (const t of data) {
|
|
159
|
+
console.log(` ${pad(truncate(t.name, 27), 28)} ${pad(gradeColor(t.agentGrade), 17)} ${pad(t.agentScore?.toFixed(1) || 'N/A', 8)} ${t.category || ''}`)
|
|
160
|
+
}
|
|
161
|
+
console.log(`\n Showing ${data.length} of ${meta?.total || '?'} tools`)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function cmdSubmit(url, name) {
|
|
165
|
+
if (!url) {
|
|
166
|
+
console.error('Usage: agenttool submit <url> [--name <name>]')
|
|
167
|
+
process.exit(1)
|
|
168
|
+
}
|
|
169
|
+
const body = { url }
|
|
170
|
+
if (name) body.name = name
|
|
171
|
+
|
|
172
|
+
const res = await fetch(`${API_BASE}/tools/submit`, {
|
|
173
|
+
method: 'POST',
|
|
174
|
+
headers: { 'Content-Type': 'application/json', 'User-Agent': 'agenttool-cli/0.1.0' },
|
|
175
|
+
body: JSON.stringify(body),
|
|
176
|
+
signal: AbortSignal.timeout(15000),
|
|
177
|
+
})
|
|
178
|
+
const json = await res.json()
|
|
179
|
+
if (!res.ok) {
|
|
180
|
+
console.error(`Error: ${json.error || json.message || res.statusText}`)
|
|
181
|
+
process.exit(1)
|
|
182
|
+
}
|
|
183
|
+
const d = json.data
|
|
184
|
+
if (d.status === 'already_exists') {
|
|
185
|
+
console.log(`\n Already graded: ${d.slug} (${gradeColor(d.agentGrade)})`)
|
|
186
|
+
console.log(` View: https://agenttool.sh/tools/${d.slug}`)
|
|
187
|
+
} else {
|
|
188
|
+
console.log(`\n Submitted: ${d.slug}`)
|
|
189
|
+
console.log(` ${d.message}`)
|
|
190
|
+
}
|
|
191
|
+
console.log()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ─── Arg Parsing ───
|
|
195
|
+
|
|
196
|
+
function parseArgs(argv) {
|
|
197
|
+
const args = argv.slice(2)
|
|
198
|
+
const command = args[0]
|
|
199
|
+
const positional = []
|
|
200
|
+
const flags = {}
|
|
201
|
+
|
|
202
|
+
for (let i = 1; i < args.length; i++) {
|
|
203
|
+
if (args[i].startsWith('--')) {
|
|
204
|
+
const key = args[i].slice(2)
|
|
205
|
+
// Boolean flags
|
|
206
|
+
if (['mcp', 'api', 'cli'].includes(key)) {
|
|
207
|
+
flags[key] = true
|
|
208
|
+
} else {
|
|
209
|
+
flags[key] = args[++i]
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
positional.push(args[i])
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return { command, positional, flags }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function printHelp() {
|
|
220
|
+
console.log(`
|
|
221
|
+
agenttool — Check agent-readiness grades for software tools
|
|
222
|
+
|
|
223
|
+
Usage:
|
|
224
|
+
agenttool search <query> Search tools by name or category
|
|
225
|
+
agenttool grade <slug> Get full grade breakdown for a tool
|
|
226
|
+
agenttool list [options] Browse graded tools
|
|
227
|
+
agenttool categories List all categories
|
|
228
|
+
agenttool submit <url> [--name X] Submit a tool for scanning
|
|
229
|
+
|
|
230
|
+
List options:
|
|
231
|
+
--category <name> Filter by category
|
|
232
|
+
--grade <min> Minimum grade (A+, A, B+, B, C+, C, D, F)
|
|
233
|
+
--sort <field> Sort by: score (default), name, recent
|
|
234
|
+
--limit <n> Max results (default: 20)
|
|
235
|
+
--mcp Only tools with MCP servers
|
|
236
|
+
--api Only tools with APIs
|
|
237
|
+
--cli Only tools with CLIs
|
|
238
|
+
|
|
239
|
+
Environment:
|
|
240
|
+
AGENTTOOL_API API base URL (default: https://agenttool.sh/api)
|
|
241
|
+
|
|
242
|
+
Examples:
|
|
243
|
+
agenttool search stripe
|
|
244
|
+
agenttool grade stripe
|
|
245
|
+
agenttool list --category Payments --grade B
|
|
246
|
+
agenttool list --mcp --sort score
|
|
247
|
+
agenttool submit https://linear.app --name Linear
|
|
248
|
+
`)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ─── Main ───
|
|
252
|
+
|
|
253
|
+
async function main() {
|
|
254
|
+
const { command, positional, flags } = parseArgs(process.argv)
|
|
255
|
+
|
|
256
|
+
if (!command || command === 'help' || flags.help) {
|
|
257
|
+
printHelp()
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
switch (command) {
|
|
263
|
+
case 'search':
|
|
264
|
+
await cmdSearch(positional.join(' '))
|
|
265
|
+
break
|
|
266
|
+
case 'grade':
|
|
267
|
+
await cmdGrade(positional[0])
|
|
268
|
+
break
|
|
269
|
+
case 'list':
|
|
270
|
+
case 'ls':
|
|
271
|
+
await cmdList({
|
|
272
|
+
category: flags.category,
|
|
273
|
+
grade: flags.grade,
|
|
274
|
+
sort: flags.sort,
|
|
275
|
+
limit: flags.limit ? Number(flags.limit) : 20,
|
|
276
|
+
mcp: flags.mcp,
|
|
277
|
+
api: flags.api,
|
|
278
|
+
cli: flags.cli,
|
|
279
|
+
})
|
|
280
|
+
break
|
|
281
|
+
case 'categories':
|
|
282
|
+
case 'cats':
|
|
283
|
+
await cmdCategories()
|
|
284
|
+
break
|
|
285
|
+
case 'submit':
|
|
286
|
+
await cmdSubmit(positional[0], flags.name)
|
|
287
|
+
break
|
|
288
|
+
default:
|
|
289
|
+
console.error(`Unknown command: ${command}`)
|
|
290
|
+
printHelp()
|
|
291
|
+
process.exit(1)
|
|
292
|
+
}
|
|
293
|
+
} catch (err) {
|
|
294
|
+
console.error(`Error: ${err.message}`)
|
|
295
|
+
process.exit(1)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
main()
|
package/package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agenttool",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "CLI to search and check agent-readiness grades for software tools",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agenttool": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": ["ai-agents", "tool-directory", "agent-readiness", "cli"],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/romainsimon/agenttool.sh"
|
|
14
|
+
},
|
|
15
|
+
"files": ["index.js", "lib/"]
|
|
16
|
+
}
|