quiver-cli 1.0.0 → 1.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 +17 -0
- package/dist/cli.js +224 -82
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,8 @@ quiver-cli check # detect drift (CI-friendly: --json, exit 1)
|
|
|
50
50
|
| `quiver-cli init` | Interactive picker; write native configs + `quiver.lock` |
|
|
51
51
|
| `quiver-cli add <id>` | Add one entry (`skill:`, `command:`, `mcp:`, `plugin:`) |
|
|
52
52
|
| `quiver-cli remove <id>` | Remove one entry; keep lockfile + configs consistent (alias: `rm`)|
|
|
53
|
+
| `quiver-cli disable <id>` | Turn an MCP server off locally (`mcp:<name>`, gitignored override)|
|
|
54
|
+
| `quiver-cli enable <id>` | Turn a locally disabled MCP server back on |
|
|
53
55
|
| `quiver-cli update [id]` | Pull newer catalog content into `.agents/` (all or one entry) |
|
|
54
56
|
| `quiver-cli sync` | Regenerate provider configs from `.agents/`; warn on drift |
|
|
55
57
|
| `quiver-cli providers` | Change which tools get configs (`opencode`, `claude`, `codex`) |
|
|
@@ -120,6 +122,21 @@ quiver-cli add plugin:rtk
|
|
|
120
122
|
quiver-cli check # also verifies that the rtk binary is on PATH
|
|
121
123
|
```
|
|
122
124
|
|
|
125
|
+
## Toggling MCP servers locally
|
|
126
|
+
|
|
127
|
+
Some MCP servers are only worth their token cost for specific tasks. Instead
|
|
128
|
+
of removing them, switch them off locally:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
quiver-cli disable mcp:posthog # drop it from the generated provider configs
|
|
132
|
+
quiver-cli enable mcp:posthog # bring it back - instant, no re-introspection
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The state lives in `.agents/config.local.json` (gitignored), so toggling never
|
|
136
|
+
touches the committed `.agents/config.json`, `quiver.lock` or your teammates'
|
|
137
|
+
setups. Disabled servers keep their lockfile entry and tool snapshot; `list`
|
|
138
|
+
marks them as `disabled`, and `check` skips their re-introspection.
|
|
139
|
+
|
|
123
140
|
## The lockfile
|
|
124
141
|
|
|
125
142
|
`quiver.lock` records, per entry: source path, content digest, and — for MCP
|
package/dist/cli.js
CHANGED
|
@@ -1199,9 +1199,47 @@ var init_fsops = __esm({
|
|
|
1199
1199
|
}
|
|
1200
1200
|
});
|
|
1201
1201
|
|
|
1202
|
-
// src/providers/
|
|
1203
|
-
import { existsSync as existsSync8 } from "fs";
|
|
1202
|
+
// src/providers/local-config.ts
|
|
1203
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1204
1204
|
import { resolve as resolve11 } from "path";
|
|
1205
|
+
var localConfigPath, readLocalConfig, disabledMcpServers, setMcpEnabled;
|
|
1206
|
+
var init_local_config = __esm({
|
|
1207
|
+
"src/providers/local-config.ts"() {
|
|
1208
|
+
"use strict";
|
|
1209
|
+
localConfigPath = (targetRoot) => resolve11(targetRoot, ".agents", "config.local.json");
|
|
1210
|
+
readLocalConfig = (targetRoot) => {
|
|
1211
|
+
const path = localConfigPath(targetRoot);
|
|
1212
|
+
if (!existsSync8(path)) return {};
|
|
1213
|
+
return JSON.parse(readFileSync6(path, "utf8"));
|
|
1214
|
+
};
|
|
1215
|
+
disabledMcpServers = (targetRoot) => {
|
|
1216
|
+
const disabled = /* @__PURE__ */ new Set();
|
|
1217
|
+
const servers = readLocalConfig(targetRoot).mcpServers ?? {};
|
|
1218
|
+
for (const [name, override] of Object.entries(servers)) {
|
|
1219
|
+
if (override.enabled === false) disabled.add(name);
|
|
1220
|
+
}
|
|
1221
|
+
return disabled;
|
|
1222
|
+
};
|
|
1223
|
+
setMcpEnabled = (targetRoot, name, enabled) => {
|
|
1224
|
+
const config = readLocalConfig(targetRoot);
|
|
1225
|
+
const servers = { ...config.mcpServers ?? {} };
|
|
1226
|
+
if (enabled) delete servers[name];
|
|
1227
|
+
else servers[name] = { enabled: false };
|
|
1228
|
+
if (Object.keys(servers).length) config.mcpServers = servers;
|
|
1229
|
+
else delete config.mcpServers;
|
|
1230
|
+
const path = localConfigPath(targetRoot);
|
|
1231
|
+
if (Object.keys(config).length === 0) {
|
|
1232
|
+
rmSync4(path, { force: true });
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
writeFileSync4(path, JSON.stringify(config, null, 2) + "\n");
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
// src/providers/opencode.ts
|
|
1241
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1242
|
+
import { resolve as resolve12 } from "path";
|
|
1205
1243
|
var formatOpenCodeJson, preserveEnvReference, preserveEnvReferences, planOpenCode;
|
|
1206
1244
|
var init_opencode = __esm({
|
|
1207
1245
|
"src/providers/opencode.ts"() {
|
|
@@ -1253,11 +1291,11 @@ var init_opencode = __esm({
|
|
|
1253
1291
|
const files = [];
|
|
1254
1292
|
const removeFiles = [];
|
|
1255
1293
|
const json = formatOpenCodeJson(rawMcpServers, opencodeConfig);
|
|
1256
|
-
const jsonPath =
|
|
1294
|
+
const jsonPath = resolve12(targetRoot, "opencode.json");
|
|
1257
1295
|
if (json) files.push({ path: jsonPath, content: json });
|
|
1258
1296
|
else removeFiles.push(jsonPath);
|
|
1259
|
-
const tuiPath =
|
|
1260
|
-
const tuiMarkerPath =
|
|
1297
|
+
const tuiPath = resolve12(targetRoot, ".opencode/tui.json");
|
|
1298
|
+
const tuiMarkerPath = resolve12(targetRoot, ".opencode/.quiver-tui");
|
|
1261
1299
|
if (tuiConfig) {
|
|
1262
1300
|
files.push({
|
|
1263
1301
|
path: tuiPath,
|
|
@@ -1268,20 +1306,20 @@ var init_opencode = __esm({
|
|
|
1268
1306
|
) + "\n"
|
|
1269
1307
|
});
|
|
1270
1308
|
files.push({ path: tuiMarkerPath, content: "managed by quiver\n" });
|
|
1271
|
-
} else if (
|
|
1309
|
+
} else if (existsSync9(tuiMarkerPath)) {
|
|
1272
1310
|
removeFiles.push(tuiPath, tuiMarkerPath);
|
|
1273
1311
|
}
|
|
1274
1312
|
const symlinks = [
|
|
1275
1313
|
...selected.skills.map((s) => ({
|
|
1276
|
-
path:
|
|
1314
|
+
path: resolve12(targetRoot, ".opencode/skills", s.name),
|
|
1277
1315
|
target: s.absDir
|
|
1278
1316
|
})),
|
|
1279
1317
|
...selected.commands.map((c) => ({
|
|
1280
|
-
path:
|
|
1318
|
+
path: resolve12(targetRoot, ".opencode/commands", `${c.name}.md`),
|
|
1281
1319
|
target: c.absPath
|
|
1282
1320
|
})),
|
|
1283
1321
|
...selected.plugins.map((plugin) => ({
|
|
1284
|
-
path:
|
|
1322
|
+
path: resolve12(
|
|
1285
1323
|
targetRoot,
|
|
1286
1324
|
".opencode/plugins",
|
|
1287
1325
|
`${plugin.name}${plugin.absPath.endsWith(".js") ? ".js" : ".ts"}`
|
|
@@ -1291,15 +1329,15 @@ var init_opencode = __esm({
|
|
|
1291
1329
|
];
|
|
1292
1330
|
const managedDirs = [
|
|
1293
1331
|
{
|
|
1294
|
-
path:
|
|
1332
|
+
path: resolve12(targetRoot, ".opencode/skills"),
|
|
1295
1333
|
expected: new Set(selected.skills.map((s) => s.name))
|
|
1296
1334
|
},
|
|
1297
1335
|
{
|
|
1298
|
-
path:
|
|
1336
|
+
path: resolve12(targetRoot, ".opencode/commands"),
|
|
1299
1337
|
expected: new Set(selected.commands.map((c) => `${c.name}.md`))
|
|
1300
1338
|
},
|
|
1301
1339
|
{
|
|
1302
|
-
path:
|
|
1340
|
+
path: resolve12(targetRoot, ".opencode/plugins"),
|
|
1303
1341
|
expected: new Set(
|
|
1304
1342
|
selected.plugins.map(
|
|
1305
1343
|
(plugin) => `${plugin.name}${plugin.absPath.endsWith(".js") ? ".js" : ".ts"}`
|
|
@@ -1342,8 +1380,8 @@ var init_selection = __esm({
|
|
|
1342
1380
|
});
|
|
1343
1381
|
|
|
1344
1382
|
// src/providers/write.ts
|
|
1345
|
-
import { existsSync as
|
|
1346
|
-
import { basename, relative as relative5, resolve as
|
|
1383
|
+
import { existsSync as existsSync10, lstatSync as lstatSync2 } from "fs";
|
|
1384
|
+
import { basename, relative as relative5, resolve as resolve13 } from "path";
|
|
1347
1385
|
var isLinkable, buildPlan, writeProviders, formatWriteResult, checkProviders;
|
|
1348
1386
|
var init_write = __esm({
|
|
1349
1387
|
"src/providers/write.ts"() {
|
|
@@ -1353,6 +1391,7 @@ var init_write = __esm({
|
|
|
1353
1391
|
init_claude();
|
|
1354
1392
|
init_codex();
|
|
1355
1393
|
init_fsops();
|
|
1394
|
+
init_local_config();
|
|
1356
1395
|
init_opencode();
|
|
1357
1396
|
init_selection();
|
|
1358
1397
|
isLinkable = (path) => {
|
|
@@ -1365,10 +1404,14 @@ var init_write = __esm({
|
|
|
1365
1404
|
buildPlan = (targetRoot, catalog, lock, onMissingEnv, onSkippedRootFile) => {
|
|
1366
1405
|
loadEnvLocal(targetRoot);
|
|
1367
1406
|
const selected = resolveSelection(catalog, lock);
|
|
1407
|
+
const disabled = disabledMcpServers(targetRoot);
|
|
1408
|
+
if (disabled.size) {
|
|
1409
|
+
selected.mcp = selected.mcp.filter((m) => !disabled.has(m.name));
|
|
1410
|
+
}
|
|
1368
1411
|
const rawMcpServers = {};
|
|
1369
1412
|
for (const mcp of selected.mcp) rawMcpServers[mcp.name] = mcp.server;
|
|
1370
1413
|
const mcpServers = interpolateEnvVars(rawMcpServers, onMissingEnv);
|
|
1371
|
-
const agentsRoot =
|
|
1414
|
+
const agentsRoot = resolve13(targetRoot, ".agents");
|
|
1372
1415
|
const inputs = {
|
|
1373
1416
|
targetRoot,
|
|
1374
1417
|
agentsRoot,
|
|
@@ -1401,12 +1444,12 @@ var init_write = __esm({
|
|
|
1401
1444
|
}
|
|
1402
1445
|
}
|
|
1403
1446
|
const rootSymlinks = [];
|
|
1404
|
-
const agentsMd =
|
|
1405
|
-
if (
|
|
1406
|
-
const rootAgents =
|
|
1447
|
+
const agentsMd = resolve13(agentsRoot, "AGENTS.md");
|
|
1448
|
+
if (existsSync10(agentsMd)) {
|
|
1449
|
+
const rootAgents = resolve13(targetRoot, "AGENTS.md");
|
|
1407
1450
|
for (const link of [
|
|
1408
1451
|
{ path: rootAgents, target: agentsMd },
|
|
1409
|
-
{ path:
|
|
1452
|
+
{ path: resolve13(targetRoot, "CLAUDE.md"), target: rootAgents }
|
|
1410
1453
|
]) {
|
|
1411
1454
|
if (isLinkable(link.path)) rootSymlinks.push(link);
|
|
1412
1455
|
else onSkippedRootFile?.(basename(link.path));
|
|
@@ -1468,15 +1511,16 @@ var init_write = __esm({
|
|
|
1468
1511
|
|
|
1469
1512
|
// src/commands/gitignore.ts
|
|
1470
1513
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1471
|
-
import { existsSync as
|
|
1472
|
-
import { resolve as
|
|
1473
|
-
var HEADER, SECRETS_COMMENT, PROVIDER_ENTRIES, SHARED_ENTRIES, SECRET_ENTRIES, patchGitignore, ignoredSourcePaths;
|
|
1514
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
1515
|
+
import { resolve as resolve14 } from "path";
|
|
1516
|
+
var HEADER, SECRETS_COMMENT, LOCAL_COMMENT, PROVIDER_ENTRIES, SHARED_ENTRIES, SECRET_ENTRIES, LOCAL_ENTRIES, patchGitignore, ensureLocalOverrideIgnored, ignoredSourcePaths;
|
|
1474
1517
|
var init_gitignore = __esm({
|
|
1475
1518
|
"src/commands/gitignore.ts"() {
|
|
1476
1519
|
"use strict";
|
|
1477
1520
|
init_schema();
|
|
1478
1521
|
HEADER = "# Generated by quiver (source of truth: .agents/ + quiver.lock)";
|
|
1479
1522
|
SECRETS_COMMENT = "# Local secrets for MCP env-var interpolation";
|
|
1523
|
+
LOCAL_COMMENT = "# Local overrides (quiver-cli enable/disable)";
|
|
1480
1524
|
PROVIDER_ENTRIES = {
|
|
1481
1525
|
claude: [".claude/", ".mcp.json"],
|
|
1482
1526
|
opencode: [".opencode/", "opencode.json"],
|
|
@@ -1484,9 +1528,10 @@ var init_gitignore = __esm({
|
|
|
1484
1528
|
};
|
|
1485
1529
|
SHARED_ENTRIES = ["AGENTS.md", "CLAUDE.md"];
|
|
1486
1530
|
SECRET_ENTRIES = [".env.local"];
|
|
1531
|
+
LOCAL_ENTRIES = [".agents/config.local.json"];
|
|
1487
1532
|
patchGitignore = (targetRoot, providers2) => {
|
|
1488
|
-
const path =
|
|
1489
|
-
const current =
|
|
1533
|
+
const path = resolve14(targetRoot, ".gitignore");
|
|
1534
|
+
const current = existsSync11(path) ? readFileSync7(path, "utf8") : "";
|
|
1490
1535
|
const lines = new Set(current.split("\n").map((l) => l.trim()));
|
|
1491
1536
|
const active = providers2?.length ? providers2 : [...PROVIDERS];
|
|
1492
1537
|
const shimEntries = [
|
|
@@ -1495,17 +1540,34 @@ var init_gitignore = __esm({
|
|
|
1495
1540
|
];
|
|
1496
1541
|
const missingShims = shimEntries.filter((e) => !lines.has(e));
|
|
1497
1542
|
const missingSecrets = SECRET_ENTRIES.filter((e) => !lines.has(e));
|
|
1498
|
-
|
|
1543
|
+
const missingLocal = LOCAL_ENTRIES.filter((e) => !lines.has(e));
|
|
1544
|
+
if (missingShims.length === 0 && missingSecrets.length === 0 && missingLocal.length === 0) {
|
|
1545
|
+
return false;
|
|
1546
|
+
}
|
|
1499
1547
|
const block2 = [];
|
|
1500
1548
|
if (missingShims.length) block2.push(HEADER, ...missingShims);
|
|
1501
1549
|
if (missingSecrets.length) block2.push(SECRETS_COMMENT, ...missingSecrets);
|
|
1550
|
+
if (missingLocal.length) block2.push(LOCAL_COMMENT, ...missingLocal);
|
|
1502
1551
|
const prefix = current && !current.endsWith("\n") ? "\n" : "";
|
|
1503
|
-
|
|
1552
|
+
writeFileSync5(
|
|
1504
1553
|
path,
|
|
1505
1554
|
current + prefix + (current ? "\n" : "") + block2.join("\n") + "\n"
|
|
1506
1555
|
);
|
|
1507
1556
|
return true;
|
|
1508
1557
|
};
|
|
1558
|
+
ensureLocalOverrideIgnored = (targetRoot) => {
|
|
1559
|
+
const path = resolve14(targetRoot, ".gitignore");
|
|
1560
|
+
const current = existsSync11(path) ? readFileSync7(path, "utf8") : "";
|
|
1561
|
+
const lines = new Set(current.split("\n").map((l) => l.trim()));
|
|
1562
|
+
const missing = LOCAL_ENTRIES.filter((e) => !lines.has(e));
|
|
1563
|
+
if (!missing.length) return false;
|
|
1564
|
+
const prefix = current && !current.endsWith("\n") ? "\n" : "";
|
|
1565
|
+
writeFileSync5(
|
|
1566
|
+
path,
|
|
1567
|
+
current + prefix + (current ? "\n" : "") + [LOCAL_COMMENT, ...missing].join("\n") + "\n"
|
|
1568
|
+
);
|
|
1569
|
+
return true;
|
|
1570
|
+
};
|
|
1509
1571
|
ignoredSourcePaths = (targetRoot) => {
|
|
1510
1572
|
try {
|
|
1511
1573
|
const out = execFileSync2(
|
|
@@ -1747,15 +1809,15 @@ var init_init = __esm({
|
|
|
1747
1809
|
});
|
|
1748
1810
|
|
|
1749
1811
|
// src/catalog/repo.ts
|
|
1750
|
-
import { existsSync as
|
|
1751
|
-
import { resolve as
|
|
1812
|
+
import { existsSync as existsSync12 } from "fs";
|
|
1813
|
+
import { resolve as resolve15 } from "path";
|
|
1752
1814
|
var repoCatalogRoot, repoCatalogExists, loadRepoCatalog;
|
|
1753
1815
|
var init_repo = __esm({
|
|
1754
1816
|
"src/catalog/repo.ts"() {
|
|
1755
1817
|
"use strict";
|
|
1756
1818
|
init_discover();
|
|
1757
|
-
repoCatalogRoot = (targetRoot) =>
|
|
1758
|
-
repoCatalogExists = (targetRoot) =>
|
|
1819
|
+
repoCatalogRoot = (targetRoot) => resolve15(targetRoot, ".agents");
|
|
1820
|
+
repoCatalogExists = (targetRoot) => existsSync12(repoCatalogRoot(targetRoot));
|
|
1759
1821
|
loadRepoCatalog = (targetRoot, source) => {
|
|
1760
1822
|
const resolved = {
|
|
1761
1823
|
source,
|
|
@@ -1869,8 +1931,8 @@ var remove_exports = {};
|
|
|
1869
1931
|
__export(remove_exports, {
|
|
1870
1932
|
remove: () => remove
|
|
1871
1933
|
});
|
|
1872
|
-
import { existsSync as
|
|
1873
|
-
import { resolve as
|
|
1934
|
+
import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
1935
|
+
import { resolve as resolve16 } from "path";
|
|
1874
1936
|
var remove, rewriteRepoPlugins, cleanupEmptyGroups, rewriteRepoMcp;
|
|
1875
1937
|
var init_remove = __esm({
|
|
1876
1938
|
"src/commands/remove.ts"() {
|
|
@@ -1920,9 +1982,9 @@ var init_remove = __esm({
|
|
|
1920
1982
|
await success(`Removed ${id}.`);
|
|
1921
1983
|
};
|
|
1922
1984
|
rewriteRepoPlugins = (targetRoot, lock) => {
|
|
1923
|
-
const configPath =
|
|
1924
|
-
if (!
|
|
1925
|
-
const config = JSON.parse(
|
|
1985
|
+
const configPath = resolve16(targetRoot, ".agents/config.json");
|
|
1986
|
+
if (!existsSync13(configPath)) return;
|
|
1987
|
+
const config = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
1926
1988
|
if (!config.plugins) return;
|
|
1927
1989
|
const keep = new Set(
|
|
1928
1990
|
Object.keys(lock.entries).map(parseEntryId).filter((p) => p?.type === "plugin").map((p) => p.name)
|
|
@@ -1931,22 +1993,22 @@ var init_remove = __esm({
|
|
|
1931
1993
|
Object.entries(config.plugins).filter(([name]) => keep.has(name))
|
|
1932
1994
|
);
|
|
1933
1995
|
if (!Object.keys(config.plugins).length) delete config.plugins;
|
|
1934
|
-
|
|
1996
|
+
writeFileSync6(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
1935
1997
|
};
|
|
1936
1998
|
cleanupEmptyGroups = (targetRoot) => {
|
|
1937
|
-
const skillsRoot =
|
|
1938
|
-
if (!
|
|
1999
|
+
const skillsRoot = resolve16(targetRoot, ".agents/skills");
|
|
2000
|
+
if (!existsSync13(skillsRoot)) return;
|
|
1939
2001
|
for (const entry of readdirSync4(skillsRoot, { withFileTypes: true })) {
|
|
1940
2002
|
if (!entry.isDirectory()) continue;
|
|
1941
|
-
const dir =
|
|
1942
|
-
if (
|
|
1943
|
-
if (readdirSync4(dir).length === 0)
|
|
2003
|
+
const dir = resolve16(skillsRoot, entry.name);
|
|
2004
|
+
if (existsSync13(resolve16(dir, "SKILL.md"))) continue;
|
|
2005
|
+
if (readdirSync4(dir).length === 0) rmSync5(dir, { recursive: true, force: true });
|
|
1944
2006
|
}
|
|
1945
2007
|
};
|
|
1946
2008
|
rewriteRepoMcp = (targetRoot, lock) => {
|
|
1947
|
-
const configPath =
|
|
1948
|
-
if (!
|
|
1949
|
-
const config = JSON.parse(
|
|
2009
|
+
const configPath = resolve16(targetRoot, ".agents/config.json");
|
|
2010
|
+
if (!existsSync13(configPath)) return;
|
|
2011
|
+
const config = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
1950
2012
|
if (!config.mcpServers) return;
|
|
1951
2013
|
const keep = new Set(
|
|
1952
2014
|
Object.keys(lock.entries).map(parseEntryId).filter((p) => p?.type === "mcp").map((p) => p.name)
|
|
@@ -1957,7 +2019,69 @@ var init_remove = __esm({
|
|
|
1957
2019
|
}
|
|
1958
2020
|
if (Object.keys(kept).length) config.mcpServers = kept;
|
|
1959
2021
|
else delete config.mcpServers;
|
|
1960
|
-
|
|
2022
|
+
writeFileSync6(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
});
|
|
2026
|
+
|
|
2027
|
+
// src/commands/toggle.ts
|
|
2028
|
+
var toggle_exports = {};
|
|
2029
|
+
__export(toggle_exports, {
|
|
2030
|
+
toggle: () => toggle
|
|
2031
|
+
});
|
|
2032
|
+
var toggle;
|
|
2033
|
+
var init_toggle = __esm({
|
|
2034
|
+
"src/commands/toggle.ts"() {
|
|
2035
|
+
"use strict";
|
|
2036
|
+
init_repo();
|
|
2037
|
+
init_io();
|
|
2038
|
+
init_schema();
|
|
2039
|
+
init_local_config();
|
|
2040
|
+
init_write();
|
|
2041
|
+
init_prompts();
|
|
2042
|
+
init_gitignore();
|
|
2043
|
+
toggle = async (options, enabled) => {
|
|
2044
|
+
const verb = enabled ? "enable" : "disable";
|
|
2045
|
+
const id = options.positionals[0];
|
|
2046
|
+
const parsed = id ? parseEntryId(id) : null;
|
|
2047
|
+
if (!parsed || parsed.type !== "mcp") {
|
|
2048
|
+
await error(`Usage: quiver-cli ${verb} mcp:<name>`);
|
|
2049
|
+
process.exitCode = 1;
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
const lock = readLockfile(options.targetRoot);
|
|
2053
|
+
if (!lock) {
|
|
2054
|
+
await error("No quiver.lock found. Run `quiver-cli init` first.");
|
|
2055
|
+
process.exitCode = 1;
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
if (!repoCatalogExists(options.targetRoot)) {
|
|
2059
|
+
await error("No .agents/ directory found. Run `quiver-cli init` first.");
|
|
2060
|
+
process.exitCode = 1;
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
if (!lock.entries[id]) {
|
|
2064
|
+
const installed = Object.keys(lock.entries).map(parseEntryId).filter((p) => p?.type === "mcp").map((p) => p.name).sort((a, b) => a.localeCompare(b));
|
|
2065
|
+
await error(
|
|
2066
|
+
`${id} is not installed.` + (installed.length ? ` Installed MCP servers: ${installed.join(", ")}.` : " No MCP servers installed.")
|
|
2067
|
+
);
|
|
2068
|
+
process.exitCode = 1;
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
2071
|
+
const disabled = disabledMcpServers(options.targetRoot);
|
|
2072
|
+
if (enabled !== disabled.has(parsed.name)) {
|
|
2073
|
+
await info(`${id} is already ${enabled ? "enabled" : "disabled"}.`);
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
setMcpEnabled(options.targetRoot, parsed.name, enabled);
|
|
2077
|
+
if (!enabled && ensureLocalOverrideIgnored(options.targetRoot)) {
|
|
2078
|
+
await step("Added .agents/config.local.json to .gitignore");
|
|
2079
|
+
}
|
|
2080
|
+
const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
|
|
2081
|
+
writeProviders(options.targetRoot, catalog, lock);
|
|
2082
|
+
await success(
|
|
2083
|
+
enabled ? `Enabled ${id}.` : `Disabled ${id} locally (.agents/config.local.json). Re-enable with \`quiver-cli enable ${id}\`.`
|
|
2084
|
+
);
|
|
1961
2085
|
};
|
|
1962
2086
|
}
|
|
1963
2087
|
});
|
|
@@ -2174,8 +2298,8 @@ var update_exports = {};
|
|
|
2174
2298
|
__export(update_exports, {
|
|
2175
2299
|
update: () => update
|
|
2176
2300
|
});
|
|
2177
|
-
import { cpSync as cpSync2, mkdirSync as mkdirSync5, readFileSync as
|
|
2178
|
-
import { dirname as dirname5, resolve as
|
|
2301
|
+
import { cpSync as cpSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
2302
|
+
import { dirname as dirname5, resolve as resolve17 } from "path";
|
|
2179
2303
|
var update, applyUpdate, report;
|
|
2180
2304
|
var init_update = __esm({
|
|
2181
2305
|
"src/commands/update.ts"() {
|
|
@@ -2262,8 +2386,8 @@ var init_update = __esm({
|
|
|
2262
2386
|
return { id, status: "local-changes" };
|
|
2263
2387
|
}
|
|
2264
2388
|
if (dryRun) return { id, status: "updated" };
|
|
2265
|
-
const dest =
|
|
2266
|
-
|
|
2389
|
+
const dest = resolve17(targetRoot, ".agents", src2.sourcePath);
|
|
2390
|
+
rmSync6(dest, { recursive: true, force: true });
|
|
2267
2391
|
mkdirSync5(dirname5(dest), { recursive: true });
|
|
2268
2392
|
cpSync2(src2.absDir, dest, { recursive: true });
|
|
2269
2393
|
entry.digest = src2.digest;
|
|
@@ -2280,7 +2404,7 @@ var init_update = __esm({
|
|
|
2280
2404
|
return { id, status: "local-changes" };
|
|
2281
2405
|
}
|
|
2282
2406
|
if (dryRun) return { id, status: "updated" };
|
|
2283
|
-
const dest =
|
|
2407
|
+
const dest = resolve17(targetRoot, ".agents", src2.sourcePath);
|
|
2284
2408
|
mkdirSync5(dirname5(dest), { recursive: true });
|
|
2285
2409
|
cpSync2(src2.absPath, dest, { force: true });
|
|
2286
2410
|
entry.digest = src2.digest;
|
|
@@ -2296,16 +2420,16 @@ var init_update = __esm({
|
|
|
2296
2420
|
return { id, status: "local-changes" };
|
|
2297
2421
|
}
|
|
2298
2422
|
if (dryRun) return { id, status: "updated" };
|
|
2299
|
-
const dest =
|
|
2423
|
+
const dest = resolve17(targetRoot, ".agents", src2.sourcePath);
|
|
2300
2424
|
mkdirSync5(dirname5(dest), { recursive: true });
|
|
2301
2425
|
cpSync2(src2.absPath, dest, { force: true });
|
|
2302
|
-
const configPath2 =
|
|
2303
|
-
const config2 = JSON.parse(
|
|
2426
|
+
const configPath2 = resolve17(targetRoot, ".agents/config.json");
|
|
2427
|
+
const config2 = JSON.parse(readFileSync9(configPath2, "utf8"));
|
|
2304
2428
|
config2.plugins = {
|
|
2305
2429
|
...config2.plugins,
|
|
2306
2430
|
[parsed.name]: sourceCatalog.config.plugins?.[parsed.name]
|
|
2307
2431
|
};
|
|
2308
|
-
|
|
2432
|
+
writeFileSync7(configPath2, JSON.stringify(config2, null, 2) + "\n");
|
|
2309
2433
|
entry.digest = src2.digest;
|
|
2310
2434
|
entry.sourcePath = src2.sourcePath;
|
|
2311
2435
|
entry.requires = src2.requires;
|
|
@@ -2319,10 +2443,10 @@ var init_update = __esm({
|
|
|
2319
2443
|
return { id, status: "local-changes" };
|
|
2320
2444
|
}
|
|
2321
2445
|
if (dryRun) return { id, status: "updated" };
|
|
2322
|
-
const configPath =
|
|
2323
|
-
const config = JSON.parse(
|
|
2446
|
+
const configPath = resolve17(targetRoot, ".agents", "config.json");
|
|
2447
|
+
const config = JSON.parse(readFileSync9(configPath, "utf8"));
|
|
2324
2448
|
config.mcpServers = { ...config.mcpServers, [parsed.name]: src.server };
|
|
2325
|
-
|
|
2449
|
+
writeFileSync7(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
2326
2450
|
entry.configDigest = src.configDigest;
|
|
2327
2451
|
entry.transport = src.server.transport;
|
|
2328
2452
|
entry.tools = null;
|
|
@@ -2387,6 +2511,7 @@ var init_list = __esm({
|
|
|
2387
2511
|
init_repo();
|
|
2388
2512
|
init_io();
|
|
2389
2513
|
init_schema();
|
|
2514
|
+
init_local_config();
|
|
2390
2515
|
init_prompts();
|
|
2391
2516
|
truncate = (s, max) => {
|
|
2392
2517
|
const flat = s.replace(/\s+/g, " ").trim();
|
|
@@ -2427,6 +2552,7 @@ var init_list = __esm({
|
|
|
2427
2552
|
for (const group of [skills, commands, mcp, plugins]) {
|
|
2428
2553
|
group.sort((a, b) => a.name.localeCompare(b.name));
|
|
2429
2554
|
}
|
|
2555
|
+
const disabled = disabledMcpServers(options.targetRoot);
|
|
2430
2556
|
if (options.json) {
|
|
2431
2557
|
console.log(
|
|
2432
2558
|
JSON.stringify(
|
|
@@ -2441,6 +2567,7 @@ var init_list = __esm({
|
|
|
2441
2567
|
mcp: mcp.map(({ name, entry }) => ({
|
|
2442
2568
|
name,
|
|
2443
2569
|
transport: entry.transport,
|
|
2570
|
+
enabled: !disabled.has(name),
|
|
2444
2571
|
detail: serverDetail2.get(name) ?? null,
|
|
2445
2572
|
toolCount: entry.tools ? Object.keys(entry.tools).length : null
|
|
2446
2573
|
})),
|
|
@@ -2500,8 +2627,9 @@ var init_list = __esm({
|
|
|
2500
2627
|
count === null ? c.dim : c.green
|
|
2501
2628
|
);
|
|
2502
2629
|
const detail = serverDetail2.get(name);
|
|
2630
|
+
const off = disabled.has(name) ? ` ${c.yellow("disabled")}` : "";
|
|
2503
2631
|
lines.push(
|
|
2504
|
-
` ${name.padEnd(nameW)} ${entry.transport.padEnd(5)} ${tools}` + (detail ? ` ${c.dim(detail)}` : "")
|
|
2632
|
+
` ${name.padEnd(nameW)} ${entry.transport.padEnd(5)} ${tools}` + (detail ? ` ${c.dim(detail)}` : "") + off
|
|
2505
2633
|
);
|
|
2506
2634
|
}
|
|
2507
2635
|
}
|
|
@@ -2666,7 +2794,7 @@ __export(check_exports, {
|
|
|
2666
2794
|
summarize: () => summarize
|
|
2667
2795
|
});
|
|
2668
2796
|
import { accessSync as accessSync2, constants as constants2 } from "fs";
|
|
2669
|
-
import { delimiter, resolve as
|
|
2797
|
+
import { delimiter, resolve as resolve18 } from "path";
|
|
2670
2798
|
var check, report2, driftLines, list2, recommend, summarize, hasCommand, truncate2, fail;
|
|
2671
2799
|
var init_check = __esm({
|
|
2672
2800
|
"src/commands/check.ts"() {
|
|
@@ -2677,6 +2805,7 @@ var init_check = __esm({
|
|
|
2677
2805
|
init_diff();
|
|
2678
2806
|
init_introspect();
|
|
2679
2807
|
init_snapshot();
|
|
2808
|
+
init_local_config();
|
|
2680
2809
|
init_write();
|
|
2681
2810
|
init_interpolate();
|
|
2682
2811
|
init_prompts();
|
|
@@ -2724,6 +2853,7 @@ var init_check = __esm({
|
|
|
2724
2853
|
}
|
|
2725
2854
|
const shimProblems = checkProviders(options.targetRoot, catalog, lock);
|
|
2726
2855
|
const mcpReports = [];
|
|
2856
|
+
const disabled = disabledMcpServers(options.targetRoot);
|
|
2727
2857
|
let lockChanged = false;
|
|
2728
2858
|
for (const [id, entry] of Object.entries(lock.entries)) {
|
|
2729
2859
|
if (options.offline) break;
|
|
@@ -2731,6 +2861,10 @@ var init_check = __esm({
|
|
|
2731
2861
|
const p = parseEntryId(id);
|
|
2732
2862
|
const catMcp = catalog.mcp.find((m) => m.name === p.name);
|
|
2733
2863
|
if (!catMcp) continue;
|
|
2864
|
+
if (disabled.has(p.name)) {
|
|
2865
|
+
mcpReports.push({ id, status: "skipped", reason: "disabled locally" });
|
|
2866
|
+
continue;
|
|
2867
|
+
}
|
|
2734
2868
|
checked.mcp += 1;
|
|
2735
2869
|
const server = interpolateEnvVars(catMcp.server);
|
|
2736
2870
|
const res = await introspect(server, { allowStdio: options.introspectStdio });
|
|
@@ -2908,7 +3042,7 @@ var init_check = __esm({
|
|
|
2908
3042
|
for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
|
|
2909
3043
|
for (const extension of extensions) {
|
|
2910
3044
|
try {
|
|
2911
|
-
accessSync2(
|
|
3045
|
+
accessSync2(resolve18(dir, command + extension), constants2.X_OK);
|
|
2912
3046
|
return true;
|
|
2913
3047
|
} catch {
|
|
2914
3048
|
}
|
|
@@ -2928,14 +3062,14 @@ var init_check = __esm({
|
|
|
2928
3062
|
// src/catalog/upstreams.ts
|
|
2929
3063
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
2930
3064
|
import {
|
|
2931
|
-
existsSync as
|
|
3065
|
+
existsSync as existsSync14,
|
|
2932
3066
|
mkdtempSync as mkdtempSync2,
|
|
2933
|
-
readFileSync as
|
|
2934
|
-
rmSync as
|
|
2935
|
-
writeFileSync as
|
|
3067
|
+
readFileSync as readFileSync10,
|
|
3068
|
+
rmSync as rmSync7,
|
|
3069
|
+
writeFileSync as writeFileSync8
|
|
2936
3070
|
} from "fs";
|
|
2937
3071
|
import { tmpdir } from "os";
|
|
2938
|
-
import { join, resolve as
|
|
3072
|
+
import { join, resolve as resolve19 } from "path";
|
|
2939
3073
|
var UPSTREAMS_FILE, upstreamsPath, loadUpstreams, writeUpstreams, fetchLatestCommit, fetchUpstreamDir, short, evaluateOrigin;
|
|
2940
3074
|
var init_upstreams = __esm({
|
|
2941
3075
|
"src/catalog/upstreams.ts"() {
|
|
@@ -2943,14 +3077,14 @@ var init_upstreams = __esm({
|
|
|
2943
3077
|
init_auth();
|
|
2944
3078
|
init_auth();
|
|
2945
3079
|
UPSTREAMS_FILE = "upstreams.json";
|
|
2946
|
-
upstreamsPath = (catalog) =>
|
|
3080
|
+
upstreamsPath = (catalog) => resolve19(catalog.root, UPSTREAMS_FILE);
|
|
2947
3081
|
loadUpstreams = (catalog) => {
|
|
2948
3082
|
const path = upstreamsPath(catalog);
|
|
2949
|
-
if (!
|
|
2950
|
-
return JSON.parse(
|
|
3083
|
+
if (!existsSync14(path)) return {};
|
|
3084
|
+
return JSON.parse(readFileSync10(path, "utf8"));
|
|
2951
3085
|
};
|
|
2952
3086
|
writeUpstreams = (catalog, map) => {
|
|
2953
|
-
|
|
3087
|
+
writeFileSync8(upstreamsPath(catalog), JSON.stringify(map, null, 2) + "\n");
|
|
2954
3088
|
};
|
|
2955
3089
|
fetchLatestCommit = async (origin) => {
|
|
2956
3090
|
const url = `https://api.github.com/repos/${origin.repo}/commits?path=${encodeURIComponent(origin.path)}&sha=${encodeURIComponent(origin.ref)}&per_page=1`;
|
|
@@ -2989,7 +3123,7 @@ var init_upstreams = __esm({
|
|
|
2989
3123
|
};
|
|
2990
3124
|
fetchUpstreamDir = (origin) => {
|
|
2991
3125
|
const tmp = mkdtempSync2(join(tmpdir(), "quiver-pull-"));
|
|
2992
|
-
const cleanup = () =>
|
|
3126
|
+
const cleanup = () => rmSync7(tmp, { recursive: true, force: true });
|
|
2993
3127
|
const git = (args, cwd) => {
|
|
2994
3128
|
execFileSync3("git", args, {
|
|
2995
3129
|
cwd,
|
|
@@ -3015,8 +3149,8 @@ var init_upstreams = __esm({
|
|
|
3015
3149
|
const msg = err instanceof Error && "stderr" in err ? String(err.stderr).trim().split("\n").pop() : err instanceof Error ? err.message : "git clone failed";
|
|
3016
3150
|
return { ok: false, reason: msg || "git clone failed" };
|
|
3017
3151
|
}
|
|
3018
|
-
const dir =
|
|
3019
|
-
if (!
|
|
3152
|
+
const dir = resolve19(tmp, origin.path);
|
|
3153
|
+
if (!existsSync14(resolve19(dir, "SKILL.md"))) {
|
|
3020
3154
|
cleanup();
|
|
3021
3155
|
return { ok: false, reason: `no SKILL.md at ${origin.path} in ${origin.repo}` };
|
|
3022
3156
|
}
|
|
@@ -3064,7 +3198,7 @@ var upstream_exports = {};
|
|
|
3064
3198
|
__export(upstream_exports, {
|
|
3065
3199
|
upstream: () => upstream
|
|
3066
3200
|
});
|
|
3067
|
-
import { cpSync as cpSync3, rmSync as
|
|
3201
|
+
import { cpSync as cpSync3, rmSync as rmSync8 } from "fs";
|
|
3068
3202
|
var upstream, guardWritableCatalog, STATUS_ORDER, pull, report3, countByStatus;
|
|
3069
3203
|
var init_upstream = __esm({
|
|
3070
3204
|
"src/commands/upstream.ts"() {
|
|
@@ -3174,7 +3308,7 @@ var init_upstream = __esm({
|
|
|
3174
3308
|
continue;
|
|
3175
3309
|
}
|
|
3176
3310
|
try {
|
|
3177
|
-
|
|
3311
|
+
rmSync8(skill.absDir, { recursive: true, force: true });
|
|
3178
3312
|
cpSync3(fetched.dir, skill.absDir, { recursive: true, dereference: true });
|
|
3179
3313
|
} finally {
|
|
3180
3314
|
fetched.cleanup();
|
|
@@ -3309,9 +3443,9 @@ __export(notifier_exports, {
|
|
|
3309
3443
|
installHint: () => installHint,
|
|
3310
3444
|
notifierSuppressed: () => notifierSuppressed
|
|
3311
3445
|
});
|
|
3312
|
-
import { existsSync as
|
|
3446
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
3313
3447
|
import { homedir as homedir2 } from "os";
|
|
3314
|
-
import { dirname as dirname6, resolve as
|
|
3448
|
+
import { dirname as dirname6, resolve as resolve20 } from "path";
|
|
3315
3449
|
var REGISTRY_URL, CHECK_TTL_MS, FETCH_TIMEOUT_MS, INSTALL_HINT, cacheFilePath, installHint, getCurrentVersion, compareSemver, readCache, writeCache, fetchLatestVersion, checkForUpdate, notifierSuppressed;
|
|
3316
3450
|
var init_notifier = __esm({
|
|
3317
3451
|
"src/version/notifier.ts"() {
|
|
@@ -3322,14 +3456,14 @@ var init_notifier = __esm({
|
|
|
3322
3456
|
FETCH_TIMEOUT_MS = 2e3;
|
|
3323
3457
|
INSTALL_HINT = "pnpm add -g quiver-cli";
|
|
3324
3458
|
cacheFilePath = () => {
|
|
3325
|
-
const base = process.env["XDG_CACHE_HOME"] ||
|
|
3326
|
-
return
|
|
3459
|
+
const base = process.env["XDG_CACHE_HOME"] || resolve20(homedir2(), ".cache");
|
|
3460
|
+
return resolve20(base, "quiver", "update-check.json");
|
|
3327
3461
|
};
|
|
3328
3462
|
installHint = () => INSTALL_HINT;
|
|
3329
3463
|
getCurrentVersion = () => {
|
|
3330
3464
|
try {
|
|
3331
3465
|
const pkg = JSON.parse(
|
|
3332
|
-
|
|
3466
|
+
readFileSync11(resolve20(packageRoot, "package.json"), "utf8")
|
|
3333
3467
|
);
|
|
3334
3468
|
return pkg.version;
|
|
3335
3469
|
} catch {
|
|
@@ -3355,9 +3489,9 @@ var init_notifier = __esm({
|
|
|
3355
3489
|
};
|
|
3356
3490
|
readCache = () => {
|
|
3357
3491
|
const path = cacheFilePath();
|
|
3358
|
-
if (!
|
|
3492
|
+
if (!existsSync15(path)) return null;
|
|
3359
3493
|
try {
|
|
3360
|
-
return JSON.parse(
|
|
3494
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
3361
3495
|
} catch {
|
|
3362
3496
|
return null;
|
|
3363
3497
|
}
|
|
@@ -3366,7 +3500,7 @@ var init_notifier = __esm({
|
|
|
3366
3500
|
try {
|
|
3367
3501
|
const path = cacheFilePath();
|
|
3368
3502
|
mkdirSync6(dirname6(path), { recursive: true });
|
|
3369
|
-
|
|
3503
|
+
writeFileSync9(path, JSON.stringify(cache, null, 2) + "\n");
|
|
3370
3504
|
} catch {
|
|
3371
3505
|
}
|
|
3372
3506
|
};
|
|
@@ -3420,6 +3554,8 @@ Commands:
|
|
|
3420
3554
|
init Interactive picker over the catalog; write native configs + quiver.lock
|
|
3421
3555
|
add <id> Add an entry (skill:<name>, command:<name>, mcp:<name>, plugin:<name>)
|
|
3422
3556
|
remove <id> Remove a single entry; keep lockfile + configs consistent
|
|
3557
|
+
disable <id> Turn an MCP server off locally (mcp:<name>, gitignored override)
|
|
3558
|
+
enable <id> Turn a locally disabled MCP server back on
|
|
3423
3559
|
sync Regenerate provider configs from .agents/ (warns on drift)
|
|
3424
3560
|
providers [a,b] Change which tools get configs (claude, opencode, codex)
|
|
3425
3561
|
update [id] Pull newer catalog content into .agents/ (all or one entry)
|
|
@@ -3533,6 +3669,12 @@ var run = async () => {
|
|
|
3533
3669
|
await remove2(options);
|
|
3534
3670
|
break;
|
|
3535
3671
|
}
|
|
3672
|
+
case "enable":
|
|
3673
|
+
case "disable": {
|
|
3674
|
+
const { toggle: toggle2 } = await Promise.resolve().then(() => (init_toggle(), toggle_exports));
|
|
3675
|
+
await toggle2(options, command === "enable");
|
|
3676
|
+
break;
|
|
3677
|
+
}
|
|
3536
3678
|
case "sync": {
|
|
3537
3679
|
const { sync: sync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
3538
3680
|
await sync2(options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quiver-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Compose selected skills, commands, plugins and MCP servers from a central catalog into any repo as native configs for opencode, Claude Code and Codex - with lockfile-based drift awareness.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|