add-mcp 1.7.0 → 1.8.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 +71 -1
- package/dist/index.js +655 -5
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -89,6 +89,10 @@ Besides the implicit add command, `add-mcp` also supports the following commands
|
|
|
89
89
|
| ------------- | ------------------------------------------------------------ |
|
|
90
90
|
| `find` | Search MCP registry servers and install a selected match |
|
|
91
91
|
| `search` | Alias for `find` |
|
|
92
|
+
| `list` | List installed MCP servers across detected agents |
|
|
93
|
+
| `remove` | Remove an MCP server from agent configurations |
|
|
94
|
+
| `sync` | Synchronize server names and installations across agents |
|
|
95
|
+
| `unify` | Alias for `sync` |
|
|
92
96
|
| `list-agents` | List all supported coding agents with scope (project/global) |
|
|
93
97
|
|
|
94
98
|
## Add Command
|
|
@@ -219,7 +223,7 @@ If you run with `-y` before this one-time registry setup is completed, the CLI e
|
|
|
219
223
|
|
|
220
224
|
| Registry | Base URL | Description |
|
|
221
225
|
| ------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
222
|
-
| **add-mcp curated registry** | `https://mcp.agent-tooling.dev/api/v1/servers`
|
|
226
|
+
| **add-mcp curated registry** | `https://mcp.agent-tooling.dev/api/v1/servers` | A curated list of first-party, verified MCP servers from popular developer tools and SaaS services. Designed to surface high-quality, officially maintained servers instead of a long tail of unmaintained or third-party entries. |
|
|
223
227
|
| **Official Anthropic registry** | `https://registry.modelcontextprotocol.io/v0.1/servers` | The community-driven MCP server registry maintained by Anthropic. Contains the broadest catalog of MCP servers. |
|
|
224
228
|
|
|
225
229
|
### Missing A Server in add-mcp Curated Registry?
|
|
@@ -289,6 +293,72 @@ To add your own registry, append an entry to `findRegistries` in `~/.config/add-
|
|
|
289
293
|
}
|
|
290
294
|
```
|
|
291
295
|
|
|
296
|
+
## List Command
|
|
297
|
+
|
|
298
|
+
List installed MCP servers across detected agents:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
# List servers for all detected agents in the project
|
|
302
|
+
npx add-mcp list
|
|
303
|
+
|
|
304
|
+
# List global server configs
|
|
305
|
+
npx add-mcp list -g
|
|
306
|
+
|
|
307
|
+
# List servers for a specific agent (shown even if not detected)
|
|
308
|
+
npx add-mcp list -a cursor
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
| Option | Description |
|
|
312
|
+
| --------------------- | -------------------------------------- |
|
|
313
|
+
| `-g, --global` | List global configs instead of project |
|
|
314
|
+
| `-a, --agent <agent>` | Filter to specific agent(s) |
|
|
315
|
+
|
|
316
|
+
## Remove Command
|
|
317
|
+
|
|
318
|
+
Remove an MCP server from agent configurations by server name, URL, or package name:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
# Remove by server name (interactive selection by default)
|
|
322
|
+
npx add-mcp remove neon
|
|
323
|
+
|
|
324
|
+
# Remove all matches without prompting
|
|
325
|
+
npx add-mcp remove neon -y
|
|
326
|
+
|
|
327
|
+
# Remove by URL
|
|
328
|
+
npx add-mcp remove https://mcp.neon.tech/mcp -y
|
|
329
|
+
|
|
330
|
+
# Remove from global configs for a specific agent
|
|
331
|
+
npx add-mcp remove neon -g -a cursor -y
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
| Option | Description |
|
|
335
|
+
| --------------------- | ------------------------------------ |
|
|
336
|
+
| `-g, --global` | Remove from global configs |
|
|
337
|
+
| `-a, --agent <agent>` | Filter to specific agent(s) |
|
|
338
|
+
| `-y, --yes` | Remove all matches without prompting |
|
|
339
|
+
|
|
340
|
+
## Sync Command
|
|
341
|
+
|
|
342
|
+
Synchronize server names and installations across all detected agents. Servers are grouped by URL or package name, and each group is unified to the shortest server name. Servers with conflicting headers, env, or args across agents are skipped with a warning.
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
# Sync project-level configs (interactive confirmation)
|
|
346
|
+
npx add-mcp sync
|
|
347
|
+
|
|
348
|
+
# Sync without prompting
|
|
349
|
+
npx add-mcp sync -y
|
|
350
|
+
|
|
351
|
+
# Sync global configs
|
|
352
|
+
npx add-mcp sync -g -y
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
| Option | Description |
|
|
356
|
+
| -------------- | -------------------------------------- |
|
|
357
|
+
| `-g, --global` | Sync global configs instead of project |
|
|
358
|
+
| `-y, --yes` | Skip confirmation prompts |
|
|
359
|
+
|
|
360
|
+
`unify` is an alias for `sync`.
|
|
361
|
+
|
|
292
362
|
## Troubleshooting
|
|
293
363
|
|
|
294
364
|
### Server not loading
|
package/dist/index.js
CHANGED
|
@@ -1322,6 +1322,14 @@ function detectIndent(text2) {
|
|
|
1322
1322
|
});
|
|
1323
1323
|
return result || { tabSize: 2, insertSpaces: true };
|
|
1324
1324
|
}
|
|
1325
|
+
function readJsonConfig(filePath) {
|
|
1326
|
+
if (!existsSync2(filePath)) {
|
|
1327
|
+
return {};
|
|
1328
|
+
}
|
|
1329
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1330
|
+
const parsed = jsonc.parse(content);
|
|
1331
|
+
return parsed;
|
|
1332
|
+
}
|
|
1325
1333
|
function writeJsonConfig(filePath, config, configKey) {
|
|
1326
1334
|
const dir = dirname3(filePath);
|
|
1327
1335
|
if (!existsSync2(dir)) {
|
|
@@ -1349,6 +1357,31 @@ function writeJsonConfig(filePath, config, configKey) {
|
|
|
1349
1357
|
}
|
|
1350
1358
|
writeFileSync(filePath, JSON.stringify(mergedConfig, null, 2));
|
|
1351
1359
|
}
|
|
1360
|
+
function removeJsonConfigKey(filePath, configKey, serverName) {
|
|
1361
|
+
if (!existsSync2(filePath)) {
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
const originalContent = readFileSync(filePath, "utf-8");
|
|
1365
|
+
const configKeyPath = configKey.split(".");
|
|
1366
|
+
try {
|
|
1367
|
+
const edits = jsonc.modify(
|
|
1368
|
+
originalContent,
|
|
1369
|
+
[...configKeyPath, serverName],
|
|
1370
|
+
void 0,
|
|
1371
|
+
{ formattingOptions: detectIndent(originalContent) }
|
|
1372
|
+
);
|
|
1373
|
+
const updatedContent = jsonc.applyEdits(originalContent, edits);
|
|
1374
|
+
writeFileSync(filePath, updatedContent);
|
|
1375
|
+
return;
|
|
1376
|
+
} catch {
|
|
1377
|
+
}
|
|
1378
|
+
const parsed = jsonc.parse(originalContent);
|
|
1379
|
+
const servers = getNestedValue(parsed, configKey);
|
|
1380
|
+
if (servers && typeof servers === "object" && serverName in servers) {
|
|
1381
|
+
delete servers[serverName];
|
|
1382
|
+
}
|
|
1383
|
+
writeFileSync(filePath, JSON.stringify(parsed, null, 2));
|
|
1384
|
+
}
|
|
1352
1385
|
function setNestedValue(obj, path, value) {
|
|
1353
1386
|
const keys = path.split(".");
|
|
1354
1387
|
const lastKey = keys.pop();
|
|
@@ -1375,6 +1408,30 @@ function readYamlConfig(filePath) {
|
|
|
1375
1408
|
const parsed = yaml.load(content);
|
|
1376
1409
|
return parsed || {};
|
|
1377
1410
|
}
|
|
1411
|
+
function removeYamlConfigKey(filePath, configKey, serverName) {
|
|
1412
|
+
if (!existsSync3(filePath)) {
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
const existing = readYamlConfig(filePath);
|
|
1416
|
+
const keys = configKey.split(".");
|
|
1417
|
+
let current = existing;
|
|
1418
|
+
for (const key of keys) {
|
|
1419
|
+
if (current && typeof current === "object" && key in current) {
|
|
1420
|
+
current = current[key];
|
|
1421
|
+
} else {
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
if (current && typeof current === "object" && serverName in current) {
|
|
1426
|
+
delete current[serverName];
|
|
1427
|
+
}
|
|
1428
|
+
const content = yaml.dump(existing, {
|
|
1429
|
+
indent: 2,
|
|
1430
|
+
lineWidth: -1,
|
|
1431
|
+
noRefs: true
|
|
1432
|
+
});
|
|
1433
|
+
writeFileSync2(filePath, content);
|
|
1434
|
+
}
|
|
1378
1435
|
function writeYamlConfig(filePath, config) {
|
|
1379
1436
|
const dir = dirname4(filePath);
|
|
1380
1437
|
if (!existsSync3(dir)) {
|
|
@@ -1405,6 +1462,26 @@ function readTomlConfig(filePath) {
|
|
|
1405
1462
|
const parsed = TOML.parse(content);
|
|
1406
1463
|
return parsed;
|
|
1407
1464
|
}
|
|
1465
|
+
function removeTomlConfigKey(filePath, configKey, serverName) {
|
|
1466
|
+
if (!existsSync4(filePath)) {
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
const existing = readTomlConfig(filePath);
|
|
1470
|
+
const keys = configKey.split(".");
|
|
1471
|
+
let current = existing;
|
|
1472
|
+
for (const key of keys) {
|
|
1473
|
+
if (current && typeof current === "object" && key in current) {
|
|
1474
|
+
current = current[key];
|
|
1475
|
+
} else {
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
if (current && typeof current === "object" && serverName in current) {
|
|
1480
|
+
delete current[serverName];
|
|
1481
|
+
}
|
|
1482
|
+
const content = TOML.stringify(existing);
|
|
1483
|
+
writeFileSync3(filePath, content);
|
|
1484
|
+
}
|
|
1408
1485
|
function writeTomlConfig(filePath, config) {
|
|
1409
1486
|
const dir = dirname5(filePath);
|
|
1410
1487
|
if (!existsSync4(dir)) {
|
|
@@ -1420,6 +1497,33 @@ function writeTomlConfig(filePath, config) {
|
|
|
1420
1497
|
}
|
|
1421
1498
|
|
|
1422
1499
|
// src/formats/index.ts
|
|
1500
|
+
function readConfig2(filePath, format) {
|
|
1501
|
+
switch (format) {
|
|
1502
|
+
case "json":
|
|
1503
|
+
return readJsonConfig(filePath);
|
|
1504
|
+
case "yaml":
|
|
1505
|
+
return readYamlConfig(filePath);
|
|
1506
|
+
case "toml":
|
|
1507
|
+
return readTomlConfig(filePath);
|
|
1508
|
+
default:
|
|
1509
|
+
throw new Error(`Unsupported config format: ${format}`);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
function removeServerFromConfig(filePath, format, configKey, serverName) {
|
|
1513
|
+
switch (format) {
|
|
1514
|
+
case "json":
|
|
1515
|
+
removeJsonConfigKey(filePath, configKey, serverName);
|
|
1516
|
+
break;
|
|
1517
|
+
case "yaml":
|
|
1518
|
+
removeYamlConfigKey(filePath, configKey, serverName);
|
|
1519
|
+
break;
|
|
1520
|
+
case "toml":
|
|
1521
|
+
removeTomlConfigKey(filePath, configKey, serverName);
|
|
1522
|
+
break;
|
|
1523
|
+
default:
|
|
1524
|
+
throw new Error(`Unsupported config format: ${format}`);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1423
1527
|
function writeConfig2(filePath, config, format, configKey) {
|
|
1424
1528
|
switch (format) {
|
|
1425
1529
|
case "json":
|
|
@@ -1574,10 +1678,126 @@ function installServer(serverName, serverConfig, agentTypes, options = {}) {
|
|
|
1574
1678
|
return results;
|
|
1575
1679
|
}
|
|
1576
1680
|
|
|
1681
|
+
// src/reader.ts
|
|
1682
|
+
function extractServerIdentity(serverConfig) {
|
|
1683
|
+
for (const key of ["url", "uri", "serverUrl"]) {
|
|
1684
|
+
const value = serverConfig[key];
|
|
1685
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1686
|
+
return value;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
const command = typeof serverConfig.command === "string" ? serverConfig.command : typeof serverConfig.cmd === "string" ? serverConfig.cmd : void 0;
|
|
1690
|
+
if (!command) {
|
|
1691
|
+
return "";
|
|
1692
|
+
}
|
|
1693
|
+
const rawArgs = Array.isArray(serverConfig.args) ? serverConfig.args.filter((a) => typeof a === "string") : Array.isArray(serverConfig.command) ? serverConfig.command.slice(1).filter((a) => typeof a === "string") : [];
|
|
1694
|
+
if (command === "npx" || command === "bunx") {
|
|
1695
|
+
const yIndex = rawArgs.indexOf("-y");
|
|
1696
|
+
const pkgIndex = yIndex >= 0 ? yIndex + 1 : 0;
|
|
1697
|
+
const pkg = rawArgs[pkgIndex];
|
|
1698
|
+
if (pkg && !pkg.startsWith("-")) {
|
|
1699
|
+
return pkg;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
if (Array.isArray(serverConfig.command)) {
|
|
1703
|
+
return serverConfig.command.join(" ");
|
|
1704
|
+
}
|
|
1705
|
+
if (rawArgs.length > 0) {
|
|
1706
|
+
return `${command} ${rawArgs.join(" ")}`;
|
|
1707
|
+
}
|
|
1708
|
+
return command;
|
|
1709
|
+
}
|
|
1710
|
+
function readServersForAgent(agentType, options) {
|
|
1711
|
+
const agent = agents[agentType];
|
|
1712
|
+
const installOptions = {
|
|
1713
|
+
local: options.scope === "local",
|
|
1714
|
+
cwd: options.cwd
|
|
1715
|
+
};
|
|
1716
|
+
const configPath = getConfigPath2(agent, installOptions);
|
|
1717
|
+
const configKey = getConfigKey(agent, installOptions);
|
|
1718
|
+
const fullConfig = readConfig2(configPath, agent.format);
|
|
1719
|
+
const serversObj = getNestedValue(fullConfig, configKey);
|
|
1720
|
+
const servers = [];
|
|
1721
|
+
if (serversObj && typeof serversObj === "object" && !Array.isArray(serversObj)) {
|
|
1722
|
+
for (const [serverName, serverConfig] of Object.entries(serversObj)) {
|
|
1723
|
+
if (serverConfig && typeof serverConfig === "object") {
|
|
1724
|
+
const config = serverConfig;
|
|
1725
|
+
servers.push({
|
|
1726
|
+
serverName,
|
|
1727
|
+
config,
|
|
1728
|
+
identity: extractServerIdentity(config),
|
|
1729
|
+
agentType,
|
|
1730
|
+
scope: options.scope,
|
|
1731
|
+
configPath
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
return {
|
|
1737
|
+
agentType,
|
|
1738
|
+
displayName: agent.displayName,
|
|
1739
|
+
detected: true,
|
|
1740
|
+
scope: options.scope,
|
|
1741
|
+
configPath,
|
|
1742
|
+
servers
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
async function gatherInstalledServers(options) {
|
|
1746
|
+
const scope = options.global ? "global" : "local";
|
|
1747
|
+
const results = [];
|
|
1748
|
+
if (options.agents && options.agents.length > 0) {
|
|
1749
|
+
const detectedSet = new Set(
|
|
1750
|
+
options.global ? await detectAllGlobalAgents() : detectProjectAgents(options.cwd)
|
|
1751
|
+
);
|
|
1752
|
+
for (const agentType of options.agents) {
|
|
1753
|
+
const detected = detectedSet.has(agentType);
|
|
1754
|
+
if (detected) {
|
|
1755
|
+
results.push(
|
|
1756
|
+
readServersForAgent(agentType, { scope, cwd: options.cwd })
|
|
1757
|
+
);
|
|
1758
|
+
} else {
|
|
1759
|
+
const agent = agents[agentType];
|
|
1760
|
+
const installOptions = {
|
|
1761
|
+
local: scope === "local",
|
|
1762
|
+
cwd: options.cwd
|
|
1763
|
+
};
|
|
1764
|
+
results.push({
|
|
1765
|
+
agentType,
|
|
1766
|
+
displayName: agent.displayName,
|
|
1767
|
+
detected: false,
|
|
1768
|
+
scope,
|
|
1769
|
+
configPath: getConfigPath2(agent, installOptions),
|
|
1770
|
+
servers: []
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
} else {
|
|
1775
|
+
const detected = options.global ? await detectAllGlobalAgents() : detectProjectAgents(options.cwd);
|
|
1776
|
+
for (const agentType of detected) {
|
|
1777
|
+
results.push(readServersForAgent(agentType, { scope, cwd: options.cwd }));
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
return results;
|
|
1781
|
+
}
|
|
1782
|
+
function findMatchingServers(agentServersList, query) {
|
|
1783
|
+
const lowerQuery = query.toLowerCase();
|
|
1784
|
+
const matches = [];
|
|
1785
|
+
for (const agentServers of agentServersList) {
|
|
1786
|
+
for (const server of agentServers.servers) {
|
|
1787
|
+
const nameMatch = server.serverName.toLowerCase().includes(lowerQuery);
|
|
1788
|
+
const identityMatch = server.identity === query;
|
|
1789
|
+
if (nameMatch || identityMatch) {
|
|
1790
|
+
matches.push(server);
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
return matches;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1577
1797
|
// package.json
|
|
1578
1798
|
var package_default = {
|
|
1579
1799
|
name: "add-mcp",
|
|
1580
|
-
version: "1.
|
|
1800
|
+
version: "1.8.0",
|
|
1581
1801
|
description: "Add MCP servers to your favorite coding agents with a single command.",
|
|
1582
1802
|
author: "Andre Landgraf <andre@neon.tech>",
|
|
1583
1803
|
license: "Apache-2.0",
|
|
@@ -1593,8 +1813,8 @@ var package_default = {
|
|
|
1593
1813
|
fmt: "prettier --write .",
|
|
1594
1814
|
build: "tsup src/index.ts --format esm --dts --clean",
|
|
1595
1815
|
dev: "tsx src/index.ts",
|
|
1596
|
-
test: "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
|
|
1597
|
-
"test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts",
|
|
1816
|
+
test: "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/reader.test.ts && tsx tests/formats-remove.test.ts && tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
|
|
1817
|
+
"test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/reader.test.ts && tsx tests/formats-remove.test.ts",
|
|
1598
1818
|
"registry:sort": "tsx scripts/sort-registry.ts",
|
|
1599
1819
|
"registry:verify": "tsx scripts/verify-registry.ts",
|
|
1600
1820
|
"test:e2e": "tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
|
|
@@ -1734,7 +1954,7 @@ function extractOptions(raw) {
|
|
|
1734
1954
|
}
|
|
1735
1955
|
return raw;
|
|
1736
1956
|
}
|
|
1737
|
-
function
|
|
1957
|
+
function extractSubcommandOptionsFromArgv() {
|
|
1738
1958
|
const argv = process.argv.slice(2);
|
|
1739
1959
|
const result = {};
|
|
1740
1960
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -1868,7 +2088,7 @@ program.command("list-agents").description("List all supported coding agents").a
|
|
|
1868
2088
|
async function runFindCommand(keyword, rawOptions) {
|
|
1869
2089
|
const options = {
|
|
1870
2090
|
...extractOptions(rawOptions),
|
|
1871
|
-
...
|
|
2091
|
+
...extractSubcommandOptionsFromArgv()
|
|
1872
2092
|
};
|
|
1873
2093
|
const query = (keyword ?? "").trim();
|
|
1874
2094
|
const registries = await ensureFindRegistriesConfigured(options.yes);
|
|
@@ -1919,7 +2139,437 @@ program.command("search [keyword]").description("Alias for find").option(
|
|
|
1919
2139
|
await runFindCommand(keyword, options);
|
|
1920
2140
|
}
|
|
1921
2141
|
);
|
|
2142
|
+
program.command("list").description("List installed MCP servers across detected agents").option("-g, --global", "List global configs instead of project-level").option("-a, --agent <agent>", "Filter to specific agent(s)", collect, []).action(async (rawOptions) => {
|
|
2143
|
+
const options = {
|
|
2144
|
+
...extractOptions(rawOptions),
|
|
2145
|
+
...extractSubcommandOptionsFromArgv()
|
|
2146
|
+
};
|
|
2147
|
+
await runListCommand(options);
|
|
2148
|
+
});
|
|
2149
|
+
program.command("remove <query>").description("Remove an MCP server from agent configurations").option("-g, --global", "Remove from global configs instead of project-level").option("-a, --agent <agent>", "Filter to specific agent(s)", collect, []).option("-y, --yes", "Remove all matches without prompting").action(
|
|
2150
|
+
async (query, rawOptions) => {
|
|
2151
|
+
const options = {
|
|
2152
|
+
...extractOptions(rawOptions),
|
|
2153
|
+
...extractSubcommandOptionsFromArgv()
|
|
2154
|
+
};
|
|
2155
|
+
await runRemoveCommand(query, options);
|
|
2156
|
+
}
|
|
2157
|
+
);
|
|
2158
|
+
program.command("sync").description(
|
|
2159
|
+
"Synchronize server names and installations across all detected agents"
|
|
2160
|
+
).option("-g, --global", "Sync global configs instead of project-level").option("-y, --yes", "Skip confirmation prompts").action(async (rawOptions) => {
|
|
2161
|
+
const options = {
|
|
2162
|
+
...extractOptions(rawOptions),
|
|
2163
|
+
...extractSubcommandOptionsFromArgv()
|
|
2164
|
+
};
|
|
2165
|
+
await runSyncCommand(options);
|
|
2166
|
+
});
|
|
2167
|
+
program.command("unify").description("Alias for sync").option("-g, --global", "Sync global configs instead of project-level").option("-y, --yes", "Skip confirmation prompts").action(async (rawOptions) => {
|
|
2168
|
+
const options = {
|
|
2169
|
+
...extractOptions(rawOptions),
|
|
2170
|
+
...extractSubcommandOptionsFromArgv()
|
|
2171
|
+
};
|
|
2172
|
+
await runSyncCommand(options);
|
|
2173
|
+
});
|
|
1922
2174
|
program.parse();
|
|
2175
|
+
async function runListCommand(options) {
|
|
2176
|
+
showLogo();
|
|
2177
|
+
console.log();
|
|
2178
|
+
const explicitAgents = resolveAgentFlags(options.agent);
|
|
2179
|
+
const agentServersList = await gatherInstalledServers({
|
|
2180
|
+
global: options.global,
|
|
2181
|
+
agents: explicitAgents.length > 0 ? explicitAgents : void 0
|
|
2182
|
+
});
|
|
2183
|
+
if (agentServersList.length === 0) {
|
|
2184
|
+
const hint = options.global ? "No agents detected globally. Use -a to target a specific agent." : "No agents detected in this project. Use -g for global or -a to target a specific agent.";
|
|
2185
|
+
p3.log.info(hint);
|
|
2186
|
+
console.log();
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
for (const agentServers of agentServersList) {
|
|
2190
|
+
if (!agentServers.detected) {
|
|
2191
|
+
console.log(
|
|
2192
|
+
`${TEXT}${agentServers.displayName}:${RESET} ${DIM}not detected${RESET}`
|
|
2193
|
+
);
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
if (agentServers.servers.length === 0) {
|
|
2197
|
+
console.log(
|
|
2198
|
+
`${TEXT}${agentServers.displayName}:${RESET} ${DIM}no servers configured${RESET}`
|
|
2199
|
+
);
|
|
2200
|
+
continue;
|
|
2201
|
+
}
|
|
2202
|
+
console.log(`${TEXT}${agentServers.displayName}:${RESET}`);
|
|
2203
|
+
for (const server of agentServers.servers) {
|
|
2204
|
+
const identityHint = server.identity ? ` ${DIM}(${server.identity})${RESET}` : "";
|
|
2205
|
+
console.log(
|
|
2206
|
+
` ${DIM}-${RESET} ${TEXT}${server.serverName}${RESET}${identityHint}`
|
|
2207
|
+
);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
console.log();
|
|
2211
|
+
}
|
|
2212
|
+
async function runRemoveCommand(query, options) {
|
|
2213
|
+
showLogo();
|
|
2214
|
+
console.log();
|
|
2215
|
+
const explicitAgents = resolveAgentFlags(options.agent);
|
|
2216
|
+
const agentServersList = await gatherInstalledServers({
|
|
2217
|
+
global: options.global,
|
|
2218
|
+
agents: explicitAgents.length > 0 ? explicitAgents : void 0
|
|
2219
|
+
});
|
|
2220
|
+
const matches = findMatchingServers(agentServersList, query);
|
|
2221
|
+
if (matches.length === 0) {
|
|
2222
|
+
p3.log.info(`No matching servers found for '${query}'`);
|
|
2223
|
+
console.log();
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
2226
|
+
const matchOptions = matches.map((m, i) => ({
|
|
2227
|
+
value: i,
|
|
2228
|
+
label: `${m.serverName} (${agents[m.agentType].displayName})`,
|
|
2229
|
+
hint: m.identity || m.configPath
|
|
2230
|
+
}));
|
|
2231
|
+
let selectedIndices;
|
|
2232
|
+
if (options.yes) {
|
|
2233
|
+
selectedIndices = matches.map((_, i) => i);
|
|
2234
|
+
p3.log.info(
|
|
2235
|
+
`Removing ${matches.length} server${matches.length !== 1 ? "s" : ""} matching '${query}'`
|
|
2236
|
+
);
|
|
2237
|
+
} else {
|
|
2238
|
+
const selected = await p3.multiselect({
|
|
2239
|
+
message: `Select servers to remove (${matches.length} match${matches.length !== 1 ? "es" : ""} found)`,
|
|
2240
|
+
options: matchOptions,
|
|
2241
|
+
required: false,
|
|
2242
|
+
initialValues: matches.map((_, i) => i)
|
|
2243
|
+
});
|
|
2244
|
+
if (p3.isCancel(selected)) {
|
|
2245
|
+
p3.log.info("No changes made");
|
|
2246
|
+
console.log();
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2249
|
+
selectedIndices = selected;
|
|
2250
|
+
if (selectedIndices.length === 0) {
|
|
2251
|
+
p3.log.info("No changes made");
|
|
2252
|
+
console.log();
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
let removedCount = 0;
|
|
2257
|
+
const affectedAgents = /* @__PURE__ */ new Set();
|
|
2258
|
+
for (const idx of selectedIndices) {
|
|
2259
|
+
const server = matches[idx];
|
|
2260
|
+
const agent = agents[server.agentType];
|
|
2261
|
+
try {
|
|
2262
|
+
removeServerFromConfig(
|
|
2263
|
+
server.configPath,
|
|
2264
|
+
agent.format,
|
|
2265
|
+
getConfigKeyForServer(server),
|
|
2266
|
+
server.serverName
|
|
2267
|
+
);
|
|
2268
|
+
removedCount++;
|
|
2269
|
+
affectedAgents.add(agent.displayName);
|
|
2270
|
+
} catch (error) {
|
|
2271
|
+
p3.log.error(
|
|
2272
|
+
`Failed to remove ${server.serverName} from ${agent.displayName}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
if (removedCount > 0) {
|
|
2277
|
+
p3.log.success(
|
|
2278
|
+
`Removed ${removedCount} server${removedCount !== 1 ? "s" : ""} from ${affectedAgents.size} agent${affectedAgents.size !== 1 ? "s" : ""}`
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
console.log();
|
|
2282
|
+
}
|
|
2283
|
+
function getConfigKeyForServer(server) {
|
|
2284
|
+
const agent = agents[server.agentType];
|
|
2285
|
+
if (server.scope === "local" && agent.localConfigKey) {
|
|
2286
|
+
return agent.localConfigKey;
|
|
2287
|
+
}
|
|
2288
|
+
return agent.configKey;
|
|
2289
|
+
}
|
|
2290
|
+
function deepEqual(a, b) {
|
|
2291
|
+
if (a === b) return true;
|
|
2292
|
+
if (a === null || b === null) return false;
|
|
2293
|
+
if (a === void 0 || b === void 0) return a === b;
|
|
2294
|
+
if (typeof a !== typeof b) return false;
|
|
2295
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
2296
|
+
if (a.length !== b.length) return false;
|
|
2297
|
+
return a.every((val, i) => deepEqual(val, b[i]));
|
|
2298
|
+
}
|
|
2299
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
2300
|
+
const aObj = a;
|
|
2301
|
+
const bObj = b;
|
|
2302
|
+
const aKeys = Object.keys(aObj).sort();
|
|
2303
|
+
const bKeys = Object.keys(bObj).sort();
|
|
2304
|
+
if (!deepEqual(aKeys, bKeys)) return false;
|
|
2305
|
+
return aKeys.every((key) => deepEqual(aObj[key], bObj[key]));
|
|
2306
|
+
}
|
|
2307
|
+
return false;
|
|
2308
|
+
}
|
|
2309
|
+
function pickCanonicalName(entries) {
|
|
2310
|
+
const nameFreq = /* @__PURE__ */ new Map();
|
|
2311
|
+
for (const entry of entries) {
|
|
2312
|
+
nameFreq.set(entry.serverName, (nameFreq.get(entry.serverName) ?? 0) + 1);
|
|
2313
|
+
}
|
|
2314
|
+
const names = [...nameFreq.entries()];
|
|
2315
|
+
names.sort(([nameA, freqA], [nameB, freqB]) => {
|
|
2316
|
+
if (nameA.length !== nameB.length) return nameA.length - nameB.length;
|
|
2317
|
+
if (freqA !== freqB) return freqB - freqA;
|
|
2318
|
+
return nameA.localeCompare(nameB);
|
|
2319
|
+
});
|
|
2320
|
+
return names[0][0];
|
|
2321
|
+
}
|
|
2322
|
+
function extractConflictFields(config) {
|
|
2323
|
+
return {
|
|
2324
|
+
headers: config.headers ?? config.http_headers ?? null,
|
|
2325
|
+
env: config.env ?? config.envs ?? config.environment ?? null,
|
|
2326
|
+
args: config.args ?? null
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
function buildSyncGroups(agentServersList) {
|
|
2330
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
2331
|
+
for (const agentServers of agentServersList) {
|
|
2332
|
+
for (const server of agentServers.servers) {
|
|
2333
|
+
if (!server.identity) continue;
|
|
2334
|
+
const existing = byIdentity.get(server.identity) ?? [];
|
|
2335
|
+
existing.push(server);
|
|
2336
|
+
byIdentity.set(server.identity, existing);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
const groups = [];
|
|
2340
|
+
for (const [identity, entries] of byIdentity) {
|
|
2341
|
+
const fieldSets = entries.map((e) => extractConflictFields(e.config));
|
|
2342
|
+
const reference = fieldSets[0];
|
|
2343
|
+
let hasConflict = false;
|
|
2344
|
+
let conflictReason;
|
|
2345
|
+
for (let i = 1; i < fieldSets.length; i++) {
|
|
2346
|
+
const other = fieldSets[i];
|
|
2347
|
+
if (!deepEqual(reference.headers, other.headers)) {
|
|
2348
|
+
hasConflict = true;
|
|
2349
|
+
conflictReason = `headers differ between ${agents[entries[0].agentType].displayName} and ${agents[entries[i].agentType].displayName}`;
|
|
2350
|
+
break;
|
|
2351
|
+
}
|
|
2352
|
+
if (!deepEqual(reference.env, other.env)) {
|
|
2353
|
+
hasConflict = true;
|
|
2354
|
+
conflictReason = `env differs between ${agents[entries[0].agentType].displayName} and ${agents[entries[i].agentType].displayName}`;
|
|
2355
|
+
break;
|
|
2356
|
+
}
|
|
2357
|
+
if (!deepEqual(reference.args, other.args)) {
|
|
2358
|
+
hasConflict = true;
|
|
2359
|
+
conflictReason = `args differ between ${agents[entries[0].agentType].displayName} and ${agents[entries[i].agentType].displayName}`;
|
|
2360
|
+
break;
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
groups.push({
|
|
2364
|
+
identity,
|
|
2365
|
+
entries,
|
|
2366
|
+
canonicalName: pickCanonicalName(entries),
|
|
2367
|
+
canonicalConfig: entries[0].config,
|
|
2368
|
+
hasConflict,
|
|
2369
|
+
conflictReason
|
|
2370
|
+
});
|
|
2371
|
+
}
|
|
2372
|
+
return groups;
|
|
2373
|
+
}
|
|
2374
|
+
async function runSyncCommand(options) {
|
|
2375
|
+
showLogo();
|
|
2376
|
+
console.log();
|
|
2377
|
+
const agentServersList = await gatherInstalledServers({
|
|
2378
|
+
global: options.global
|
|
2379
|
+
});
|
|
2380
|
+
const agentsWithServers = agentServersList.filter(
|
|
2381
|
+
(a) => a.servers.length > 0
|
|
2382
|
+
);
|
|
2383
|
+
if (agentServersList.length < 2) {
|
|
2384
|
+
p3.log.info("Need at least 2 detected agents to sync");
|
|
2385
|
+
console.log();
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
const groups = buildSyncGroups(agentServersList);
|
|
2389
|
+
const detectedAgentTypes = new Set(agentServersList.map((a) => a.agentType));
|
|
2390
|
+
const renames = [];
|
|
2391
|
+
const additions = [];
|
|
2392
|
+
const skipped = [];
|
|
2393
|
+
for (const group of groups) {
|
|
2394
|
+
if (group.hasConflict) {
|
|
2395
|
+
skipped.push(group);
|
|
2396
|
+
continue;
|
|
2397
|
+
}
|
|
2398
|
+
const presentAgents = new Set(group.entries.map((e) => e.agentType));
|
|
2399
|
+
for (const entry of group.entries) {
|
|
2400
|
+
if (entry.serverName !== group.canonicalName) {
|
|
2401
|
+
renames.push({
|
|
2402
|
+
group,
|
|
2403
|
+
agentType: entry.agentType,
|
|
2404
|
+
oldName: entry.serverName
|
|
2405
|
+
});
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
for (const agentType of detectedAgentTypes) {
|
|
2409
|
+
if (!presentAgents.has(agentType)) {
|
|
2410
|
+
additions.push({ group, agentType });
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
if (renames.length === 0 && additions.length === 0 && skipped.length === 0) {
|
|
2415
|
+
p3.log.info("All servers are already in sync");
|
|
2416
|
+
console.log();
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
const planLines = [];
|
|
2420
|
+
if (renames.length > 0) {
|
|
2421
|
+
planLines.push(chalk.cyan("Renames:"));
|
|
2422
|
+
for (const r of renames) {
|
|
2423
|
+
planLines.push(
|
|
2424
|
+
` ${agents[r.agentType].displayName}: ${r.oldName} \u2192 ${r.group.canonicalName}`
|
|
2425
|
+
);
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
if (additions.length > 0) {
|
|
2429
|
+
planLines.push(chalk.cyan("Additions:"));
|
|
2430
|
+
for (const a of additions) {
|
|
2431
|
+
planLines.push(
|
|
2432
|
+
` ${agents[a.agentType].displayName}: + ${a.group.canonicalName} (${a.group.identity})`
|
|
2433
|
+
);
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
if (skipped.length > 0) {
|
|
2437
|
+
planLines.push(chalk.yellow("Skipped (conflicts):"));
|
|
2438
|
+
for (const s of skipped) {
|
|
2439
|
+
planLines.push(` ${s.identity}: ${s.conflictReason}`);
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
if (renames.length === 0 && additions.length === 0) {
|
|
2443
|
+
p3.note(planLines.join("\n"), "Sync Plan");
|
|
2444
|
+
p3.log.info(
|
|
2445
|
+
"All servers are already in sync (some skipped due to conflicts)"
|
|
2446
|
+
);
|
|
2447
|
+
console.log();
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
p3.note(planLines.join("\n"), "Sync Plan");
|
|
2451
|
+
if (!options.yes) {
|
|
2452
|
+
const confirmed = await p3.confirm({
|
|
2453
|
+
message: "Proceed with sync?"
|
|
2454
|
+
});
|
|
2455
|
+
if (p3.isCancel(confirmed) || !confirmed) {
|
|
2456
|
+
p3.log.info("No changes made");
|
|
2457
|
+
console.log();
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
const scope = options.global ? "global" : "local";
|
|
2462
|
+
let changeCount = 0;
|
|
2463
|
+
for (const rename of renames) {
|
|
2464
|
+
const { group, agentType } = rename;
|
|
2465
|
+
const result = installServerForAgent(
|
|
2466
|
+
group.canonicalName,
|
|
2467
|
+
buildServerConfigFromStored(group.canonicalConfig),
|
|
2468
|
+
agentType,
|
|
2469
|
+
{ local: scope === "local" }
|
|
2470
|
+
);
|
|
2471
|
+
if (result.success) {
|
|
2472
|
+
changeCount++;
|
|
2473
|
+
} else {
|
|
2474
|
+
p3.log.error(
|
|
2475
|
+
`Failed to write ${group.canonicalName} to ${agents[agentType].displayName}: ${result.error}`
|
|
2476
|
+
);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
for (const addition of additions) {
|
|
2480
|
+
const { group, agentType } = addition;
|
|
2481
|
+
const result = installServerForAgent(
|
|
2482
|
+
group.canonicalName,
|
|
2483
|
+
buildServerConfigFromStored(group.canonicalConfig),
|
|
2484
|
+
agentType,
|
|
2485
|
+
{ local: scope === "local" }
|
|
2486
|
+
);
|
|
2487
|
+
if (result.success) {
|
|
2488
|
+
changeCount++;
|
|
2489
|
+
} else {
|
|
2490
|
+
p3.log.error(
|
|
2491
|
+
`Failed to add ${group.canonicalName} to ${agents[agentType].displayName}: ${result.error}`
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
for (const rename of renames) {
|
|
2496
|
+
const { group, agentType, oldName } = rename;
|
|
2497
|
+
const agentConfig = agents[agentType];
|
|
2498
|
+
const entry = group.entries.find((e) => e.agentType === agentType);
|
|
2499
|
+
if (!entry) continue;
|
|
2500
|
+
try {
|
|
2501
|
+
removeServerFromConfig(
|
|
2502
|
+
entry.configPath,
|
|
2503
|
+
agentConfig.format,
|
|
2504
|
+
getConfigKeyForServer(entry),
|
|
2505
|
+
oldName
|
|
2506
|
+
);
|
|
2507
|
+
} catch (error) {
|
|
2508
|
+
p3.log.error(
|
|
2509
|
+
`Failed to remove old alias ${oldName} from ${agentConfig.displayName}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2510
|
+
);
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
p3.log.success(
|
|
2514
|
+
`Synced ${changeCount} server${changeCount !== 1 ? "s" : ""} across ${detectedAgentTypes.size} agent${detectedAgentTypes.size !== 1 ? "s" : ""}`
|
|
2515
|
+
);
|
|
2516
|
+
console.log();
|
|
2517
|
+
}
|
|
2518
|
+
var TRANSPORT_ALIASES = {
|
|
2519
|
+
http: "http",
|
|
2520
|
+
sse: "sse",
|
|
2521
|
+
streamable_http: "http",
|
|
2522
|
+
streamableHttp: "http",
|
|
2523
|
+
"streamable-http": "http",
|
|
2524
|
+
remote: "http"
|
|
2525
|
+
};
|
|
2526
|
+
function normalizeTransportType(raw) {
|
|
2527
|
+
if (typeof raw === "string" && raw in TRANSPORT_ALIASES) {
|
|
2528
|
+
return TRANSPORT_ALIASES[raw];
|
|
2529
|
+
}
|
|
2530
|
+
return "http";
|
|
2531
|
+
}
|
|
2532
|
+
function buildServerConfigFromStored(config) {
|
|
2533
|
+
const url = typeof config.url === "string" ? config.url : typeof config.uri === "string" ? config.uri : typeof config.serverUrl === "string" ? config.serverUrl : void 0;
|
|
2534
|
+
if (url) {
|
|
2535
|
+
const result2 = {
|
|
2536
|
+
type: normalizeTransportType(config.type),
|
|
2537
|
+
url
|
|
2538
|
+
};
|
|
2539
|
+
const headers = config.headers && typeof config.headers === "object" ? config.headers : config.http_headers && typeof config.http_headers === "object" ? config.http_headers : void 0;
|
|
2540
|
+
if (headers && Object.keys(headers).length > 0) {
|
|
2541
|
+
result2.headers = headers;
|
|
2542
|
+
}
|
|
2543
|
+
return result2;
|
|
2544
|
+
}
|
|
2545
|
+
const command = typeof config.command === "string" ? config.command : typeof config.cmd === "string" ? config.cmd : void 0;
|
|
2546
|
+
const args = Array.isArray(config.args) ? config.args.filter((a) => typeof a === "string") : [];
|
|
2547
|
+
const env = config.env && typeof config.env === "object" ? config.env : config.envs && typeof config.envs === "object" ? config.envs : config.environment && typeof config.environment === "object" ? config.environment : void 0;
|
|
2548
|
+
const result = {};
|
|
2549
|
+
if (command) result.command = command;
|
|
2550
|
+
if (args.length > 0) result.args = args;
|
|
2551
|
+
if (env && Object.keys(env).length > 0) result.env = env;
|
|
2552
|
+
return result;
|
|
2553
|
+
}
|
|
2554
|
+
function resolveAgentFlags(agentFlags) {
|
|
2555
|
+
if (!agentFlags || agentFlags.length === 0) return [];
|
|
2556
|
+
const resolved = [];
|
|
2557
|
+
const invalid = [];
|
|
2558
|
+
for (const input of agentFlags) {
|
|
2559
|
+
const agentType = resolveAgentType(input);
|
|
2560
|
+
if (agentType) {
|
|
2561
|
+
resolved.push(agentType);
|
|
2562
|
+
} else {
|
|
2563
|
+
invalid.push(input);
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
if (invalid.length > 0) {
|
|
2567
|
+
p3.log.error(`Invalid agents: ${invalid.join(", ")}`);
|
|
2568
|
+
p3.log.info(`Valid agents: ${getAgentTypes().join(", ")}`);
|
|
2569
|
+
process.exit(1);
|
|
2570
|
+
}
|
|
2571
|
+
return resolved;
|
|
2572
|
+
}
|
|
1923
2573
|
function listAgents() {
|
|
1924
2574
|
showLogo();
|
|
1925
2575
|
console.log();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "add-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Add MCP servers to your favorite coding agents with a single command.",
|
|
5
5
|
"author": "Andre Landgraf <andre@neon.tech>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"fmt": "prettier --write .",
|
|
17
17
|
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
18
18
|
"dev": "tsx src/index.ts",
|
|
19
|
-
"test": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
|
|
20
|
-
"test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts",
|
|
19
|
+
"test": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/reader.test.ts && tsx tests/formats-remove.test.ts && tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
|
|
20
|
+
"test:unit": "tsx tests/source-parser.test.ts && tsx tests/agents.test.ts && tsx tests/config.test.ts && tsx tests/installer.test.ts && tsx tests/find.test.ts && tsx tests/reader.test.ts && tsx tests/formats-remove.test.ts",
|
|
21
21
|
"registry:sort": "tsx scripts/sort-registry.ts",
|
|
22
22
|
"registry:verify": "tsx scripts/verify-registry.ts",
|
|
23
23
|
"test:e2e": "tsx tests/e2e/install.test.ts && tsx tests/e2e/cli.test.ts",
|