mcpill 1.9.0 → 1.11.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/CHANGELOG.md +17 -0
- package/dist/cli.js +562 -24
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.11.0
|
|
4
|
+
|
|
5
|
+
- `trigger: UserPromptSubmit` is a valid hook trigger in `PILL.md` `## Hook:` sections and `.mcpill/server/hooks/*.md` files
|
|
6
|
+
- `mcpill compile` writes `UserPromptSubmit` hooks to `.claude/settings.json` without a `matcher` field (not supported by this event type)
|
|
7
|
+
- `matcher` or `blocking: true` on a `UserPromptSubmit` hook emits a compile-time warning and the field is dropped
|
|
8
|
+
|
|
9
|
+
## 1.10.0
|
|
10
|
+
|
|
11
|
+
- `mcpill compile` emits `.mcpill/dist/<pill>/` — a standalone npm package containing `install.js`, `index.js`, `tools.js`, `package.json`, and (for blocking pills) `hooks/pre-tool-gate.js` + `hooks/post-analysis-flag.js`
|
|
12
|
+
- `<pill> install --agent <target>` wires the MCP server and blocking hooks into the target agent's config; supports `claude-code`, `cursor`, `windsurf`, `github-copilot`, `codex`; idempotent
|
|
13
|
+
- `<pill> uninstall --agent <target>` reverses the install cleanly
|
|
14
|
+
- `hooks/pre-tool-gate.js` normalises stdin from all four agent input shapes (Claude Code, Cursor, Windsurf, GitHub Copilot) and branches exit code / output per agent (exit 1, exit 2, or JSON deny)
|
|
15
|
+
- `index.js` in the standalone package wraps `required_tool` handlers to write the flag file, enabling enforcement on agents with no `PostToolUse` hook (Windsurf)
|
|
16
|
+
- Opt-in `postinstall: true` and `version` fields in `PILL.md ## Server` block
|
|
17
|
+
- `<pill> install` writes `~/.{pillName}/state.json { enabled: true }` so gate enforcement is active immediately after install; `uninstall` removes it
|
|
18
|
+
- `mcpill init` scaffolds `README.md` with a quickstart guide covering project layout and tool-editing workflow
|
|
19
|
+
|
|
3
20
|
## 1.9.0
|
|
4
21
|
|
|
5
22
|
- `blocking: true` field on `PILL.md` `## Hook:` blocks; `mcpill compile` generates a `.mjs` `PreToolUse` gate script (exits 1 until a required tool has run) and a `PostToolUse` flag script
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
|
-
import { dirname, join as
|
|
6
|
+
import { dirname, join as join7 } from "path";
|
|
7
7
|
import { readFileSync as readFileSync5 } from "fs";
|
|
8
8
|
|
|
9
9
|
// src/commands/init.ts
|
|
@@ -1076,8 +1076,13 @@ function mergeUserHooksIntoSettings(baseDir, hookBlocks) {
|
|
|
1076
1076
|
...block.matcher !== void 0 ? { matcher: block.matcher } : {},
|
|
1077
1077
|
hooks: [{ type: "command", command: block.command }]
|
|
1078
1078
|
};
|
|
1079
|
-
|
|
1080
|
-
|
|
1079
|
+
let idx;
|
|
1080
|
+
if (block.event === "UserPromptSubmit") {
|
|
1081
|
+
idx = existing.findIndex((e) => e.hooks[0]?.command === block.command);
|
|
1082
|
+
} else {
|
|
1083
|
+
const matcherKey = block.matcher ?? "";
|
|
1084
|
+
idx = existing.findIndex((e) => (e.matcher ?? "") === matcherKey);
|
|
1085
|
+
}
|
|
1081
1086
|
if (idx !== -1) {
|
|
1082
1087
|
existing[idx] = newEntry;
|
|
1083
1088
|
} else {
|
|
@@ -1375,8 +1380,8 @@ async function runServer(opts) {
|
|
|
1375
1380
|
}
|
|
1376
1381
|
|
|
1377
1382
|
// src/commands/compile.ts
|
|
1378
|
-
import { resolve, join as
|
|
1379
|
-
import { readFileSync as readFileSync4, writeFileSync as
|
|
1383
|
+
import { resolve, join as join6 } from "path";
|
|
1384
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync3, readdirSync as readdirSync2 } from "fs";
|
|
1380
1385
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1381
1386
|
|
|
1382
1387
|
// src/compiler/serialize.ts
|
|
@@ -1548,6 +1553,514 @@ function mergeHandlers(doc, existing) {
|
|
|
1548
1553
|
return { doc: { ...doc, tools }, stubbed };
|
|
1549
1554
|
}
|
|
1550
1555
|
|
|
1556
|
+
// src/compiler/standalone.ts
|
|
1557
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
1558
|
+
import { join as join5 } from "path";
|
|
1559
|
+
function generateStandaloneIndexJs(pillName, version, prompts, resources, blockingHookTools) {
|
|
1560
|
+
const promptsJson = JSON.stringify(prompts, null, 2);
|
|
1561
|
+
const resourcesJson = JSON.stringify(resources, null, 2);
|
|
1562
|
+
const lines = [
|
|
1563
|
+
`#!/usr/bin/env node`,
|
|
1564
|
+
`import { createServer } from 'mcpster';`,
|
|
1565
|
+
`import { z } from 'mcpill-runtime';`,
|
|
1566
|
+
`import { writeFileSync, mkdirSync } from 'fs';`,
|
|
1567
|
+
`import { homedir } from 'os';`,
|
|
1568
|
+
`import { join } from 'path';`,
|
|
1569
|
+
`import tools from './tools.js';`,
|
|
1570
|
+
``,
|
|
1571
|
+
`const PILL_NAME = ${JSON.stringify(pillName)};`,
|
|
1572
|
+
`const PILL_VERSION = ${JSON.stringify(version)};`,
|
|
1573
|
+
`const REQUIRED_TOOLS = ${JSON.stringify(blockingHookTools)};`,
|
|
1574
|
+
``,
|
|
1575
|
+
`const prompts = ${promptsJson};`,
|
|
1576
|
+
`const resources = ${resourcesJson};`
|
|
1577
|
+
];
|
|
1578
|
+
if (blockingHookTools.length > 0) {
|
|
1579
|
+
lines.push(
|
|
1580
|
+
``,
|
|
1581
|
+
`const _writeFlagFile = (pillName, toolName) => {`,
|
|
1582
|
+
` const dir = join(homedir(), '.' + pillName);`,
|
|
1583
|
+
` try {`,
|
|
1584
|
+
` mkdirSync(dir, { recursive: true });`,
|
|
1585
|
+
` writeFileSync(join(dir, 'last_' + toolName + '_run.json'), JSON.stringify({ timestamp: new Date().toISOString() }));`,
|
|
1586
|
+
` } catch {}`,
|
|
1587
|
+
`};`
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
lines.push(
|
|
1591
|
+
``,
|
|
1592
|
+
`const server = createServer({ name: PILL_NAME, version: PILL_VERSION, transport: 'stdio' });`,
|
|
1593
|
+
``,
|
|
1594
|
+
`for (const { name, description, schema, handler } of tools) {`,
|
|
1595
|
+
` server.defineTool({`,
|
|
1596
|
+
` name,`,
|
|
1597
|
+
` description,`,
|
|
1598
|
+
` schema: z.object(schema),`
|
|
1599
|
+
);
|
|
1600
|
+
if (blockingHookTools.length > 0) {
|
|
1601
|
+
lines.push(
|
|
1602
|
+
` handler: REQUIRED_TOOLS.includes(name)`,
|
|
1603
|
+
` ? async (args) => { const r = await handler(args); _writeFlagFile(PILL_NAME, name); return r; }`,
|
|
1604
|
+
` : handler,`
|
|
1605
|
+
);
|
|
1606
|
+
} else {
|
|
1607
|
+
lines.push(` handler,`);
|
|
1608
|
+
}
|
|
1609
|
+
lines.push(
|
|
1610
|
+
` });`,
|
|
1611
|
+
`}`,
|
|
1612
|
+
``,
|
|
1613
|
+
`for (const { name: promptName, description, messages } of prompts) {`,
|
|
1614
|
+
` server.definePrompt({`,
|
|
1615
|
+
` name: promptName,`,
|
|
1616
|
+
` description,`,
|
|
1617
|
+
` handler: async (args) =>`,
|
|
1618
|
+
` messages.map(m => m.content.replace(/{{(\\w+)}}/g, (_, k) => String(args[k] ?? ''))).join('\\n'),`,
|
|
1619
|
+
` });`,
|
|
1620
|
+
`}`,
|
|
1621
|
+
``,
|
|
1622
|
+
`for (const { uri, name: resourceName, content } of resources) {`,
|
|
1623
|
+
` server.defineResource({`,
|
|
1624
|
+
` uri,`,
|
|
1625
|
+
` description: resourceName ?? uri,`,
|
|
1626
|
+
` resolver: async () => content,`,
|
|
1627
|
+
` });`,
|
|
1628
|
+
`}`,
|
|
1629
|
+
``,
|
|
1630
|
+
`await server.start();`,
|
|
1631
|
+
``
|
|
1632
|
+
);
|
|
1633
|
+
return lines.join("\n");
|
|
1634
|
+
}
|
|
1635
|
+
function generateStandaloneGateScript(pillName, blockingHooks) {
|
|
1636
|
+
const hooksData = blockingHooks.map((h) => ({
|
|
1637
|
+
required_tool: h.required_tool,
|
|
1638
|
+
gate_window_seconds: h.gate_window_seconds ?? 120,
|
|
1639
|
+
instructions: h.instructions ?? `[mcpill:${pillName}] Run mcp__${pillName}__${h.required_tool} before calling other tools.`
|
|
1640
|
+
}));
|
|
1641
|
+
return [
|
|
1642
|
+
`#!/usr/bin/env node`,
|
|
1643
|
+
`import { readFileSync, existsSync } from 'fs';`,
|
|
1644
|
+
`import { homedir } from 'os';`,
|
|
1645
|
+
`import { join } from 'path';`,
|
|
1646
|
+
``,
|
|
1647
|
+
`const PILL_NAME = ${JSON.stringify(pillName)};`,
|
|
1648
|
+
`const HOOKS = ${JSON.stringify(hooksData, null, 2)};`,
|
|
1649
|
+
``,
|
|
1650
|
+
`const raw = readFileSync(0, 'utf-8');`,
|
|
1651
|
+
`const input = JSON.parse(raw);`,
|
|
1652
|
+
``,
|
|
1653
|
+
`// Agent detection + tool name normalization`,
|
|
1654
|
+
`let toolName;`,
|
|
1655
|
+
`if (typeof input.tool_info === 'object' && input.tool_info !== null && typeof input.tool_info.mcp_tool_name === 'string') {`,
|
|
1656
|
+
` toolName = input.tool_info.mcp_tool_name; // Windsurf`,
|
|
1657
|
+
`} else if (typeof input.toolName === 'string') {`,
|
|
1658
|
+
` toolName = input.toolName; // GitHub Copilot`,
|
|
1659
|
+
`} else {`,
|
|
1660
|
+
` toolName = input.tool_name ?? ''; // Claude Code, Cursor, Codex`,
|
|
1661
|
+
`}`,
|
|
1662
|
+
``,
|
|
1663
|
+
`if (toolName.startsWith('mcp__' + PILL_NAME + '__')) process.exit(0);`,
|
|
1664
|
+
``,
|
|
1665
|
+
`const unsatisfied = [];`,
|
|
1666
|
+
`for (const hook of HOOKS) {`,
|
|
1667
|
+
` const stateFile = join(homedir(), '.' + PILL_NAME, 'state.json');`,
|
|
1668
|
+
` if (!existsSync(stateFile)) continue;`,
|
|
1669
|
+
` const state = JSON.parse(readFileSync(stateFile, 'utf-8'));`,
|
|
1670
|
+
` if (state.enabled === false) continue;`,
|
|
1671
|
+
` const flagFile = join(homedir(), '.' + PILL_NAME, 'last_' + hook.required_tool + '_run.json');`,
|
|
1672
|
+
` if (existsSync(flagFile)) {`,
|
|
1673
|
+
` const flag = JSON.parse(readFileSync(flagFile, 'utf-8'));`,
|
|
1674
|
+
` const elapsedMs = Date.now() - new Date(flag.timestamp).getTime();`,
|
|
1675
|
+
` if (elapsedMs <= hook.gate_window_seconds * 1000) continue;`,
|
|
1676
|
+
` }`,
|
|
1677
|
+
` unsatisfied.push(hook.instructions);`,
|
|
1678
|
+
`}`,
|
|
1679
|
+
``,
|
|
1680
|
+
`if (unsatisfied.length === 0) process.exit(0);`,
|
|
1681
|
+
``,
|
|
1682
|
+
`const blockMsg = unsatisfied.join('\\n');`,
|
|
1683
|
+
``,
|
|
1684
|
+
`// Per-agent output format`,
|
|
1685
|
+
`if (typeof input.tool_info === 'object' && input.tool_info !== null && typeof input.tool_info.mcp_tool_name === 'string') {`,
|
|
1686
|
+
` // Windsurf: exit 2`,
|
|
1687
|
+
` process.stderr.write(blockMsg + '\\n');`,
|
|
1688
|
+
` process.exit(2);`,
|
|
1689
|
+
`} else if (typeof input.toolName === 'string') {`,
|
|
1690
|
+
` // GitHub Copilot: JSON deny`,
|
|
1691
|
+
` process.stdout.write(JSON.stringify({ permissionDecision: 'deny', permissionDecisionReason: blockMsg }));`,
|
|
1692
|
+
` process.exit(0);`,
|
|
1693
|
+
`} else if (input.workspace_roots !== undefined || typeof input.conversation_id === 'string') {`,
|
|
1694
|
+
` // Cursor: JSON deny`,
|
|
1695
|
+
` process.stdout.write(JSON.stringify({ permission: 'deny', agentMessage: blockMsg }));`,
|
|
1696
|
+
` process.exit(0);`,
|
|
1697
|
+
`} else {`,
|
|
1698
|
+
` // Claude Code / Codex: exit 1`,
|
|
1699
|
+
` process.stderr.write(blockMsg + '\\n');`,
|
|
1700
|
+
` process.exit(1);`,
|
|
1701
|
+
`}`,
|
|
1702
|
+
``
|
|
1703
|
+
].join("\n");
|
|
1704
|
+
}
|
|
1705
|
+
function generateStandaloneFlagScript(pillName) {
|
|
1706
|
+
return [
|
|
1707
|
+
`#!/usr/bin/env node`,
|
|
1708
|
+
`import { writeFileSync, mkdirSync } from 'fs';`,
|
|
1709
|
+
`import { homedir } from 'os';`,
|
|
1710
|
+
`import { join } from 'path';`,
|
|
1711
|
+
``,
|
|
1712
|
+
`const PILL_NAME = ${JSON.stringify(pillName)};`,
|
|
1713
|
+
``,
|
|
1714
|
+
`const toolIdx = process.argv.indexOf('--tool');`,
|
|
1715
|
+
`const toolName = toolIdx !== -1 ? process.argv[toolIdx + 1] : null;`,
|
|
1716
|
+
``,
|
|
1717
|
+
`if (!toolName) {`,
|
|
1718
|
+
` process.stderr.write('[' + PILL_NAME + '] error: --tool <name> required\\n');`,
|
|
1719
|
+
` process.exit(1);`,
|
|
1720
|
+
`}`,
|
|
1721
|
+
``,
|
|
1722
|
+
`const dir = join(homedir(), '.' + PILL_NAME);`,
|
|
1723
|
+
`const flagFile = join(dir, 'last_' + toolName + '_run.json');`,
|
|
1724
|
+
``,
|
|
1725
|
+
`try {`,
|
|
1726
|
+
` mkdirSync(dir, { recursive: true });`,
|
|
1727
|
+
` writeFileSync(flagFile, JSON.stringify({ timestamp: new Date().toISOString() }));`,
|
|
1728
|
+
`} catch (e) {`,
|
|
1729
|
+
` process.stderr.write('[' + PILL_NAME + '] warning: could not write flag file: ' + e.message + '\\n');`,
|
|
1730
|
+
`}`,
|
|
1731
|
+
``
|
|
1732
|
+
].join("\n");
|
|
1733
|
+
}
|
|
1734
|
+
var INSTALL_JS_BODY = `
|
|
1735
|
+
function readJson(filePath) {
|
|
1736
|
+
if (!existsSync(filePath)) return {};
|
|
1737
|
+
try {
|
|
1738
|
+
return JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
1739
|
+
} catch {
|
|
1740
|
+
const bakPath = filePath + '.bak';
|
|
1741
|
+
try { writeFileSync(bakPath, readFileSync(filePath)); } catch {}
|
|
1742
|
+
process.stderr.write('[mcpill] warning: ' + filePath + ' was not valid JSON \u2014 backed up to ' + bakPath + ' and recreated\\n');
|
|
1743
|
+
return {};
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
function writeJson(filePath, obj) {
|
|
1748
|
+
const dir = dirname(filePath);
|
|
1749
|
+
mkdirSync(dir, { recursive: true });
|
|
1750
|
+
writeFileSync(filePath, JSON.stringify(obj, null, 2) + '\\n');
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
function activatePill() {
|
|
1754
|
+
mkdirSync(dirname(STATE_FILE), { recursive: true });
|
|
1755
|
+
writeJson(STATE_FILE, { enabled: true });
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
function deactivatePill() {
|
|
1759
|
+
try { rmSync(STATE_FILE, { force: true }); } catch {}
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
function installClaudeCode() {
|
|
1763
|
+
const settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
1764
|
+
const wasInstalled = existsSync(settingsPath) && (readJson(settingsPath).mcpServers || {})[PILL_NAME] !== undefined;
|
|
1765
|
+
const s = readJson(settingsPath);
|
|
1766
|
+
if (!s.mcpServers) s.mcpServers = {};
|
|
1767
|
+
s.mcpServers[PILL_NAME] = MCP_ENTRY;
|
|
1768
|
+
if (HAS_BLOCKING_HOOKS) {
|
|
1769
|
+
if (!s.hooks) s.hooks = {};
|
|
1770
|
+
if (!s.hooks.PreToolUse) s.hooks.PreToolUse = [];
|
|
1771
|
+
if (!s.hooks.PostToolUse) s.hooks.PostToolUse = [];
|
|
1772
|
+
const gateMarker = '/' + PILL_NAME + '/hooks/';
|
|
1773
|
+
s.hooks.PreToolUse = s.hooks.PreToolUse.filter(e => !e.hooks?.[0]?.command?.includes(gateMarker));
|
|
1774
|
+
s.hooks.PostToolUse = s.hooks.PostToolUse.filter(e => !e.matcher?.startsWith('mcp__' + PILL_NAME + '__'));
|
|
1775
|
+
s.hooks.PreToolUse.push({ matcher: '.*', hooks: [{ type: 'command', command: 'node "' + GATE_PATH + '"' }] });
|
|
1776
|
+
for (const hook of HOOKS) {
|
|
1777
|
+
s.hooks.PostToolUse.push({
|
|
1778
|
+
matcher: 'mcp__' + PILL_NAME + '__' + hook.required_tool,
|
|
1779
|
+
hooks: [{ type: 'command', command: 'node "' + FLAG_PATH + '" --tool ' + hook.required_tool }],
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
writeJson(settingsPath, s);
|
|
1784
|
+
activatePill();
|
|
1785
|
+
if (!silent) process.stdout.write('[mcpill] info: ' + (wasInstalled ? 'reinstalled' : 'installed') + ' ' + PILL_NAME + (HAS_BLOCKING_HOOKS ? '' : ' (no blocking hooks \u2014 MCP server only)') + ' for claude-code\\n');
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
function uninstallClaudeCode() {
|
|
1789
|
+
const settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
1790
|
+
if (!existsSync(settingsPath)) return;
|
|
1791
|
+
const s = readJson(settingsPath);
|
|
1792
|
+
if (s.mcpServers) delete s.mcpServers[PILL_NAME];
|
|
1793
|
+
if (s.hooks) {
|
|
1794
|
+
const gateMarker = '/' + PILL_NAME + '/hooks/';
|
|
1795
|
+
if (s.hooks.PreToolUse) s.hooks.PreToolUse = s.hooks.PreToolUse.filter(e => !e.hooks?.[0]?.command?.includes(gateMarker));
|
|
1796
|
+
if (s.hooks.PostToolUse) s.hooks.PostToolUse = s.hooks.PostToolUse.filter(e => !e.matcher?.startsWith('mcp__' + PILL_NAME + '__'));
|
|
1797
|
+
}
|
|
1798
|
+
writeJson(settingsPath, s);
|
|
1799
|
+
deactivatePill();
|
|
1800
|
+
if (!silent) process.stdout.write('[mcpill] info: uninstalled ' + PILL_NAME + ' from claude-code\\n');
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
function installCursor() {
|
|
1804
|
+
const mcpPath = join(homedir(), '.cursor', 'mcp.json');
|
|
1805
|
+
const mcp = readJson(mcpPath);
|
|
1806
|
+
if (!mcp.mcpServers) mcp.mcpServers = {};
|
|
1807
|
+
mcp.mcpServers[PILL_NAME] = MCP_ENTRY;
|
|
1808
|
+
writeJson(mcpPath, mcp);
|
|
1809
|
+
if (HAS_BLOCKING_HOOKS) {
|
|
1810
|
+
const hooksPath = join(homedir(), '.cursor', 'hooks.json');
|
|
1811
|
+
const h = readJson(hooksPath);
|
|
1812
|
+
if (!h.hooks) h.hooks = {};
|
|
1813
|
+
if (!h.hooks.beforeMCPExecution) h.hooks.beforeMCPExecution = [];
|
|
1814
|
+
if (!h.hooks.afterMCPExecution) h.hooks.afterMCPExecution = [];
|
|
1815
|
+
const gateMarker = '/' + PILL_NAME + '/hooks/';
|
|
1816
|
+
h.hooks.beforeMCPExecution = h.hooks.beforeMCPExecution.filter(e => !e.command?.includes(gateMarker));
|
|
1817
|
+
h.hooks.afterMCPExecution = h.hooks.afterMCPExecution.filter(e => !e.command?.includes(gateMarker));
|
|
1818
|
+
h.hooks.beforeMCPExecution.push({ command: 'node "' + GATE_PATH + '"', type: 'command', matcher: '.*' });
|
|
1819
|
+
for (const hook of HOOKS) {
|
|
1820
|
+
h.hooks.afterMCPExecution.push({
|
|
1821
|
+
command: 'node "' + FLAG_PATH + '" --tool ' + hook.required_tool,
|
|
1822
|
+
type: 'command',
|
|
1823
|
+
matcher: 'mcp__' + PILL_NAME + '__' + hook.required_tool,
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
writeJson(hooksPath, h);
|
|
1827
|
+
}
|
|
1828
|
+
activatePill();
|
|
1829
|
+
if (!silent) process.stdout.write('[mcpill] info: installed ' + PILL_NAME + ' for cursor\\n');
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
function uninstallCursor() {
|
|
1833
|
+
const mcpPath = join(homedir(), '.cursor', 'mcp.json');
|
|
1834
|
+
if (existsSync(mcpPath)) {
|
|
1835
|
+
const mcp = readJson(mcpPath);
|
|
1836
|
+
if (mcp.mcpServers) delete mcp.mcpServers[PILL_NAME];
|
|
1837
|
+
writeJson(mcpPath, mcp);
|
|
1838
|
+
}
|
|
1839
|
+
const hooksPath = join(homedir(), '.cursor', 'hooks.json');
|
|
1840
|
+
if (existsSync(hooksPath)) {
|
|
1841
|
+
const h = readJson(hooksPath);
|
|
1842
|
+
const gateMarker = '/' + PILL_NAME + '/hooks/';
|
|
1843
|
+
if (h.hooks?.beforeMCPExecution) h.hooks.beforeMCPExecution = h.hooks.beforeMCPExecution.filter(e => !e.command?.includes(gateMarker));
|
|
1844
|
+
if (h.hooks?.afterMCPExecution) h.hooks.afterMCPExecution = h.hooks.afterMCPExecution.filter(e => !e.command?.includes(gateMarker));
|
|
1845
|
+
writeJson(hooksPath, h);
|
|
1846
|
+
}
|
|
1847
|
+
deactivatePill();
|
|
1848
|
+
if (!silent) process.stdout.write('[mcpill] info: uninstalled ' + PILL_NAME + ' from cursor\\n');
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
function installWindsurf() {
|
|
1852
|
+
const mcpPath = join(homedir(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
1853
|
+
const mcp = readJson(mcpPath);
|
|
1854
|
+
if (!mcp.mcpServers) mcp.mcpServers = {};
|
|
1855
|
+
mcp.mcpServers[PILL_NAME] = MCP_ENTRY;
|
|
1856
|
+
writeJson(mcpPath, mcp);
|
|
1857
|
+
if (HAS_BLOCKING_HOOKS) {
|
|
1858
|
+
const hooksPath = join(homedir(), '.codeium', 'windsurf', 'hooks.json');
|
|
1859
|
+
const h = readJson(hooksPath);
|
|
1860
|
+
if (!h.hooks) h.hooks = {};
|
|
1861
|
+
if (!h.hooks.pre_mcp_tool_use) h.hooks.pre_mcp_tool_use = [];
|
|
1862
|
+
const gateMarker = '/' + PILL_NAME + '/hooks/';
|
|
1863
|
+
h.hooks.pre_mcp_tool_use = h.hooks.pre_mcp_tool_use.filter(e => !e.command?.includes(gateMarker));
|
|
1864
|
+
h.hooks.pre_mcp_tool_use.push({ command: 'node "' + GATE_PATH + '"' });
|
|
1865
|
+
writeJson(hooksPath, h);
|
|
1866
|
+
}
|
|
1867
|
+
activatePill();
|
|
1868
|
+
if (!silent) process.stdout.write('[mcpill] info: installed ' + PILL_NAME + ' for windsurf\\n');
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
function uninstallWindsurf() {
|
|
1872
|
+
const mcpPath = join(homedir(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
1873
|
+
if (existsSync(mcpPath)) {
|
|
1874
|
+
const mcp = readJson(mcpPath);
|
|
1875
|
+
if (mcp.mcpServers) delete mcp.mcpServers[PILL_NAME];
|
|
1876
|
+
writeJson(mcpPath, mcp);
|
|
1877
|
+
}
|
|
1878
|
+
const hooksPath = join(homedir(), '.codeium', 'windsurf', 'hooks.json');
|
|
1879
|
+
if (existsSync(hooksPath)) {
|
|
1880
|
+
const h = readJson(hooksPath);
|
|
1881
|
+
const gateMarker = '/' + PILL_NAME + '/hooks/';
|
|
1882
|
+
if (h.hooks?.pre_mcp_tool_use) h.hooks.pre_mcp_tool_use = h.hooks.pre_mcp_tool_use.filter(e => !e.command?.includes(gateMarker));
|
|
1883
|
+
writeJson(hooksPath, h);
|
|
1884
|
+
}
|
|
1885
|
+
deactivatePill();
|
|
1886
|
+
if (!silent) process.stdout.write('[mcpill] info: uninstalled ' + PILL_NAME + ' from windsurf\\n');
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
function installCopilot() {
|
|
1890
|
+
const mcpPath = join(homedir(), '.vscode', 'mcp.json');
|
|
1891
|
+
const mcp = readJson(mcpPath);
|
|
1892
|
+
if (!mcp.mcpServers) mcp.mcpServers = {};
|
|
1893
|
+
mcp.mcpServers[PILL_NAME] = MCP_ENTRY;
|
|
1894
|
+
writeJson(mcpPath, mcp);
|
|
1895
|
+
if (HAS_BLOCKING_HOOKS) {
|
|
1896
|
+
const hooksPath = join(homedir(), '.copilot', 'settings.json');
|
|
1897
|
+
const h = readJson(hooksPath);
|
|
1898
|
+
if (!h.hooks) h.hooks = {};
|
|
1899
|
+
if (!h.hooks.preToolUse) h.hooks.preToolUse = [];
|
|
1900
|
+
if (!h.hooks.postToolUse) h.hooks.postToolUse = [];
|
|
1901
|
+
const gateMarker = '/' + PILL_NAME + '/hooks/';
|
|
1902
|
+
h.hooks.preToolUse = h.hooks.preToolUse.filter(e => !e.bash?.includes(gateMarker));
|
|
1903
|
+
h.hooks.postToolUse = h.hooks.postToolUse.filter(e => !e.bash?.includes(gateMarker));
|
|
1904
|
+
h.hooks.preToolUse.push({ type: 'command', bash: 'node "' + GATE_PATH + '"', timeoutSec: 30 });
|
|
1905
|
+
for (const hook of HOOKS) {
|
|
1906
|
+
h.hooks.postToolUse.push({
|
|
1907
|
+
type: 'command',
|
|
1908
|
+
bash: 'node "' + FLAG_PATH + '" --tool ' + hook.required_tool,
|
|
1909
|
+
timeoutSec: 30,
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
writeJson(hooksPath, h);
|
|
1913
|
+
}
|
|
1914
|
+
activatePill();
|
|
1915
|
+
if (!silent) process.stdout.write('[mcpill] info: installed ' + PILL_NAME + ' for github-copilot\\n');
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
function uninstallCopilot() {
|
|
1919
|
+
const mcpPath = join(homedir(), '.vscode', 'mcp.json');
|
|
1920
|
+
if (existsSync(mcpPath)) {
|
|
1921
|
+
const mcp = readJson(mcpPath);
|
|
1922
|
+
if (mcp.mcpServers) delete mcp.mcpServers[PILL_NAME];
|
|
1923
|
+
writeJson(mcpPath, mcp);
|
|
1924
|
+
}
|
|
1925
|
+
const hooksPath = join(homedir(), '.copilot', 'settings.json');
|
|
1926
|
+
if (existsSync(hooksPath)) {
|
|
1927
|
+
const h = readJson(hooksPath);
|
|
1928
|
+
const gateMarker = '/' + PILL_NAME + '/hooks/';
|
|
1929
|
+
if (h.hooks?.preToolUse) h.hooks.preToolUse = h.hooks.preToolUse.filter(e => !e.bash?.includes(gateMarker));
|
|
1930
|
+
if (h.hooks?.postToolUse) h.hooks.postToolUse = h.hooks.postToolUse.filter(e => !e.bash?.includes(gateMarker));
|
|
1931
|
+
writeJson(hooksPath, h);
|
|
1932
|
+
}
|
|
1933
|
+
deactivatePill();
|
|
1934
|
+
if (!silent) process.stdout.write('[mcpill] info: uninstalled ' + PILL_NAME + ' from github-copilot\\n');
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
function installCodex() {
|
|
1938
|
+
const configPath = join(homedir(), '.codex', 'config.toml');
|
|
1939
|
+
mkdirSync(join(homedir(), '.codex'), { recursive: true });
|
|
1940
|
+
let existing = '';
|
|
1941
|
+
if (existsSync(configPath)) {
|
|
1942
|
+
try { existing = readFileSync(configPath, 'utf-8'); } catch {}
|
|
1943
|
+
}
|
|
1944
|
+
// Remove existing entry for this pill
|
|
1945
|
+
const pat = new RegExp('\\\\[mcp_servers\\\\.' + PILL_NAME.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&') + '\\\\][\\\\s\\\\S]*?(?=\\\\[|$)', 'g');
|
|
1946
|
+
existing = existing.replace(pat, '').trimEnd();
|
|
1947
|
+
if (!existing.includes('[mcp_servers]')) existing += '\\n[mcp_servers]';
|
|
1948
|
+
existing += '\\n\\n[mcp_servers.' + PILL_NAME + ']\\ncommand = "node"\\nargs = [' + JSON.stringify(INDEX_PATH) + ']\\n';
|
|
1949
|
+
writeFileSync(configPath, existing.trim() + '\\n');
|
|
1950
|
+
activatePill();
|
|
1951
|
+
if (!silent) process.stdout.write('[mcpill] info: installed ' + PILL_NAME + ' for codex (MCP server only \u2014 hooks not yet supported for codex)\\n');
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
function uninstallCodex() {
|
|
1955
|
+
const configPath = join(homedir(), '.codex', 'config.toml');
|
|
1956
|
+
if (!existsSync(configPath)) return;
|
|
1957
|
+
let content = readFileSync(configPath, 'utf-8');
|
|
1958
|
+
const pat = new RegExp('\\\\[mcp_servers\\\\.' + PILL_NAME.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&') + '\\\\][\\\\s\\\\S]*?(?=\\\\[|$)', 'g');
|
|
1959
|
+
content = content.replace(pat, '').trim() + '\\n';
|
|
1960
|
+
writeFileSync(configPath, content);
|
|
1961
|
+
deactivatePill();
|
|
1962
|
+
if (!silent) process.stdout.write('[mcpill] info: uninstalled ' + PILL_NAME + ' from codex\\n');
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
if (command === 'install') {
|
|
1966
|
+
switch (agent) {
|
|
1967
|
+
case 'claude-code': installClaudeCode(); break;
|
|
1968
|
+
case 'cursor': installCursor(); break;
|
|
1969
|
+
case 'windsurf': installWindsurf(); break;
|
|
1970
|
+
case 'github-copilot': installCopilot(); break;
|
|
1971
|
+
case 'codex': installCodex(); break;
|
|
1972
|
+
}
|
|
1973
|
+
} else {
|
|
1974
|
+
switch (agent) {
|
|
1975
|
+
case 'claude-code': uninstallClaudeCode(); break;
|
|
1976
|
+
case 'cursor': uninstallCursor(); break;
|
|
1977
|
+
case 'windsurf': uninstallWindsurf(); break;
|
|
1978
|
+
case 'github-copilot': uninstallCopilot(); break;
|
|
1979
|
+
case 'codex': uninstallCodex(); break;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
`;
|
|
1983
|
+
function generateStandaloneInstallJs(pillName, version, blockingHooks) {
|
|
1984
|
+
const hooksData = blockingHooks.map((h) => ({
|
|
1985
|
+
required_tool: h.required_tool
|
|
1986
|
+
}));
|
|
1987
|
+
const constants = [
|
|
1988
|
+
`#!/usr/bin/env node`,
|
|
1989
|
+
`import { readFileSync, writeFileSync, mkdirSync, existsSync, rmSync } from 'fs';`,
|
|
1990
|
+
`import { homedir } from 'os';`,
|
|
1991
|
+
`import { join, dirname, resolve } from 'path';`,
|
|
1992
|
+
`import { fileURLToPath } from 'url';`,
|
|
1993
|
+
``,
|
|
1994
|
+
`const __filename = fileURLToPath(import.meta.url);`,
|
|
1995
|
+
`const __dirname = dirname(__filename);`,
|
|
1996
|
+
``,
|
|
1997
|
+
`const PILL_NAME = ${JSON.stringify(pillName)};`,
|
|
1998
|
+
`const PILL_VERSION = ${JSON.stringify(version)};`,
|
|
1999
|
+
`const HAS_BLOCKING_HOOKS = ${blockingHooks.length > 0};`,
|
|
2000
|
+
`const HOOKS = ${JSON.stringify(hooksData, null, 2)};`,
|
|
2001
|
+
`const VALID_AGENTS = ['claude-code', 'cursor', 'windsurf', 'github-copilot', 'codex'];`,
|
|
2002
|
+
``,
|
|
2003
|
+
`const argv = process.argv.slice(2);`,
|
|
2004
|
+
`const command = argv[0];`,
|
|
2005
|
+
`const agentIdx = argv.indexOf('--agent');`,
|
|
2006
|
+
`const agent = agentIdx !== -1 ? argv[agentIdx + 1] : null;`,
|
|
2007
|
+
`const silent = argv.includes('--silent');`,
|
|
2008
|
+
``,
|
|
2009
|
+
`if (!['install', 'uninstall'].includes(command)) {`,
|
|
2010
|
+
` process.stderr.write('[mcpill] error: expected \\'install\\' or \\'uninstall\\', got \\'' + command + '\\'\\n');`,
|
|
2011
|
+
` process.exit(1);`,
|
|
2012
|
+
`}`,
|
|
2013
|
+
`if (!agent) {`,
|
|
2014
|
+
` process.stderr.write('[mcpill] error: --agent <target> is required\\n');`,
|
|
2015
|
+
` process.exit(1);`,
|
|
2016
|
+
`}`,
|
|
2017
|
+
`if (!VALID_AGENTS.includes(agent)) {`,
|
|
2018
|
+
` process.stderr.write('[mcpill] error: unknown agent \\'' + agent + '\\'. Valid targets: ' + VALID_AGENTS.join(', ') + '\\n');`,
|
|
2019
|
+
` process.exit(1);`,
|
|
2020
|
+
`}`,
|
|
2021
|
+
``,
|
|
2022
|
+
`const INDEX_PATH = resolve(__dirname, 'index.js');`,
|
|
2023
|
+
`const GATE_PATH = resolve(__dirname, 'hooks', 'pre-tool-gate.js');`,
|
|
2024
|
+
`const FLAG_PATH = resolve(__dirname, 'hooks', 'post-analysis-flag.js');`,
|
|
2025
|
+
`const MCP_ENTRY = { command: 'node', args: [INDEX_PATH] };`,
|
|
2026
|
+
`const STATE_FILE = join(homedir(), '.' + PILL_NAME, 'state.json');`
|
|
2027
|
+
].join("\n");
|
|
2028
|
+
return constants + "\n" + INSTALL_JS_BODY;
|
|
2029
|
+
}
|
|
2030
|
+
function generateDistPackageJson(pillName, version, opts) {
|
|
2031
|
+
const pkg2 = {
|
|
2032
|
+
name: pillName,
|
|
2033
|
+
version,
|
|
2034
|
+
type: "module",
|
|
2035
|
+
bin: { [pillName]: "install.js" },
|
|
2036
|
+
dependencies: {
|
|
2037
|
+
"mcpster": "*",
|
|
2038
|
+
"mcpill-runtime": "*"
|
|
2039
|
+
}
|
|
2040
|
+
};
|
|
2041
|
+
if (opts.postinstall) {
|
|
2042
|
+
pkg2.scripts = { postinstall: `node install.js --agent claude-code --silent` };
|
|
2043
|
+
}
|
|
2044
|
+
return JSON.stringify(pkg2, null, 2) + "\n";
|
|
2045
|
+
}
|
|
2046
|
+
function emitStandalonePackage(baseDir, pillName, version, toolsJsContent, doc, blockingHooks, opts = {}) {
|
|
2047
|
+
const distDir = join5(baseDir, ".mcpill", "dist", pillName);
|
|
2048
|
+
const hooksDir = join5(distDir, "hooks");
|
|
2049
|
+
mkdirSync3(hooksDir, { recursive: true });
|
|
2050
|
+
const blockingHookTools = blockingHooks.filter((h) => h.required_tool).map((h) => h.required_tool);
|
|
2051
|
+
writeFileSync3(join5(distDir, "package.json"), generateDistPackageJson(pillName, version, opts));
|
|
2052
|
+
writeFileSync3(join5(distDir, "tools.js"), toolsJsContent);
|
|
2053
|
+
writeFileSync3(
|
|
2054
|
+
join5(distDir, "index.js"),
|
|
2055
|
+
generateStandaloneIndexJs(pillName, version, doc.prompts, doc.resources, blockingHookTools)
|
|
2056
|
+
);
|
|
2057
|
+
writeFileSync3(join5(distDir, "install.js"), generateStandaloneInstallJs(pillName, version, blockingHooks));
|
|
2058
|
+
if (blockingHooks.length > 0) {
|
|
2059
|
+
writeFileSync3(join5(hooksDir, "pre-tool-gate.js"), generateStandaloneGateScript(pillName, blockingHooks));
|
|
2060
|
+
writeFileSync3(join5(hooksDir, "post-analysis-flag.js"), generateStandaloneFlagScript(pillName));
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
|
|
1551
2064
|
// src/commands/compile.ts
|
|
1552
2065
|
var zodTypeNameMap = {
|
|
1553
2066
|
ZodString: "string",
|
|
@@ -1597,18 +2110,18 @@ ${r.content}`;
|
|
|
1597
2110
|
}
|
|
1598
2111
|
async function runCompile(opts) {
|
|
1599
2112
|
const baseDir = resolve(opts.dir ?? process.cwd());
|
|
1600
|
-
const mcpillDir =
|
|
1601
|
-
const serverDir =
|
|
2113
|
+
const mcpillDir = join6(baseDir, ".mcpill");
|
|
2114
|
+
const serverDir = join6(mcpillDir, "server");
|
|
1602
2115
|
if (opts.toMd) {
|
|
1603
|
-
const configPath =
|
|
2116
|
+
const configPath = join6(serverDir, "mcpill.config.json");
|
|
1604
2117
|
if (!existsSync3(configPath)) {
|
|
1605
2118
|
throw new Error("No pill found \u2014 run mcpill compile first");
|
|
1606
2119
|
}
|
|
1607
2120
|
const config = await loadConfig(serverDir);
|
|
1608
2121
|
const resources = await loadResources(serverDir);
|
|
1609
|
-
const promptsPath =
|
|
2122
|
+
const promptsPath = join6(serverDir, "prompts.json");
|
|
1610
2123
|
const prompts = JSON.parse(readFileSync4(promptsPath, "utf-8"));
|
|
1611
|
-
const toolsPath =
|
|
2124
|
+
const toolsPath = join6(serverDir, "tools.js");
|
|
1612
2125
|
const handlers = existsSync3(toolsPath) ? extractHandlers(readFileSync4(toolsPath, "utf-8")) : /* @__PURE__ */ new Map();
|
|
1613
2126
|
let tools = [];
|
|
1614
2127
|
if (existsSync3(toolsPath)) {
|
|
@@ -1642,11 +2155,13 @@ async function runCompile(opts) {
|
|
|
1642
2155
|
return;
|
|
1643
2156
|
}
|
|
1644
2157
|
const doc = parseServerDir(mcpillDir);
|
|
1645
|
-
const toolsJsPath =
|
|
2158
|
+
const toolsJsPath = join6(serverDir, "tools.js");
|
|
1646
2159
|
const existing = existsSync3(toolsJsPath) ? extractHandlers(readFileSync4(toolsJsPath, "utf-8")) : /* @__PURE__ */ new Map();
|
|
1647
2160
|
const { doc: mergedDoc, stubbed } = mergeHandlers(doc, existing);
|
|
1648
|
-
const pillMdPath =
|
|
2161
|
+
const pillMdPath = join6(baseDir, "PILL.md");
|
|
1649
2162
|
let pillContent = null;
|
|
2163
|
+
let pillVersion = "0.1.0";
|
|
2164
|
+
let pillPostinstall = false;
|
|
1650
2165
|
if (existsSync3(pillMdPath)) {
|
|
1651
2166
|
pillContent = readFileSync4(pillMdPath, "utf-8");
|
|
1652
2167
|
const serverSection = ("\n" + pillContent).split("\n## ").find((s) => s.startsWith("Server\n"));
|
|
@@ -1656,6 +2171,8 @@ async function runCompile(opts) {
|
|
|
1656
2171
|
if (kv["transport"] === "stdio" || kv["transport"] === "http") {
|
|
1657
2172
|
mergedDoc.config.transport = kv["transport"];
|
|
1658
2173
|
}
|
|
2174
|
+
if (kv["version"]) pillVersion = kv["version"].trim();
|
|
2175
|
+
if (kv["postinstall"]?.trim().toLowerCase() === "true") pillPostinstall = true;
|
|
1659
2176
|
}
|
|
1660
2177
|
}
|
|
1661
2178
|
if (opts.strict && stubbed.length > 0) {
|
|
@@ -1664,18 +2181,21 @@ async function runCompile(opts) {
|
|
|
1664
2181
|
for (const name of stubbed) {
|
|
1665
2182
|
console.warn(`\u26A0 Stub generated for tool: ${name}`);
|
|
1666
2183
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
2184
|
+
mkdirSync4(serverDir, { recursive: true });
|
|
2185
|
+
writeFileSync4(join6(serverDir, "mcpill.config.json"), JSON.stringify(mergedDoc.config, null, 2));
|
|
2186
|
+
writeFileSync4(join6(serverDir, "prompts.json"), JSON.stringify(mergedDoc.prompts, null, 2));
|
|
2187
|
+
writeFileSync4(join6(serverDir, "resources.md"), serializeResourcesMd(mergedDoc.resources));
|
|
2188
|
+
const toolsJsContent = generateToolsJs(mergedDoc.tools);
|
|
2189
|
+
writeFileSync4(toolsJsPath, toolsJsContent);
|
|
1672
2190
|
ensureDeps(serverDir);
|
|
1673
2191
|
console.log(`\u2713 .mcpill/server/ updated from .mcpill/server.md, .mcpill/server/tools/, .mcpill/server/prompts/`);
|
|
1674
2192
|
if (opts.noHooks) {
|
|
1675
|
-
|
|
2193
|
+
writeFileSync4(join6(mcpillDir, ".no-hooks"), "");
|
|
2194
|
+
emitStandalonePackage(baseDir, mergedDoc.config.name, pillVersion, toolsJsContent, mergedDoc, [], { postinstall: false });
|
|
2195
|
+
console.log(`\u2713 .mcpill/dist/${mergedDoc.config.name}/ updated`);
|
|
1676
2196
|
return;
|
|
1677
2197
|
}
|
|
1678
|
-
const agentMdPath = existsSync3(
|
|
2198
|
+
const agentMdPath = existsSync3(join6(baseDir, "AGENT.md")) ? join6(baseDir, "AGENT.md") : existsSync3(join6(mcpillDir, "AGENT.md")) ? join6(mcpillDir, "AGENT.md") : null;
|
|
1679
2199
|
const pillTools = pillContent ? parsePillMdTools(pillContent) : [];
|
|
1680
2200
|
const agentTools = agentMdPath ? parseAgentMdTools(readFileSync4(agentMdPath, "utf-8")) : [];
|
|
1681
2201
|
const toolMap = new Map([...pillTools, ...agentTools].map((t) => [t.name, t]));
|
|
@@ -1686,7 +2206,7 @@ async function runCompile(opts) {
|
|
|
1686
2206
|
console.log(`\u2713 .claude/settings.json updated with PreToolUse hook`);
|
|
1687
2207
|
}
|
|
1688
2208
|
const pillHooks = pillContent ? parsePillMdHooks(pillContent) : [];
|
|
1689
|
-
const hooksDir =
|
|
2209
|
+
const hooksDir = join6(mcpillDir, "server", "hooks");
|
|
1690
2210
|
const fileHooks = [];
|
|
1691
2211
|
if (existsSync3(hooksDir)) {
|
|
1692
2212
|
let hookFiles = [];
|
|
@@ -1695,13 +2215,29 @@ async function runCompile(opts) {
|
|
|
1695
2215
|
} catch {
|
|
1696
2216
|
}
|
|
1697
2217
|
for (const file of hookFiles) {
|
|
1698
|
-
const entry = parseHookFile(readFileSync4(
|
|
2218
|
+
const entry = parseHookFile(readFileSync4(join6(hooksDir, file), "utf-8"));
|
|
1699
2219
|
if (entry) fileHooks.push(entry);
|
|
1700
2220
|
}
|
|
1701
2221
|
}
|
|
1702
2222
|
const allUserHooks = [...pillHooks, ...fileHooks];
|
|
1703
|
-
const
|
|
1704
|
-
|
|
2223
|
+
const normalizedUserHooks = allUserHooks.map((h) => {
|
|
2224
|
+
if (h.event !== "UserPromptSubmit") return h;
|
|
2225
|
+
const norm = { ...h };
|
|
2226
|
+
if (norm.blocking) {
|
|
2227
|
+
console.warn(`warn: UserPromptSubmit hooks do not support blocking \u2014 field ignored`);
|
|
2228
|
+
delete norm.blocking;
|
|
2229
|
+
delete norm.required_tool;
|
|
2230
|
+
delete norm.gate_window_seconds;
|
|
2231
|
+
delete norm.instructions;
|
|
2232
|
+
}
|
|
2233
|
+
if (norm.matcher !== void 0) {
|
|
2234
|
+
console.warn(`warn: UserPromptSubmit hooks do not support matcher \u2014 field ignored`);
|
|
2235
|
+
delete norm.matcher;
|
|
2236
|
+
}
|
|
2237
|
+
return norm;
|
|
2238
|
+
});
|
|
2239
|
+
const blockingHooks = normalizedUserHooks.filter((h) => h.blocking);
|
|
2240
|
+
const regularHooks = normalizedUserHooks.filter((h) => !h.blocking);
|
|
1705
2241
|
for (const hook of blockingHooks) {
|
|
1706
2242
|
if (!hook.name) throw new Error(`blocking hook is missing a name`);
|
|
1707
2243
|
if (!hook.required_tool) throw new Error(`blocking hook '${hook.name}' is missing required_tool`);
|
|
@@ -1721,6 +2257,8 @@ async function runCompile(opts) {
|
|
|
1721
2257
|
mergeUserHooksIntoSettings(baseDir, generatedEntries);
|
|
1722
2258
|
console.log(`\u2713 .claude/settings.json updated with ${blockingHooks.length} blocking hook(s)`);
|
|
1723
2259
|
}
|
|
2260
|
+
emitStandalonePackage(baseDir, mergedDoc.config.name, pillVersion, toolsJsContent, mergedDoc, blockingHooks, { postinstall: pillPostinstall });
|
|
2261
|
+
console.log(`\u2713 .mcpill/dist/${mergedDoc.config.name}/ updated`);
|
|
1724
2262
|
}
|
|
1725
2263
|
|
|
1726
2264
|
// src/commands/pack.ts
|
|
@@ -1769,7 +2307,7 @@ async function runPublish(dir, access) {
|
|
|
1769
2307
|
// src/cli.ts
|
|
1770
2308
|
var pkgDir = dirname(fileURLToPath(import.meta.url));
|
|
1771
2309
|
var pkg = JSON.parse(
|
|
1772
|
-
readFileSync5(
|
|
2310
|
+
readFileSync5(join7(pkgDir, "../package.json"), "utf-8")
|
|
1773
2311
|
);
|
|
1774
2312
|
var program = new Command();
|
|
1775
2313
|
program.name("mcpill").version(pkg.version);
|