aicoolies 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 +48 -0
- package/aicoolies.mjs +149 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# aicoolies
|
|
2
|
+
|
|
3
|
+
Official CLI for the [aicoolies](https://aicoolies.com) developer-tools knowledge graph.
|
|
4
|
+
|
|
5
|
+
Every command is an unauthenticated read of the public API. There is no API key, no signup, and no
|
|
6
|
+
configuration — the CLI has zero runtime dependencies and talks to production over `fetch`.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx aicoolies meta
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Commands
|
|
13
|
+
|
|
14
|
+
| Command | What it does |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| `aicoolies meta` | Collection counts and freshness timestamps (`/api/agents/meta`) |
|
|
17
|
+
| `aicoolies context [--include tools,reviews] [--compact] [--category slug]` | Filterable catalog index (`/api/agents/context`) |
|
|
18
|
+
| `aicoolies search <query>` | Search tools by name or slug in the CC-BY dataset |
|
|
19
|
+
| `aicoolies tool <slug>` | One tool record from the CC-BY dataset |
|
|
20
|
+
| `aicoolies resources` | Official developer resource URLs, fetched through the aicoolies MCP server |
|
|
21
|
+
| `aicoolies openapi` | The OpenAPI 3.1 document |
|
|
22
|
+
| `aicoolies --version` | Print the CLI version |
|
|
23
|
+
|
|
24
|
+
Output is JSON on stdout, so it pipes into `jq` cleanly:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx aicoolies context --include tools --compact | jq '.tools | length'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Environment
|
|
31
|
+
|
|
32
|
+
| Variable | Default | Purpose |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `AICOOLIES_URL` | `https://aicoolies.com` | API origin, for pointing the CLI at a local instance |
|
|
35
|
+
|
|
36
|
+
## Errors
|
|
37
|
+
|
|
38
|
+
Failures are [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem documents. The CLI prints the
|
|
39
|
+
machine-readable `code` and the human-readable `detail`, and includes the retry delay when the public
|
|
40
|
+
rate limit is hit.
|
|
41
|
+
|
|
42
|
+
## Related
|
|
43
|
+
|
|
44
|
+
- [Developer portal](https://aicoolies.com/developers) — API docs, versioning, rate limits, error model
|
|
45
|
+
- [OpenAPI](https://aicoolies.com/openapi.json) · [MCP server](https://aicoolies.com/mcp) · [llms.txt](https://aicoolies.com/llms.txt)
|
|
46
|
+
- [Open data](https://aicoolies.com/data) — CC-BY 4.0 tool, comparison, and review datasets
|
|
47
|
+
|
|
48
|
+
MIT licensed. The catalog datasets are CC-BY 4.0, attribution `aicoolies.com`.
|
package/aicoolies.mjs
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
const BASE = process.env.AICOOLIES_URL ?? 'https://aicoolies.com'
|
|
6
|
+
|
|
7
|
+
function packageVersion() {
|
|
8
|
+
try {
|
|
9
|
+
// The CLI ships as its own package: the manifest sits next to this file
|
|
10
|
+
// both in the repo and in the published tarball.
|
|
11
|
+
const manifest = readFileSync(fileURLToPath(new URL('./package.json', import.meta.url)), 'utf8')
|
|
12
|
+
return JSON.parse(manifest).version ?? '0.0.0'
|
|
13
|
+
} catch {
|
|
14
|
+
return '0.0.0'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const HELP = `aicoolies — official CLI for the aicoolies.com catalog
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
aicoolies meta Collection counts and freshness timestamps
|
|
22
|
+
aicoolies context [options] Catalog index (--include tools,reviews --compact --category slug)
|
|
23
|
+
aicoolies search <query> Search tools by name or slug
|
|
24
|
+
aicoolies tool <slug> One tool record from the CC-BY dataset
|
|
25
|
+
aicoolies resources Official developer resource URLs (via the MCP server)
|
|
26
|
+
aicoolies openapi OpenAPI 3.1 document
|
|
27
|
+
aicoolies --version
|
|
28
|
+
aicoolies --help
|
|
29
|
+
|
|
30
|
+
Every command is an unauthenticated read of the public API — no API key, no signup.
|
|
31
|
+
|
|
32
|
+
Environment:
|
|
33
|
+
AICOOLIES_URL API origin (default ${BASE})
|
|
34
|
+
`
|
|
35
|
+
|
|
36
|
+
function flag(args, name) {
|
|
37
|
+
const index = args.indexOf(name)
|
|
38
|
+
if (index === -1) return undefined
|
|
39
|
+
return args[index + 1]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function readJson(path) {
|
|
43
|
+
const response = await fetch(`${BASE}${path}`, {
|
|
44
|
+
headers: { Accept: 'application/json' },
|
|
45
|
+
})
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
// The API answers failures with RFC 9457 problem documents; surface the
|
|
48
|
+
// machine-readable code and the human detail instead of a bare status.
|
|
49
|
+
const problem = await response.json().catch(() => null)
|
|
50
|
+
if (problem && typeof problem === 'object' && problem.code) {
|
|
51
|
+
const retry = problem.retry_after_seconds ? ` (retry in ${problem.retry_after_seconds}s)` : ''
|
|
52
|
+
throw new Error(
|
|
53
|
+
`${path} failed: ${problem.code} — ${problem.detail ?? problem.title}${retry}`,
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`${path} returned ${response.status}`)
|
|
57
|
+
}
|
|
58
|
+
return response.json()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function callMcpTool(name, args = {}) {
|
|
62
|
+
const response = await fetch(`${BASE}/mcp`, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: {
|
|
65
|
+
'Content-Type': 'application/json',
|
|
66
|
+
Accept: 'application/json, text/event-stream',
|
|
67
|
+
},
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
jsonrpc: '2.0',
|
|
70
|
+
id: 1,
|
|
71
|
+
method: 'tools/call',
|
|
72
|
+
params: { name, arguments: args },
|
|
73
|
+
}),
|
|
74
|
+
})
|
|
75
|
+
const payload = await response.json()
|
|
76
|
+
if (payload.error) throw new Error(`${name} failed: ${payload.error.message}`)
|
|
77
|
+
return payload.result?.structuredContent ?? payload.result
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function main(argv) {
|
|
81
|
+
const [command, ...rest] = argv
|
|
82
|
+
if (!command || command === '--help' || command === 'help' || command === '-h') {
|
|
83
|
+
process.stdout.write(HELP)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (command === '--version' || command === '-v' || command === 'version') {
|
|
88
|
+
process.stdout.write(`${packageVersion()}\n`)
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
switch (command) {
|
|
93
|
+
case 'meta': {
|
|
94
|
+
process.stdout.write(`${JSON.stringify(await readJson('/api/agents/meta'), null, 2)}\n`)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
case 'resources': {
|
|
98
|
+
process.stdout.write(
|
|
99
|
+
`${JSON.stringify(await callMcpTool('list_developer_resources'), null, 2)}\n`,
|
|
100
|
+
)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
case 'openapi': {
|
|
104
|
+
process.stdout.write(`${JSON.stringify(await readJson('/openapi.json'), null, 2)}\n`)
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
case 'context': {
|
|
108
|
+
const params = new URLSearchParams()
|
|
109
|
+
const include = flag(rest, '--include')
|
|
110
|
+
const category = flag(rest, '--category')
|
|
111
|
+
if (include) params.set('include', include)
|
|
112
|
+
if (category) params.set('category', category)
|
|
113
|
+
if (rest.includes('--compact')) params.set('compact', 'true')
|
|
114
|
+
const query = params.toString()
|
|
115
|
+
process.stdout.write(
|
|
116
|
+
`${JSON.stringify(await readJson(`/api/agents/context${query ? `?${query}` : ''}`), null, 2)}\n`,
|
|
117
|
+
)
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
case 'search': {
|
|
121
|
+
const q = rest.join(' ').trim().toLowerCase()
|
|
122
|
+
if (!q) throw new Error('search needs a query')
|
|
123
|
+
const payload = await readJson('/data/tools.json')
|
|
124
|
+
const tools = Array.isArray(payload.tools) ? payload.tools : []
|
|
125
|
+
const hits = tools
|
|
126
|
+
.filter((tool) => `${tool.slug} ${tool.name}`.toLowerCase().includes(q))
|
|
127
|
+
.slice(0, 15)
|
|
128
|
+
process.stdout.write(`${JSON.stringify(hits, null, 2)}\n`)
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
case 'tool': {
|
|
132
|
+
const slug = rest[0]
|
|
133
|
+
if (!slug) throw new Error('tool needs a slug')
|
|
134
|
+
const payload = await readJson('/data/tools.json')
|
|
135
|
+
const tools = Array.isArray(payload.tools) ? payload.tools : []
|
|
136
|
+
const tool = tools.find((entry) => entry.slug === slug)
|
|
137
|
+
if (!tool) throw new Error(`unknown tool: ${slug}`)
|
|
138
|
+
process.stdout.write(`${JSON.stringify(tool, null, 2)}\n`)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
default:
|
|
142
|
+
throw new Error(`unknown command: ${command}`)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
main(process.argv.slice(2)).catch((error) => {
|
|
147
|
+
process.stderr.write(`${error instanceof Error ? error.message : error}\n`)
|
|
148
|
+
process.exitCode = 1
|
|
149
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aicoolies",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official CLI for the aicoolies.com developer-tools catalog — unauthenticated reads of the public API, MCP, and CC-BY datasets",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://aicoolies.com/developers#cli",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/rasitakyol/aicoolies.git",
|
|
10
|
+
"directory": "packages/cli"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://aicoolies.com/contact"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"aicoolies",
|
|
17
|
+
"cli",
|
|
18
|
+
"developer-tools",
|
|
19
|
+
"ai-tools",
|
|
20
|
+
"catalog",
|
|
21
|
+
"mcp",
|
|
22
|
+
"openapi",
|
|
23
|
+
"agents"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"bin": {
|
|
27
|
+
"aicoolies": "aicoolies.mjs"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"aicoolies.mjs",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20.9.0"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
}
|
|
39
|
+
}
|