integrationsdotsh 0.0.1-beta.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 +38 -0
- package/dist/cli.js +145 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# integrations
|
|
2
|
+
|
|
3
|
+
The CLI for [integrations.sh](https://integrations.sh) — the registry of agent
|
|
4
|
+
integrations. Search the catalog and read everything a domain exposes
|
|
5
|
+
(MCP · REST · GraphQL · CLI) without leaving the terminal.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm i -g integrationsdotsh # command is `integrations`
|
|
11
|
+
# or
|
|
12
|
+
brew install integrations # (planned)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
integrations search slack # search the catalog
|
|
19
|
+
integrations stripe.com # everything a domain exposes
|
|
20
|
+
integrations notion # fuzzy domain match
|
|
21
|
+
|
|
22
|
+
integrations help
|
|
23
|
+
integrations --version
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Point at a different registry host (e.g. a local dev server):
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
INTEGRATIONS_BASE=http://localhost:4321 integrations search slack
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Planned
|
|
33
|
+
|
|
34
|
+
- `integrations auth <domain>` — how to authenticate (credential primitives)
|
|
35
|
+
- `integrations probe <domain>` — live-read a domain's `.well-known/` surface
|
|
36
|
+
(`ai-catalog.json`, `api-catalog`, `ai-plugin.json`, `llms.txt`)
|
|
37
|
+
- `integrations add <domain>` — hand off to Executor
|
|
38
|
+
- `--json` machine output
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
var VERSION = "0.0.1-beta.0";
|
|
5
|
+
var BASE = (process.env.INTEGRATIONS_BASE ?? "https://integrations.sh").replace(/\/$/, "");
|
|
6
|
+
var KIND_ORDER = ["mcp", "openapi", "graphql", "cli"];
|
|
7
|
+
var TAG = { mcp: "mcp", openapi: "rest", graphql: "graphql", cli: "cli" };
|
|
8
|
+
var SECTION = {
|
|
9
|
+
mcp: "MCP SERVERS",
|
|
10
|
+
openapi: "REST · OPENAPI",
|
|
11
|
+
graphql: "GRAPHQL",
|
|
12
|
+
cli: "CLI"
|
|
13
|
+
};
|
|
14
|
+
var useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
15
|
+
var dim = (s) => useColor ? `\x1B[2m${s}\x1B[0m` : s;
|
|
16
|
+
var bold = (s) => useColor ? `\x1B[1m${s}\x1B[0m` : s;
|
|
17
|
+
function fail(msg) {
|
|
18
|
+
process.stderr.write(`integrations: ${msg}
|
|
19
|
+
`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
async function loadRegistry() {
|
|
23
|
+
let res;
|
|
24
|
+
try {
|
|
25
|
+
res = await fetch(`${BASE}/api.json`, { headers: { accept: "application/json" } });
|
|
26
|
+
} catch {
|
|
27
|
+
return fail(`could not reach ${BASE}/api.json`);
|
|
28
|
+
}
|
|
29
|
+
if (!res.ok)
|
|
30
|
+
return fail(`${BASE}/api.json returned ${res.status}`);
|
|
31
|
+
return await res.json();
|
|
32
|
+
}
|
|
33
|
+
function groupByDomain(records) {
|
|
34
|
+
const groups = new Map;
|
|
35
|
+
for (const r of records) {
|
|
36
|
+
const d = r.domain || r.slug;
|
|
37
|
+
if (!d)
|
|
38
|
+
continue;
|
|
39
|
+
let g = groups.get(d);
|
|
40
|
+
if (!g) {
|
|
41
|
+
g = { domain: d, total: 0, kinds: new Set, records: [], pop: 0 };
|
|
42
|
+
groups.set(d, g);
|
|
43
|
+
}
|
|
44
|
+
g.total++;
|
|
45
|
+
g.kinds.add(r.kind);
|
|
46
|
+
g.records.push(r);
|
|
47
|
+
g.pop = Math.max(g.pop, r.popularity ?? 0);
|
|
48
|
+
}
|
|
49
|
+
return groups;
|
|
50
|
+
}
|
|
51
|
+
var formatsOf = (g) => KIND_ORDER.filter((k) => g.kinds.has(k)).map((k) => TAG[k]).join(" · ");
|
|
52
|
+
var byPop = (a, b) => b.pop - a.pop || b.total - a.total || a.domain.localeCompare(b.domain);
|
|
53
|
+
var clip = (s, n) => s.replace(/\s+/g, " ").slice(0, n);
|
|
54
|
+
async function cmdSearch(query) {
|
|
55
|
+
if (!query)
|
|
56
|
+
fail("usage: integrations search <query>");
|
|
57
|
+
const q = query.toLowerCase();
|
|
58
|
+
const records = await loadRegistry();
|
|
59
|
+
const groups = [...groupByDomain(records).values()].filter((g) => g.domain.toLowerCase().includes(q) || g.records.some((r) => r.name.toLowerCase().includes(q) || (r.description ?? "").toLowerCase().includes(q))).sort(byPop);
|
|
60
|
+
if (groups.length === 0) {
|
|
61
|
+
process.stdout.write(dim(`no matches
|
|
62
|
+
`));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const LIMIT = 30;
|
|
66
|
+
const shown = groups.slice(0, LIMIT);
|
|
67
|
+
const pad = Math.min(28, Math.max(...shown.map((g) => g.domain.length)));
|
|
68
|
+
for (const g of shown) {
|
|
69
|
+
const desc = g.records.find((r) => r.description)?.description ?? "";
|
|
70
|
+
process.stdout.write(`${bold(g.domain.padEnd(pad))} ${dim(formatsOf(g).padEnd(22))} ${dim(clip(desc, 64))}
|
|
71
|
+
`);
|
|
72
|
+
}
|
|
73
|
+
if (groups.length > LIMIT)
|
|
74
|
+
process.stdout.write(dim(`
|
|
75
|
+
… ${groups.length - LIMIT} more — narrow your query
|
|
76
|
+
`));
|
|
77
|
+
}
|
|
78
|
+
async function cmdDomain(term) {
|
|
79
|
+
const t = term.toLowerCase();
|
|
80
|
+
const records = await loadRegistry();
|
|
81
|
+
const groups = groupByDomain(records);
|
|
82
|
+
let g = groups.get(term);
|
|
83
|
+
if (!g) {
|
|
84
|
+
const cands = [...groups.values()].filter((x) => x.domain.toLowerCase().includes(t)).sort(byPop);
|
|
85
|
+
if (cands.length === 0)
|
|
86
|
+
fail(`no domain matching "${term}"`);
|
|
87
|
+
if (cands.length > 1 && cands[0].domain.toLowerCase() !== t) {
|
|
88
|
+
process.stdout.write(dim(`did you mean:
|
|
89
|
+
`));
|
|
90
|
+
for (const c of cands.slice(0, 8))
|
|
91
|
+
process.stdout.write(` ${c.domain} ${dim(formatsOf(c))}
|
|
92
|
+
`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
g = cands[0];
|
|
96
|
+
}
|
|
97
|
+
process.stdout.write(`
|
|
98
|
+
${bold(g.domain)} ${dim(`— ${g.total} integration${g.total === 1 ? "" : "s"}`)}
|
|
99
|
+
`);
|
|
100
|
+
for (const kind of KIND_ORDER) {
|
|
101
|
+
const items = g.records.filter((r) => r.kind === kind).sort((a, b) => (b.popularity ?? 0) - (a.popularity ?? 0) || a.name.localeCompare(b.name));
|
|
102
|
+
if (items.length === 0)
|
|
103
|
+
continue;
|
|
104
|
+
process.stdout.write(`
|
|
105
|
+
${dim(SECTION[kind])}
|
|
106
|
+
`);
|
|
107
|
+
for (const r of items) {
|
|
108
|
+
const desc = r.description ? dim(" " + clip(r.description, 60)) : "";
|
|
109
|
+
process.stdout.write(` ${r.name}${desc} ${dim(`${BASE}/${r.kind}/${r.slug}/`)}
|
|
110
|
+
`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
process.stdout.write(`
|
|
114
|
+
`);
|
|
115
|
+
}
|
|
116
|
+
function help() {
|
|
117
|
+
process.stdout.write(`integrations — the registry of agent integrations (integrations.sh)
|
|
118
|
+
|
|
119
|
+
usage:
|
|
120
|
+
integrations search <query> search the catalog
|
|
121
|
+
integrations <domain> show everything a domain exposes
|
|
122
|
+
integrations help show this help
|
|
123
|
+
integrations --version print version
|
|
124
|
+
|
|
125
|
+
examples:
|
|
126
|
+
integrations search slack
|
|
127
|
+
integrations stripe.com
|
|
128
|
+
integrations notion
|
|
129
|
+
|
|
130
|
+
env:
|
|
131
|
+
INTEGRATIONS_BASE registry host (default https://integrations.sh)
|
|
132
|
+
`);
|
|
133
|
+
}
|
|
134
|
+
async function main() {
|
|
135
|
+
const [, , cmd, ...rest] = process.argv;
|
|
136
|
+
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h")
|
|
137
|
+
return help();
|
|
138
|
+
if (cmd === "--version" || cmd === "-v")
|
|
139
|
+
return void process.stdout.write(`${VERSION}
|
|
140
|
+
`);
|
|
141
|
+
if (cmd === "search")
|
|
142
|
+
return cmdSearch(rest.join(" "));
|
|
143
|
+
return cmdDomain(cmd);
|
|
144
|
+
}
|
|
145
|
+
main().catch((e) => fail(e?.message ?? String(e)));
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "integrationsdotsh",
|
|
3
|
+
"version": "0.0.1-beta.0",
|
|
4
|
+
"description": "CLI for integrations.sh — search the registry of agent integrations and read a domain's surface.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"integrations": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "bun build src/cli.ts --target node --outdir dist && chmod +x dist/cli.js",
|
|
17
|
+
"dev": "bun run src/cli.ts",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"prepublishOnly": "bun run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"integrations",
|
|
23
|
+
"mcp",
|
|
24
|
+
"openapi",
|
|
25
|
+
"graphql",
|
|
26
|
+
"agents",
|
|
27
|
+
"registry",
|
|
28
|
+
"cli"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^22.0.0",
|
|
33
|
+
"typescript": "^5.6.0"
|
|
34
|
+
}
|
|
35
|
+
}
|