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.
Files changed (2) hide show
  1. package/index.js +299 -0
  2. 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
+ }