@contextstream/mcp-server 0.4.66 → 0.4.67
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 +157 -4
- package/dist/hooks/auto-rules.js +357 -14
- package/dist/hooks/runner.js +44 -2
- package/dist/hooks/session-init.js +34 -1
- package/dist/index.js +633 -65
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1563,10 +1563,12 @@ var init_files = __esm({
|
|
|
1563
1563
|
// src/hooks-config.ts
|
|
1564
1564
|
var hooks_config_exports = {};
|
|
1565
1565
|
__export(hooks_config_exports, {
|
|
1566
|
+
CLAUDE_ENFORCEMENT_CRITICAL_HOOKS: () => CLAUDE_ENFORCEMENT_CRITICAL_HOOKS,
|
|
1566
1567
|
CLINE_POSTTOOLUSE_HOOK_SCRIPT: () => CLINE_POSTTOOLUSE_HOOK_SCRIPT,
|
|
1567
1568
|
CLINE_PRETOOLUSE_HOOK_SCRIPT: () => CLINE_PRETOOLUSE_HOOK_SCRIPT,
|
|
1568
1569
|
CLINE_USER_PROMPT_HOOK_SCRIPT: () => CLINE_USER_PROMPT_HOOK_SCRIPT,
|
|
1569
1570
|
CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT: () => CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT,
|
|
1571
|
+
CURSOR_ENFORCEMENT_CRITICAL_HOOKS: () => CURSOR_ENFORCEMENT_CRITICAL_HOOKS,
|
|
1570
1572
|
CURSOR_PRETOOLUSE_HOOK_SCRIPT: () => CURSOR_PRETOOLUSE_HOOK_SCRIPT,
|
|
1571
1573
|
MEDIA_AWARE_HOOK_SCRIPT: () => MEDIA_AWARE_HOOK_SCRIPT,
|
|
1572
1574
|
PRECOMPACT_HOOK_SCRIPT: () => PRECOMPACT_HOOK_SCRIPT,
|
|
@@ -1585,6 +1587,7 @@ __export(hooks_config_exports, {
|
|
|
1585
1587
|
getIndexStatusPath: () => getIndexStatusPath,
|
|
1586
1588
|
getKiloCodeHooksDir: () => getKiloCodeHooksDir,
|
|
1587
1589
|
getRooCodeHooksDir: () => getRooCodeHooksDir,
|
|
1590
|
+
getWindsurfHooksConfigPath: () => getWindsurfHooksConfigPath,
|
|
1588
1591
|
installAllEditorHooks: () => installAllEditorHooks,
|
|
1589
1592
|
installClaudeCodeHooks: () => installClaudeCodeHooks,
|
|
1590
1593
|
installClineHookScripts: () => installClineHookScripts,
|
|
@@ -1593,15 +1596,18 @@ __export(hooks_config_exports, {
|
|
|
1593
1596
|
installHookScripts: () => installHookScripts,
|
|
1594
1597
|
installKiloCodeHookScripts: () => installKiloCodeHookScripts,
|
|
1595
1598
|
installRooCodeHookScripts: () => installRooCodeHookScripts,
|
|
1599
|
+
installWindsurfHookScripts: () => installWindsurfHookScripts,
|
|
1596
1600
|
markProjectIndexed: () => markProjectIndexed,
|
|
1597
1601
|
mergeHooksIntoSettings: () => mergeHooksIntoSettings,
|
|
1598
1602
|
readClaudeSettings: () => readClaudeSettings,
|
|
1599
1603
|
readCursorHooksConfig: () => readCursorHooksConfig,
|
|
1600
1604
|
readIndexStatus: () => readIndexStatus,
|
|
1605
|
+
readWindsurfHooksConfig: () => readWindsurfHooksConfig,
|
|
1601
1606
|
unmarkProjectIndexed: () => unmarkProjectIndexed,
|
|
1602
1607
|
writeClaudeSettings: () => writeClaudeSettings,
|
|
1603
1608
|
writeCursorHooksConfig: () => writeCursorHooksConfig,
|
|
1604
|
-
writeIndexStatus: () => writeIndexStatus
|
|
1609
|
+
writeIndexStatus: () => writeIndexStatus,
|
|
1610
|
+
writeWindsurfHooksConfig: () => writeWindsurfHooksConfig
|
|
1605
1611
|
});
|
|
1606
1612
|
import * as fs4 from "node:fs/promises";
|
|
1607
1613
|
import * as fsSync from "node:fs";
|
|
@@ -1711,6 +1717,18 @@ function buildHooksConfig(options) {
|
|
|
1711
1717
|
]
|
|
1712
1718
|
}
|
|
1713
1719
|
];
|
|
1720
|
+
config.PostCompact = [
|
|
1721
|
+
{
|
|
1722
|
+
matcher: "*",
|
|
1723
|
+
hooks: [
|
|
1724
|
+
{
|
|
1725
|
+
type: "command",
|
|
1726
|
+
command: getHookCommand("post-compact"),
|
|
1727
|
+
timeout: 15
|
|
1728
|
+
}
|
|
1729
|
+
]
|
|
1730
|
+
}
|
|
1731
|
+
];
|
|
1714
1732
|
}
|
|
1715
1733
|
if (options?.includeSessionInit !== false) {
|
|
1716
1734
|
config.SessionStart = [
|
|
@@ -1725,6 +1743,102 @@ function buildHooksConfig(options) {
|
|
|
1725
1743
|
]
|
|
1726
1744
|
}
|
|
1727
1745
|
];
|
|
1746
|
+
config.InstructionsLoaded = [
|
|
1747
|
+
{
|
|
1748
|
+
matcher: "*",
|
|
1749
|
+
hooks: [
|
|
1750
|
+
{
|
|
1751
|
+
type: "command",
|
|
1752
|
+
command: getHookCommand("instructions-loaded"),
|
|
1753
|
+
timeout: 10
|
|
1754
|
+
}
|
|
1755
|
+
]
|
|
1756
|
+
}
|
|
1757
|
+
];
|
|
1758
|
+
config.ConfigChange = [
|
|
1759
|
+
{
|
|
1760
|
+
matcher: "*",
|
|
1761
|
+
hooks: [
|
|
1762
|
+
{
|
|
1763
|
+
type: "command",
|
|
1764
|
+
command: getHookCommand("config-change"),
|
|
1765
|
+
timeout: 10
|
|
1766
|
+
}
|
|
1767
|
+
]
|
|
1768
|
+
}
|
|
1769
|
+
];
|
|
1770
|
+
config.CwdChanged = [
|
|
1771
|
+
{
|
|
1772
|
+
matcher: "*",
|
|
1773
|
+
hooks: [
|
|
1774
|
+
{
|
|
1775
|
+
type: "command",
|
|
1776
|
+
command: getHookCommand("cwd-changed"),
|
|
1777
|
+
timeout: 10
|
|
1778
|
+
}
|
|
1779
|
+
]
|
|
1780
|
+
}
|
|
1781
|
+
];
|
|
1782
|
+
config.FileChanged = [
|
|
1783
|
+
{
|
|
1784
|
+
matcher: ".*",
|
|
1785
|
+
hooks: [
|
|
1786
|
+
{
|
|
1787
|
+
type: "command",
|
|
1788
|
+
command: getHookCommand("file-changed"),
|
|
1789
|
+
timeout: 10
|
|
1790
|
+
}
|
|
1791
|
+
]
|
|
1792
|
+
}
|
|
1793
|
+
];
|
|
1794
|
+
config.WorktreeCreate = [
|
|
1795
|
+
{
|
|
1796
|
+
matcher: "*",
|
|
1797
|
+
hooks: [
|
|
1798
|
+
{
|
|
1799
|
+
type: "command",
|
|
1800
|
+
command: getHookCommand("worktree-create"),
|
|
1801
|
+
timeout: 15
|
|
1802
|
+
}
|
|
1803
|
+
]
|
|
1804
|
+
}
|
|
1805
|
+
];
|
|
1806
|
+
config.WorktreeRemove = [
|
|
1807
|
+
{
|
|
1808
|
+
matcher: "*",
|
|
1809
|
+
hooks: [
|
|
1810
|
+
{
|
|
1811
|
+
type: "command",
|
|
1812
|
+
command: getHookCommand("worktree-remove"),
|
|
1813
|
+
timeout: 15
|
|
1814
|
+
}
|
|
1815
|
+
]
|
|
1816
|
+
}
|
|
1817
|
+
];
|
|
1818
|
+
config.Elicitation = [
|
|
1819
|
+
{
|
|
1820
|
+
matcher: ".*",
|
|
1821
|
+
hooks: [
|
|
1822
|
+
{
|
|
1823
|
+
type: "command",
|
|
1824
|
+
command: getHookCommand("elicitation"),
|
|
1825
|
+
timeout: 10
|
|
1826
|
+
}
|
|
1827
|
+
]
|
|
1828
|
+
}
|
|
1829
|
+
];
|
|
1830
|
+
config.ElicitationResult = [
|
|
1831
|
+
{
|
|
1832
|
+
matcher: ".*",
|
|
1833
|
+
hooks: [
|
|
1834
|
+
{
|
|
1835
|
+
type: "command",
|
|
1836
|
+
command: getHookCommand("elicitation-result"),
|
|
1837
|
+
timeout: 10
|
|
1838
|
+
}
|
|
1839
|
+
]
|
|
1840
|
+
}
|
|
1841
|
+
];
|
|
1728
1842
|
}
|
|
1729
1843
|
if (options?.includeSessionEnd !== false) {
|
|
1730
1844
|
config.Stop = [
|
|
@@ -1751,6 +1865,18 @@ function buildHooksConfig(options) {
|
|
|
1751
1865
|
]
|
|
1752
1866
|
}
|
|
1753
1867
|
];
|
|
1868
|
+
config.StopFailure = [
|
|
1869
|
+
{
|
|
1870
|
+
matcher: "*",
|
|
1871
|
+
hooks: [
|
|
1872
|
+
{
|
|
1873
|
+
type: "command",
|
|
1874
|
+
command: getHookCommand("stop-failure"),
|
|
1875
|
+
timeout: 10
|
|
1876
|
+
}
|
|
1877
|
+
]
|
|
1878
|
+
}
|
|
1879
|
+
];
|
|
1754
1880
|
}
|
|
1755
1881
|
const postToolUseHooks = [];
|
|
1756
1882
|
if (options?.includePostWrite !== false) {
|
|
@@ -1846,6 +1972,12 @@ function buildHooksConfig(options) {
|
|
|
1846
1972
|
hooks: [{ type: "command", command: getHookCommand("subagent-stop"), timeout: 15 }]
|
|
1847
1973
|
}
|
|
1848
1974
|
];
|
|
1975
|
+
config.TaskCreated = [
|
|
1976
|
+
{
|
|
1977
|
+
matcher: "*",
|
|
1978
|
+
hooks: [{ type: "command", command: getHookCommand("task-created"), timeout: 10 }]
|
|
1979
|
+
}
|
|
1980
|
+
];
|
|
1849
1981
|
config.TaskCompleted = [
|
|
1850
1982
|
{
|
|
1851
1983
|
matcher: "*",
|
|
@@ -1926,11 +2058,21 @@ async function installClaudeCodeHooks(options) {
|
|
|
1926
2058
|
getHookCommand("user-prompt-submit"),
|
|
1927
2059
|
getHookCommand("on-save-intent"),
|
|
1928
2060
|
getHookCommand("session-start"),
|
|
2061
|
+
getHookCommand("instructions-loaded"),
|
|
2062
|
+
getHookCommand("config-change"),
|
|
2063
|
+
getHookCommand("cwd-changed"),
|
|
2064
|
+
getHookCommand("file-changed"),
|
|
2065
|
+
getHookCommand("worktree-create"),
|
|
2066
|
+
getHookCommand("worktree-remove"),
|
|
2067
|
+
getHookCommand("elicitation"),
|
|
2068
|
+
getHookCommand("elicitation-result"),
|
|
1929
2069
|
getHookCommand("stop"),
|
|
2070
|
+
getHookCommand("stop-failure"),
|
|
1930
2071
|
getHookCommand("session-end"),
|
|
1931
2072
|
getHookCommand("post-tool-use-failure"),
|
|
1932
2073
|
getHookCommand("subagent-start"),
|
|
1933
2074
|
getHookCommand("subagent-stop"),
|
|
2075
|
+
getHookCommand("task-created"),
|
|
1934
2076
|
getHookCommand("task-completed"),
|
|
1935
2077
|
getHookCommand("teammate-idle"),
|
|
1936
2078
|
getHookCommand("notification"),
|
|
@@ -1938,6 +2080,7 @@ async function installClaudeCodeHooks(options) {
|
|
|
1938
2080
|
);
|
|
1939
2081
|
if (options.includePreCompact !== false) {
|
|
1940
2082
|
result.scripts.push(getHookCommand("pre-compact"));
|
|
2083
|
+
result.scripts.push(getHookCommand("post-compact"));
|
|
1941
2084
|
}
|
|
1942
2085
|
if (options.includeMediaAware === true) {
|
|
1943
2086
|
result.scripts.push(getHookCommand("media-aware"));
|
|
@@ -2232,6 +2375,15 @@ function getCursorHooksDir(scope, projectPath) {
|
|
|
2232
2375
|
}
|
|
2233
2376
|
return path4.join(projectPath, ".cursor", "hooks");
|
|
2234
2377
|
}
|
|
2378
|
+
function getWindsurfHooksConfigPath(scope, projectPath) {
|
|
2379
|
+
if (scope === "global") {
|
|
2380
|
+
return path4.join(homedir3(), ".codeium", "windsurf", "hooks.json");
|
|
2381
|
+
}
|
|
2382
|
+
if (!projectPath) {
|
|
2383
|
+
throw new Error("projectPath required for project scope");
|
|
2384
|
+
}
|
|
2385
|
+
return path4.join(projectPath, ".windsurf", "hooks.json");
|
|
2386
|
+
}
|
|
2235
2387
|
async function readCursorHooksConfig(scope, projectPath) {
|
|
2236
2388
|
const configPath = getCursorHooksConfigPath(scope, projectPath);
|
|
2237
2389
|
try {
|
|
@@ -2258,17 +2410,31 @@ async function installCursorHookScripts(options) {
|
|
|
2258
2410
|
return !hook.command?.includes("contextstream");
|
|
2259
2411
|
});
|
|
2260
2412
|
};
|
|
2261
|
-
const
|
|
2262
|
-
const
|
|
2413
|
+
const cleanedHooks = {};
|
|
2414
|
+
for (const [eventName, entries] of Object.entries(existingConfig.hooks || {})) {
|
|
2415
|
+
cleanedHooks[eventName] = filterContextStreamHooks(entries);
|
|
2416
|
+
}
|
|
2263
2417
|
const preToolUseCommand = getHookCommand("pre-tool-use");
|
|
2264
2418
|
const userPromptCommand = getHookCommand("user-prompt-submit");
|
|
2265
2419
|
const saveIntentCommand = getHookCommand("on-save-intent");
|
|
2420
|
+
const postToolUseCommand = getHookCommand("post-tool-use");
|
|
2421
|
+
const postToolUseFailureCommand = getHookCommand("post-tool-use-failure");
|
|
2422
|
+
const preCompactCommand = getHookCommand("pre-compact");
|
|
2423
|
+
const sessionStartCommand = getHookCommand("session-start");
|
|
2424
|
+
const sessionEndCommand = getHookCommand("session-end");
|
|
2425
|
+
const stopCommand = getHookCommand("stop");
|
|
2426
|
+
const notificationCommand = getHookCommand("notification");
|
|
2427
|
+
const permissionRequestCommand = getHookCommand("permission-request");
|
|
2428
|
+
const subagentStartCommand = getHookCommand("subagent-start");
|
|
2429
|
+
const subagentStopCommand = getHookCommand("subagent-stop");
|
|
2430
|
+
const taskCompletedCommand = getHookCommand("task-completed");
|
|
2431
|
+
const teammateIdleCommand = getHookCommand("teammate-idle");
|
|
2266
2432
|
const config = {
|
|
2267
2433
|
version: 1,
|
|
2268
2434
|
hooks: {
|
|
2269
|
-
...
|
|
2435
|
+
...cleanedHooks,
|
|
2270
2436
|
preToolUse: [
|
|
2271
|
-
...
|
|
2437
|
+
...cleanedHooks.preToolUse || [],
|
|
2272
2438
|
{
|
|
2273
2439
|
command: preToolUseCommand,
|
|
2274
2440
|
type: "command",
|
|
@@ -2276,7 +2442,7 @@ async function installCursorHookScripts(options) {
|
|
|
2276
2442
|
}
|
|
2277
2443
|
],
|
|
2278
2444
|
beforeSubmitPrompt: [
|
|
2279
|
-
...
|
|
2445
|
+
...cleanedHooks.beforeSubmitPrompt || [],
|
|
2280
2446
|
{
|
|
2281
2447
|
command: userPromptCommand,
|
|
2282
2448
|
type: "command",
|
|
@@ -2287,6 +2453,70 @@ async function installCursorHookScripts(options) {
|
|
|
2287
2453
|
type: "command",
|
|
2288
2454
|
timeout: 5
|
|
2289
2455
|
}
|
|
2456
|
+
],
|
|
2457
|
+
beforeMCPExecution: [
|
|
2458
|
+
...cleanedHooks.beforeMCPExecution || [],
|
|
2459
|
+
{ command: preToolUseCommand, type: "command", timeout: 5 }
|
|
2460
|
+
],
|
|
2461
|
+
afterMCPExecution: [
|
|
2462
|
+
...cleanedHooks.afterMCPExecution || [],
|
|
2463
|
+
{ command: postToolUseCommand, type: "command", timeout: 10 }
|
|
2464
|
+
],
|
|
2465
|
+
beforeShellExecution: [
|
|
2466
|
+
...cleanedHooks.beforeShellExecution || [],
|
|
2467
|
+
{ command: preToolUseCommand, type: "command", timeout: 5 }
|
|
2468
|
+
],
|
|
2469
|
+
afterShellExecution: [
|
|
2470
|
+
...cleanedHooks.afterShellExecution || [],
|
|
2471
|
+
{ command: postToolUseFailureCommand, type: "command", timeout: 10 }
|
|
2472
|
+
],
|
|
2473
|
+
beforeReadFile: [
|
|
2474
|
+
...cleanedHooks.beforeReadFile || [],
|
|
2475
|
+
{ command: preToolUseCommand, type: "command", timeout: 5 }
|
|
2476
|
+
],
|
|
2477
|
+
afterFileEdit: [
|
|
2478
|
+
...cleanedHooks.afterFileEdit || [],
|
|
2479
|
+
{ command: postToolUseCommand, type: "command", timeout: 10 }
|
|
2480
|
+
],
|
|
2481
|
+
preCompact: [
|
|
2482
|
+
...cleanedHooks.preCompact || [],
|
|
2483
|
+
{ command: preCompactCommand, type: "command", timeout: 15 }
|
|
2484
|
+
],
|
|
2485
|
+
sessionStart: [
|
|
2486
|
+
...cleanedHooks.sessionStart || [],
|
|
2487
|
+
{ command: sessionStartCommand, type: "command", timeout: 15 }
|
|
2488
|
+
],
|
|
2489
|
+
sessionEnd: [
|
|
2490
|
+
...cleanedHooks.sessionEnd || [],
|
|
2491
|
+
{ command: sessionEndCommand, type: "command", timeout: 10 }
|
|
2492
|
+
],
|
|
2493
|
+
stop: [
|
|
2494
|
+
...cleanedHooks.stop || [],
|
|
2495
|
+
{ command: stopCommand, type: "command", timeout: 15 }
|
|
2496
|
+
],
|
|
2497
|
+
subagentStart: [
|
|
2498
|
+
...cleanedHooks.subagentStart || [],
|
|
2499
|
+
{ command: subagentStartCommand, type: "command", timeout: 10 }
|
|
2500
|
+
],
|
|
2501
|
+
subagentStop: [
|
|
2502
|
+
...cleanedHooks.subagentStop || [],
|
|
2503
|
+
{ command: subagentStopCommand, type: "command", timeout: 10 }
|
|
2504
|
+
],
|
|
2505
|
+
taskCompleted: [
|
|
2506
|
+
...cleanedHooks.taskCompleted || [],
|
|
2507
|
+
{ command: taskCompletedCommand, type: "command", timeout: 10 }
|
|
2508
|
+
],
|
|
2509
|
+
teammateIdle: [
|
|
2510
|
+
...cleanedHooks.teammateIdle || [],
|
|
2511
|
+
{ command: teammateIdleCommand, type: "command", timeout: 10 }
|
|
2512
|
+
],
|
|
2513
|
+
notification: [
|
|
2514
|
+
...cleanedHooks.notification || [],
|
|
2515
|
+
{ command: notificationCommand, type: "command", timeout: 10 }
|
|
2516
|
+
],
|
|
2517
|
+
permissionRequest: [
|
|
2518
|
+
...cleanedHooks.permissionRequest || [],
|
|
2519
|
+
{ command: permissionRequestCommand, type: "command", timeout: 10 }
|
|
2290
2520
|
]
|
|
2291
2521
|
}
|
|
2292
2522
|
};
|
|
@@ -2298,6 +2528,83 @@ async function installCursorHookScripts(options) {
|
|
|
2298
2528
|
config: configPath
|
|
2299
2529
|
};
|
|
2300
2530
|
}
|
|
2531
|
+
async function readWindsurfHooksConfig(scope, projectPath) {
|
|
2532
|
+
const configPath = getWindsurfHooksConfigPath(scope, projectPath);
|
|
2533
|
+
try {
|
|
2534
|
+
const content = await fs4.readFile(configPath, "utf-8");
|
|
2535
|
+
return JSON.parse(content);
|
|
2536
|
+
} catch {
|
|
2537
|
+
return { hooks: {} };
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
async function writeWindsurfHooksConfig(config, scope, projectPath) {
|
|
2541
|
+
const configPath = getWindsurfHooksConfigPath(scope, projectPath);
|
|
2542
|
+
const dir = path4.dirname(configPath);
|
|
2543
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
2544
|
+
await fs4.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
2545
|
+
}
|
|
2546
|
+
async function installWindsurfHookScripts(options) {
|
|
2547
|
+
const existingConfig = await readWindsurfHooksConfig(options.scope, options.projectPath);
|
|
2548
|
+
const filterContextStreamHooks = (hooks) => {
|
|
2549
|
+
if (!hooks) return [];
|
|
2550
|
+
return hooks.filter((h) => {
|
|
2551
|
+
const hook = h;
|
|
2552
|
+
return !hook.command?.toLowerCase().includes("contextstream");
|
|
2553
|
+
});
|
|
2554
|
+
};
|
|
2555
|
+
const cleanedHooks = {};
|
|
2556
|
+
for (const [eventName, entries] of Object.entries(existingConfig.hooks || {})) {
|
|
2557
|
+
cleanedHooks[eventName] = filterContextStreamHooks(entries);
|
|
2558
|
+
}
|
|
2559
|
+
const preToolUseCommand = getHookCommand("pre-tool-use");
|
|
2560
|
+
const userPromptCommand = getHookCommand("user-prompt-submit");
|
|
2561
|
+
const postToolUseCommand = getHookCommand("post-tool-use");
|
|
2562
|
+
const sessionEndCommand = getHookCommand("session-end");
|
|
2563
|
+
const config = {
|
|
2564
|
+
hooks: {
|
|
2565
|
+
...cleanedHooks,
|
|
2566
|
+
pre_mcp_tool_use: [
|
|
2567
|
+
...cleanedHooks.pre_mcp_tool_use || [],
|
|
2568
|
+
{ command: preToolUseCommand, show_output: true }
|
|
2569
|
+
],
|
|
2570
|
+
pre_user_prompt: [
|
|
2571
|
+
...cleanedHooks.pre_user_prompt || [],
|
|
2572
|
+
{ command: userPromptCommand }
|
|
2573
|
+
],
|
|
2574
|
+
pre_read_code: [
|
|
2575
|
+
...cleanedHooks.pre_read_code || [],
|
|
2576
|
+
{ command: preToolUseCommand, show_output: true }
|
|
2577
|
+
],
|
|
2578
|
+
pre_write_code: [
|
|
2579
|
+
...cleanedHooks.pre_write_code || [],
|
|
2580
|
+
{ command: preToolUseCommand, show_output: true }
|
|
2581
|
+
],
|
|
2582
|
+
pre_run_command: [
|
|
2583
|
+
...cleanedHooks.pre_run_command || [],
|
|
2584
|
+
{ command: preToolUseCommand, show_output: true }
|
|
2585
|
+
],
|
|
2586
|
+
post_write_code: [
|
|
2587
|
+
...cleanedHooks.post_write_code || [],
|
|
2588
|
+
{ command: postToolUseCommand, show_output: false }
|
|
2589
|
+
],
|
|
2590
|
+
post_mcp_tool_use: [
|
|
2591
|
+
...cleanedHooks.post_mcp_tool_use || [],
|
|
2592
|
+
{ command: postToolUseCommand, show_output: false }
|
|
2593
|
+
],
|
|
2594
|
+
post_cascade_response_with_transcript: [
|
|
2595
|
+
...cleanedHooks.post_cascade_response_with_transcript || [],
|
|
2596
|
+
{ command: sessionEndCommand }
|
|
2597
|
+
]
|
|
2598
|
+
}
|
|
2599
|
+
};
|
|
2600
|
+
await writeWindsurfHooksConfig(config, options.scope, options.projectPath);
|
|
2601
|
+
const configPath = getWindsurfHooksConfigPath(options.scope, options.projectPath);
|
|
2602
|
+
return {
|
|
2603
|
+
preMcpToolUse: preToolUseCommand,
|
|
2604
|
+
preUserPrompt: userPromptCommand,
|
|
2605
|
+
config: configPath
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2301
2608
|
async function installEditorHooks(options) {
|
|
2302
2609
|
const { editor, scope, projectPath, includePreCompact, includePostWrite } = options;
|
|
2303
2610
|
switch (editor) {
|
|
@@ -2357,12 +2664,20 @@ async function installEditorHooks(options) {
|
|
|
2357
2664
|
hooksDir: getCursorHooksDir(scope, projectPath)
|
|
2358
2665
|
};
|
|
2359
2666
|
}
|
|
2667
|
+
case "windsurf": {
|
|
2668
|
+
const scripts = await installWindsurfHookScripts({ scope, projectPath });
|
|
2669
|
+
return {
|
|
2670
|
+
editor: "windsurf",
|
|
2671
|
+
installed: [scripts.preMcpToolUse, scripts.preUserPrompt, scripts.config],
|
|
2672
|
+
hooksDir: path4.dirname(scripts.config)
|
|
2673
|
+
};
|
|
2674
|
+
}
|
|
2360
2675
|
default:
|
|
2361
2676
|
throw new Error(`Unsupported editor: ${editor}`);
|
|
2362
2677
|
}
|
|
2363
2678
|
}
|
|
2364
2679
|
async function installAllEditorHooks(options) {
|
|
2365
|
-
const editors = options.editors || ["claude", "cline", "roo", "kilo", "cursor"];
|
|
2680
|
+
const editors = options.editors || ["claude", "cline", "roo", "kilo", "cursor", "windsurf"];
|
|
2366
2681
|
const results = [];
|
|
2367
2682
|
for (const editor of editors) {
|
|
2368
2683
|
try {
|
|
@@ -2390,8 +2705,9 @@ ContextStream can install hooks for multiple AI code editors to enforce ContextS
|
|
|
2390
2705
|
|
|
2391
2706
|
| Editor | Hooks Location | Hook Types |
|
|
2392
2707
|
|--------|---------------|------------|
|
|
2393
|
-
| **Claude Code** | \`~/.claude/hooks/\` | PreToolUse, UserPromptSubmit,
|
|
2394
|
-
| **Cursor** | \`~/.cursor/hooks/\` | preToolUse,
|
|
2708
|
+
| **Claude Code** | \`~/.claude/hooks/\` | PreToolUse, UserPromptSubmit, SessionStart, Pre/PostCompact, PostToolUse |
|
|
2709
|
+
| **Cursor** | \`~/.cursor/hooks/\` | preToolUse, beforeSubmitPrompt, before/after MCP+Shell, beforeReadFile, afterFileEdit |
|
|
2710
|
+
| **Windsurf** | \`~/.codeium/windsurf/hooks.json\` | pre_mcp_tool_use, pre_user_prompt, pre/post code + command hooks |
|
|
2395
2711
|
| **Cline** | \`~/Documents/Cline/Rules/Hooks/\` | PreToolUse, UserPromptSubmit |
|
|
2396
2712
|
| **Roo Code** | \`~/.roo/hooks/\` | PreToolUse, UserPromptSubmit |
|
|
2397
2713
|
| **Kilo Code** | \`~/.kilocode/hooks/\` | PreToolUse, UserPromptSubmit |
|
|
@@ -2402,9 +2718,19 @@ ${generateHooksDocumentation()}
|
|
|
2402
2718
|
|
|
2403
2719
|
### Cursor Hooks
|
|
2404
2720
|
|
|
2405
|
-
Cursor uses a \`hooks.json\` configuration file:
|
|
2406
|
-
- **preToolUse**:
|
|
2407
|
-
- **beforeSubmitPrompt**:
|
|
2721
|
+
Cursor uses a \`hooks.json\` configuration file with enforcement + lifecycle hooks:
|
|
2722
|
+
- **preToolUse / beforeMCPExecution / beforeShellExecution / beforeReadFile**: blocks or redirects non-ContextStream-first flows
|
|
2723
|
+
- **beforeSubmitPrompt**: injects ContextStream rules reminder + save-intent capture
|
|
2724
|
+
- **afterMCPExecution / afterShellExecution / afterFileEdit**: post-action bookkeeping/index updates
|
|
2725
|
+
- **sessionStart / preCompact / stop / sessionEnd**: lifecycle persistence and reliability
|
|
2726
|
+
|
|
2727
|
+
### Windsurf Hooks
|
|
2728
|
+
|
|
2729
|
+
Windsurf uses a \`hooks.json\` configuration file:
|
|
2730
|
+
- **pre_mcp_tool_use / pre_read_code / pre_write_code / pre_run_command**: pre-action gating and enforcement
|
|
2731
|
+
- **pre_user_prompt**: injects ContextStream-first guidance each prompt
|
|
2732
|
+
- **post_write_code / post_mcp_tool_use**: post-action indexing bookkeeping
|
|
2733
|
+
- **post_cascade_response_with_transcript**: end-of-response/session capture
|
|
2408
2734
|
|
|
2409
2735
|
#### Output Format
|
|
2410
2736
|
\`\`\`json
|
|
@@ -2434,7 +2760,7 @@ Hooks are executable scripts named after the hook type (no extension).
|
|
|
2434
2760
|
|
|
2435
2761
|
### Installation
|
|
2436
2762
|
|
|
2437
|
-
Use \`generate_rules(install_hooks=true, editors=["claude", "cursor", "cline", "roo", "kilo"])\` to install hooks for specific editors, or omit \`editors\` to install for all.
|
|
2763
|
+
Use \`generate_rules(install_hooks=true, editors=["claude", "cursor", "windsurf", "cline", "roo", "kilo"])\` to install hooks for specific editors, or omit \`editors\` to install for all.
|
|
2438
2764
|
|
|
2439
2765
|
### Disabling Hooks
|
|
2440
2766
|
|
|
@@ -2443,7 +2769,7 @@ Set environment variables:
|
|
|
2443
2769
|
- \`CONTEXTSTREAM_REMINDER_ENABLED=false\` - Disable UserPromptSubmit reminders
|
|
2444
2770
|
`.trim();
|
|
2445
2771
|
}
|
|
2446
|
-
var PRETOOLUSE_HOOK_SCRIPT, USER_PROMPT_HOOK_SCRIPT, MEDIA_AWARE_HOOK_SCRIPT, PRECOMPACT_HOOK_SCRIPT, CLINE_PRETOOLUSE_HOOK_SCRIPT, CLINE_USER_PROMPT_HOOK_SCRIPT, CLINE_POSTTOOLUSE_HOOK_SCRIPT, CURSOR_PRETOOLUSE_HOOK_SCRIPT, CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT;
|
|
2772
|
+
var PRETOOLUSE_HOOK_SCRIPT, USER_PROMPT_HOOK_SCRIPT, MEDIA_AWARE_HOOK_SCRIPT, PRECOMPACT_HOOK_SCRIPT, CLAUDE_ENFORCEMENT_CRITICAL_HOOKS, CURSOR_ENFORCEMENT_CRITICAL_HOOKS, CLINE_PRETOOLUSE_HOOK_SCRIPT, CLINE_USER_PROMPT_HOOK_SCRIPT, CLINE_POSTTOOLUSE_HOOK_SCRIPT, CURSOR_PRETOOLUSE_HOOK_SCRIPT, CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT;
|
|
2447
2773
|
var init_hooks_config = __esm({
|
|
2448
2774
|
"src/hooks-config.ts"() {
|
|
2449
2775
|
"use strict";
|
|
@@ -2924,6 +3250,23 @@ After compaction, call session_init(is_post_compact=true) to restore context.
|
|
|
2924
3250
|
if __name__ == "__main__":
|
|
2925
3251
|
main()
|
|
2926
3252
|
`;
|
|
3253
|
+
CLAUDE_ENFORCEMENT_CRITICAL_HOOKS = [
|
|
3254
|
+
"PreToolUse",
|
|
3255
|
+
"UserPromptSubmit",
|
|
3256
|
+
"SessionStart",
|
|
3257
|
+
"PreCompact",
|
|
3258
|
+
"PostToolUse"
|
|
3259
|
+
];
|
|
3260
|
+
CURSOR_ENFORCEMENT_CRITICAL_HOOKS = [
|
|
3261
|
+
"preToolUse",
|
|
3262
|
+
"beforeSubmitPrompt",
|
|
3263
|
+
"beforeMCPExecution",
|
|
3264
|
+
"beforeShellExecution",
|
|
3265
|
+
"beforeReadFile",
|
|
3266
|
+
"afterFileEdit",
|
|
3267
|
+
"sessionStart",
|
|
3268
|
+
"preCompact"
|
|
3269
|
+
];
|
|
2927
3270
|
CLINE_PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
|
|
2928
3271
|
"""
|
|
2929
3272
|
ContextStream PreToolUse Hook for Cline
|
|
@@ -3427,6 +3770,7 @@ memory(
|
|
|
3427
3770
|
| Persistent plan | \`session(action="capture_plan")\` |
|
|
3428
3771
|
| Task status | \`memory(action="update_task")\` |
|
|
3429
3772
|
| Decision capture | \`session(action="capture", event_type="decision")\` |
|
|
3773
|
+
| Update skill by name | \`skill(action="update", name="...", instruction_body="...", change_summary="...")\` |
|
|
3430
3774
|
| Past context | \`session(action="recall", query="...")\` |
|
|
3431
3775
|
| Lessons | \`session(action="get_lessons", query="...")\` |
|
|
3432
3776
|
|
|
@@ -3462,6 +3806,9 @@ ${options.workspaceId ? `# Workspace ID: ${options.workspaceId}` : ""}
|
|
|
3462
3806
|
if (NO_HOOKS_EDITORS.includes(editor.toLowerCase())) {
|
|
3463
3807
|
content += NO_HOOKS_SUPPLEMENT;
|
|
3464
3808
|
}
|
|
3809
|
+
if (editor.toLowerCase() === "antigravity") {
|
|
3810
|
+
content += ANTIGRAVITY_SUPPLEMENT;
|
|
3811
|
+
}
|
|
3465
3812
|
if (options?.additionalRules) {
|
|
3466
3813
|
content += "\n\n## Project-Specific Rules\n\n" + options.additionalRules;
|
|
3467
3814
|
}
|
|
@@ -3497,7 +3844,7 @@ function generateAllRuleFiles(options) {
|
|
|
3497
3844
|
}))
|
|
3498
3845
|
).filter((r) => r !== null);
|
|
3499
3846
|
}
|
|
3500
|
-
var DEFAULT_CLAUDE_MCP_SERVER_NAME, RULES_VERSION, CONTEXTSTREAM_TOOL_NAMES, CONTEXTSTREAM_RULES_BOOTSTRAP, CONTEXTSTREAM_RULES_DYNAMIC, CONTEXTSTREAM_RULES_FULL, CONTEXTSTREAM_RULES_MINIMAL, NO_HOOKS_SUPPLEMENT, NO_HOOKS_EDITORS, TEMPLATES;
|
|
3847
|
+
var DEFAULT_CLAUDE_MCP_SERVER_NAME, RULES_VERSION, CONTEXTSTREAM_TOOL_NAMES, CONTEXTSTREAM_RULES_BOOTSTRAP, CONTEXTSTREAM_RULES_DYNAMIC, CONTEXTSTREAM_RULES_FULL, CONTEXTSTREAM_RULES_MINIMAL, NO_HOOKS_SUPPLEMENT, ANTIGRAVITY_SUPPLEMENT, NO_HOOKS_EDITORS, TEMPLATES;
|
|
3501
3848
|
var init_rules_templates = __esm({
|
|
3502
3849
|
"src/rules-templates.ts"() {
|
|
3503
3850
|
"use strict";
|
|
@@ -4429,13 +4776,32 @@ After updating, user should restart their AI tool.
|
|
|
4429
4776
|
|
|
4430
4777
|
---
|
|
4431
4778
|
`;
|
|
4432
|
-
|
|
4779
|
+
ANTIGRAVITY_SUPPLEMENT = `
|
|
4780
|
+
---
|
|
4781
|
+
## Antigravity-Specific Reliability Notes
|
|
4782
|
+
|
|
4783
|
+
- Antigravity currently has no documented lifecycle hooks for ContextStream enforcement.
|
|
4784
|
+
- Treat ContextStream-first behavior as mandatory policy: run \`context(...)\` first, then \`search(mode="auto", ...)\` before local discovery.
|
|
4785
|
+
- Keep \`mcp_config.json\` valid and minimal: preserve non-ContextStream servers and only update the \`contextstream\` server block.
|
|
4786
|
+
- If ContextStream appears skipped, verify:
|
|
4787
|
+
1. MCP server status is healthy in Antigravity settings
|
|
4788
|
+
2. Project is indexed and \`search(mode="auto", ...)\` is retried before local fallbacks
|
|
4789
|
+
3. Instructions file contains the current ContextStream managed block
|
|
4790
|
+
`;
|
|
4791
|
+
NO_HOOKS_EDITORS = ["copilot", "codex", "opencode", "aider", "antigravity"];
|
|
4433
4792
|
TEMPLATES = {
|
|
4434
4793
|
codex: {
|
|
4435
4794
|
filename: "AGENTS.md",
|
|
4436
4795
|
description: "Codex CLI agent instructions",
|
|
4437
4796
|
build: (rules) => `# Codex CLI Instructions
|
|
4438
4797
|
${rules}
|
|
4798
|
+
`
|
|
4799
|
+
},
|
|
4800
|
+
opencode: {
|
|
4801
|
+
filename: "AGENTS.md",
|
|
4802
|
+
description: "OpenCode CLI agent instructions",
|
|
4803
|
+
build: (rules) => `# OpenCode CLI Instructions
|
|
4804
|
+
${rules}
|
|
4439
4805
|
`
|
|
4440
4806
|
},
|
|
4441
4807
|
cursor: {
|
|
@@ -4443,6 +4809,17 @@ ${rules}
|
|
|
4443
4809
|
description: "Cursor AI rules",
|
|
4444
4810
|
build: (rules) => `# Cursor Rules
|
|
4445
4811
|
${rules}
|
|
4812
|
+
`
|
|
4813
|
+
},
|
|
4814
|
+
windsurf: {
|
|
4815
|
+
filename: ".windsurf/rules/contextstream.md",
|
|
4816
|
+
description: "Windsurf AI rules",
|
|
4817
|
+
build: (rules) => `---
|
|
4818
|
+
trigger: always_on
|
|
4819
|
+
---
|
|
4820
|
+
|
|
4821
|
+
# Windsurf Rules
|
|
4822
|
+
${rules}
|
|
4446
4823
|
`
|
|
4447
4824
|
},
|
|
4448
4825
|
cline: {
|
|
@@ -19536,6 +19913,68 @@ function formatRunResult(result) {
|
|
|
19536
19913
|
${result.output}`;
|
|
19537
19914
|
return JSON.stringify(result, null, 2);
|
|
19538
19915
|
}
|
|
19916
|
+
function computeSkillMatchScore(query, name, title) {
|
|
19917
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
19918
|
+
if (!normalizedQuery) return null;
|
|
19919
|
+
const normalizedName = String(name || "").trim().toLowerCase();
|
|
19920
|
+
const normalizedTitle = String(title || "").trim().toLowerCase();
|
|
19921
|
+
if (normalizedName === normalizedQuery) return 0;
|
|
19922
|
+
if (normalizedTitle === normalizedQuery) return 1;
|
|
19923
|
+
if (normalizedName.includes(normalizedQuery)) return 2;
|
|
19924
|
+
if (normalizedTitle.includes(normalizedQuery)) return 3;
|
|
19925
|
+
return null;
|
|
19926
|
+
}
|
|
19927
|
+
function rankSkillCandidates(query, items) {
|
|
19928
|
+
return items.map((item) => {
|
|
19929
|
+
const id = String(item?.id || "");
|
|
19930
|
+
const name = String(item?.name || "?");
|
|
19931
|
+
const title = String(item?.title || name);
|
|
19932
|
+
const score = computeSkillMatchScore(query, name, title);
|
|
19933
|
+
if (!id || score == null) return null;
|
|
19934
|
+
return {
|
|
19935
|
+
id,
|
|
19936
|
+
name,
|
|
19937
|
+
title,
|
|
19938
|
+
scope: String(item?.scope || "?"),
|
|
19939
|
+
status: String(item?.status || "?"),
|
|
19940
|
+
score
|
|
19941
|
+
};
|
|
19942
|
+
}).filter((item) => item !== null).sort((a, b) => {
|
|
19943
|
+
if (a.score !== b.score) return a.score - b.score;
|
|
19944
|
+
if (a.name !== b.name) return a.name.localeCompare(b.name);
|
|
19945
|
+
if (a.title !== b.title) return a.title.localeCompare(b.title);
|
|
19946
|
+
return a.id.localeCompare(b.id);
|
|
19947
|
+
});
|
|
19948
|
+
}
|
|
19949
|
+
async function resolveSkillIdForAction(client, input, action) {
|
|
19950
|
+
if (input.skill_id) return input.skill_id;
|
|
19951
|
+
const name = typeof input.name === "string" ? input.name.trim() : "";
|
|
19952
|
+
if (!name) {
|
|
19953
|
+
throw new Error(`Either skill_id or name is required for '${action}'`);
|
|
19954
|
+
}
|
|
19955
|
+
const result = await client.listSkills({
|
|
19956
|
+
workspace_id: input.workspace_id,
|
|
19957
|
+
project_id: input.project_id,
|
|
19958
|
+
query: name,
|
|
19959
|
+
limit: 25
|
|
19960
|
+
});
|
|
19961
|
+
const ranked = rankSkillCandidates(name, result.items || []);
|
|
19962
|
+
if (ranked.length === 0) {
|
|
19963
|
+
throw new Error(`Skill '${name}' not found. Provide a different name or skill_id.`);
|
|
19964
|
+
}
|
|
19965
|
+
const bestScore = ranked[0].score;
|
|
19966
|
+
const bestCandidates = ranked.filter((candidate) => candidate.score === bestScore);
|
|
19967
|
+
if (bestCandidates.length > 1) {
|
|
19968
|
+
const candidates = bestCandidates.slice(0, 5).map(
|
|
19969
|
+
(candidate) => `- ${candidate.title} (${candidate.name}) [${candidate.scope}|${candidate.status}] id=${candidate.id}`
|
|
19970
|
+
).join("\n");
|
|
19971
|
+
throw new Error(
|
|
19972
|
+
`Skill name '${name}' is ambiguous. Provide skill_id or use a more specific name. Candidates:
|
|
19973
|
+
${candidates}`
|
|
19974
|
+
);
|
|
19975
|
+
}
|
|
19976
|
+
return ranked[0].id;
|
|
19977
|
+
}
|
|
19539
19978
|
function readStatNumber(payload, key) {
|
|
19540
19979
|
if (!payload || typeof payload !== "object") return void 0;
|
|
19541
19980
|
const direct = payload[key];
|
|
@@ -21935,20 +22374,8 @@ Actions:
|
|
|
21935
22374
|
return { content: [{ type: "text", text }] };
|
|
21936
22375
|
}
|
|
21937
22376
|
case "get": {
|
|
21938
|
-
|
|
21939
|
-
|
|
21940
|
-
skillData = await client.getSkill(input.skill_id);
|
|
21941
|
-
} else if (input.name) {
|
|
21942
|
-
const result = await client.listSkills({
|
|
21943
|
-
workspace_id: input.workspace_id,
|
|
21944
|
-
query: input.name,
|
|
21945
|
-
limit: 1
|
|
21946
|
-
});
|
|
21947
|
-
skillData = result.items?.find((s) => s.name === input.name) || result.items?.[0];
|
|
21948
|
-
if (!skillData) throw new Error(`Skill '${input.name}' not found`);
|
|
21949
|
-
} else {
|
|
21950
|
-
throw new Error("Either skill_id or name is required for 'get'");
|
|
21951
|
-
}
|
|
22377
|
+
const resolvedId = await resolveSkillIdForAction(client, input, "get");
|
|
22378
|
+
const skillData = await client.getSkill(resolvedId);
|
|
21952
22379
|
const detail = formatSkillDetail(skillData);
|
|
21953
22380
|
return { content: [{ type: "text", text: detail }] };
|
|
21954
22381
|
}
|
|
@@ -21976,8 +22403,8 @@ Actions:
|
|
|
21976
22403
|
return { content: [{ type: "text", text: `Skill '${input.name}' created (id=${result.id || "?"}).` }] };
|
|
21977
22404
|
}
|
|
21978
22405
|
case "update": {
|
|
21979
|
-
|
|
21980
|
-
const result = await client.updateSkill(
|
|
22406
|
+
const resolvedId = await resolveSkillIdForAction(client, input, "update");
|
|
22407
|
+
const result = await client.updateSkill(resolvedId, {
|
|
21981
22408
|
title: input.title,
|
|
21982
22409
|
description: input.description,
|
|
21983
22410
|
instruction_body: input.instruction_body,
|
|
@@ -21991,21 +22418,10 @@ Actions:
|
|
|
21991
22418
|
priority: input.priority,
|
|
21992
22419
|
change_summary: input.change_summary
|
|
21993
22420
|
});
|
|
21994
|
-
return { content: [{ type: "text", text: `Skill ${
|
|
22421
|
+
return { content: [{ type: "text", text: `Skill ${resolvedId} updated (version=${result.version || 0}).` }] };
|
|
21995
22422
|
}
|
|
21996
22423
|
case "run": {
|
|
21997
|
-
|
|
21998
|
-
if (!resolvedId && input.name) {
|
|
21999
|
-
const result2 = await client.listSkills({
|
|
22000
|
-
workspace_id: input.workspace_id,
|
|
22001
|
-
query: input.name,
|
|
22002
|
-
limit: 1
|
|
22003
|
-
});
|
|
22004
|
-
const found = result2.items?.find((s) => s.name === input.name) || result2.items?.[0];
|
|
22005
|
-
if (!found?.id) throw new Error(`Skill '${input.name}' not found`);
|
|
22006
|
-
resolvedId = found.id;
|
|
22007
|
-
}
|
|
22008
|
-
if (!resolvedId) throw new Error("Either skill_id or name is required for 'run'");
|
|
22424
|
+
const resolvedId = await resolveSkillIdForAction(client, input, "run");
|
|
22009
22425
|
const result = await client.runSkill(resolvedId, {
|
|
22010
22426
|
params: input.params,
|
|
22011
22427
|
dry_run: input.dry_run
|
|
@@ -22013,9 +22429,9 @@ Actions:
|
|
|
22013
22429
|
return { content: [{ type: "text", text: formatRunResult(result) }] };
|
|
22014
22430
|
}
|
|
22015
22431
|
case "delete": {
|
|
22016
|
-
|
|
22017
|
-
await client.deleteSkill(
|
|
22018
|
-
return { content: [{ type: "text", text: `Skill ${
|
|
22432
|
+
const resolvedId = await resolveSkillIdForAction(client, input, "delete");
|
|
22433
|
+
await client.deleteSkill(resolvedId);
|
|
22434
|
+
return { content: [{ type: "text", text: `Skill ${resolvedId} deleted.` }] };
|
|
22019
22435
|
}
|
|
22020
22436
|
case "import": {
|
|
22021
22437
|
let importContent = input.content;
|
|
@@ -22044,10 +22460,10 @@ Actions:
|
|
|
22044
22460
|
return { content: [{ type: "text", text: result.content || JSON.stringify(result, null, 2) }] };
|
|
22045
22461
|
}
|
|
22046
22462
|
case "share": {
|
|
22047
|
-
if (!input.skill_id) throw new Error("'skill_id' is required for share");
|
|
22048
22463
|
if (!input.scope) throw new Error("'scope' is required for share");
|
|
22049
|
-
const
|
|
22050
|
-
|
|
22464
|
+
const resolvedId = await resolveSkillIdForAction(client, input, "share");
|
|
22465
|
+
const result = await client.shareSkill(resolvedId, input.scope);
|
|
22466
|
+
return { content: [{ type: "text", text: `Skill ${resolvedId} shared with scope=${result.scope || input.scope}.` }] };
|
|
22051
22467
|
}
|
|
22052
22468
|
default:
|
|
22053
22469
|
throw new Error(`Invalid skill action: '${action}'. Valid: list, get, create, update, run, delete, import, export, share`);
|
|
@@ -24223,6 +24639,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
24223
24639
|
).length;
|
|
24224
24640
|
const skippedCount = results.filter((r) => r.status.startsWith("skipped")).length;
|
|
24225
24641
|
const baseMessage = input.dry_run ? "Dry run complete. Use dry_run: false to write files." : skippedCount > 0 ? `Generated ${createdCount} rule files. ${skippedCount} skipped (existing files). Re-run with overwrite_existing: true to replace ContextStream blocks.` : `Generated ${createdCount} rule files.`;
|
|
24642
|
+
const copilotSetupNote = editors.includes("copilot") ? "Select Copilot in setup; setup handles both ~/.copilot/mcp-config.json and project .vscode/mcp.json automatically." : void 0;
|
|
24226
24643
|
const globalPrompt = input.apply_global ? "Global rule update complete." : globalTargets.length > 0 ? "Apply rules globally too? Re-run with apply_global: true." : "No global rule locations are known for these editors.";
|
|
24227
24644
|
let hooksResults;
|
|
24228
24645
|
let hooksPrompt;
|
|
@@ -24231,7 +24648,8 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
24231
24648
|
cline: "cline",
|
|
24232
24649
|
roo: "roo",
|
|
24233
24650
|
kilo: "kilo",
|
|
24234
|
-
cursor: "cursor"
|
|
24651
|
+
cursor: "cursor",
|
|
24652
|
+
windsurf: "windsurf"
|
|
24235
24653
|
};
|
|
24236
24654
|
const hookSupportedEditors = editors.filter((e) => e in editorHookMap);
|
|
24237
24655
|
const shouldInstallHooks = hookSupportedEditors.length > 0 && input.install_hooks !== false;
|
|
@@ -24270,6 +24688,10 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
24270
24688
|
{ editor, file: "~/.cursor/hooks/contextstream-beforesubmit.py", status: "dry run - would create" },
|
|
24271
24689
|
{ editor, file: "~/.cursor/hooks.json", status: "dry run - would update" }
|
|
24272
24690
|
);
|
|
24691
|
+
} else if (editor === "windsurf") {
|
|
24692
|
+
hooksResults.push(
|
|
24693
|
+
{ editor, file: "~/.codeium/windsurf/hooks.json", status: "dry run - would update" }
|
|
24694
|
+
);
|
|
24273
24695
|
}
|
|
24274
24696
|
}
|
|
24275
24697
|
} else {
|
|
@@ -24298,6 +24720,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
24298
24720
|
...globalTargets.length > 0 ? { global_targets: globalTargets } : {},
|
|
24299
24721
|
...hooksResults ? { hooks_results: hooksResults } : {},
|
|
24300
24722
|
message: baseMessage,
|
|
24723
|
+
...copilotSetupNote ? { copilot_setup_note: copilotSetupNote } : {},
|
|
24301
24724
|
global_prompt: globalPrompt,
|
|
24302
24725
|
...hooksPrompt ? { hooks_prompt: hooksPrompt } : {}
|
|
24303
24726
|
};
|
|
@@ -24388,7 +24811,10 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
24388
24811
|
const summary = {
|
|
24389
24812
|
folder: folderPath,
|
|
24390
24813
|
results,
|
|
24391
|
-
message: input.dry_run ? "Dry run complete. Use dry_run: false to write files." : skippedCount > 0 ? `Generated ${createdCount} rule files. ${skippedCount} skipped (existing files). Re-run with overwrite_existing: true to replace ContextStream blocks.` : `Generated ${createdCount} rule files
|
|
24814
|
+
message: input.dry_run ? "Dry run complete. Use dry_run: false to write files." : skippedCount > 0 ? `Generated ${createdCount} rule files. ${skippedCount} skipped (existing files). Re-run with overwrite_existing: true to replace ContextStream blocks.` : `Generated ${createdCount} rule files.`,
|
|
24815
|
+
...editors.includes("copilot") ? {
|
|
24816
|
+
copilot_setup_note: "Select Copilot in setup; setup handles both ~/.copilot/mcp-config.json and project .vscode/mcp.json automatically."
|
|
24817
|
+
} : {}
|
|
24392
24818
|
};
|
|
24393
24819
|
return {
|
|
24394
24820
|
content: [{ type: "text", text: formatContent(summary) }]
|
|
@@ -30546,6 +30972,7 @@ This will:
|
|
|
30546
30972
|
- Start a 5-day Pro trial
|
|
30547
30973
|
- Auto-configure your editor's MCP settings
|
|
30548
30974
|
- Write rules files for better AI assistance
|
|
30975
|
+
- If you select Copilot, setup automatically writes both ~/.copilot/mcp-config.json and project .vscode/mcp.json
|
|
30549
30976
|
|
|
30550
30977
|
Preview first:
|
|
30551
30978
|
npx --prefer-online -y @contextstream/mcp-server@latest setup --dry-run
|
|
@@ -32387,9 +32814,11 @@ init_hooks_config();
|
|
|
32387
32814
|
init_files();
|
|
32388
32815
|
var EDITOR_LABELS = {
|
|
32389
32816
|
codex: "Codex CLI",
|
|
32817
|
+
copilot: "GitHub Copilot (VS Code)",
|
|
32390
32818
|
opencode: "OpenCode",
|
|
32391
32819
|
claude: "Claude Code",
|
|
32392
32820
|
cursor: "Cursor / VS Code",
|
|
32821
|
+
windsurf: "Windsurf",
|
|
32393
32822
|
cline: "Cline",
|
|
32394
32823
|
kilo: "Kilo Code",
|
|
32395
32824
|
roo: "Roo Code",
|
|
@@ -32397,7 +32826,7 @@ var EDITOR_LABELS = {
|
|
|
32397
32826
|
antigravity: "Antigravity (Google)"
|
|
32398
32827
|
};
|
|
32399
32828
|
function supportsProjectMcpConfig(editor) {
|
|
32400
|
-
return editor === "opencode" || editor === "
|
|
32829
|
+
return editor === "opencode" || editor === "copilot" || editor === "cursor" || editor === "claude" || editor === "kilo" || editor === "roo";
|
|
32401
32830
|
}
|
|
32402
32831
|
function normalizeInput(value) {
|
|
32403
32832
|
return value.trim();
|
|
@@ -32468,6 +32897,7 @@ var CONTEXTSTREAM_PREAMBLE_PATTERNS2 = [
|
|
|
32468
32897
|
/^#\s+codex cli instructions$/i,
|
|
32469
32898
|
/^#\s+claude code instructions$/i,
|
|
32470
32899
|
/^#\s+cursor rules$/i,
|
|
32900
|
+
/^#\s+windsurf rules$/i,
|
|
32471
32901
|
/^#\s+cline rules$/i,
|
|
32472
32902
|
/^#\s+kilo code rules$/i,
|
|
32473
32903
|
/^#\s+roo code rules$/i,
|
|
@@ -32608,6 +33038,10 @@ function globalRulesPathForEditor(editor) {
|
|
|
32608
33038
|
switch (editor) {
|
|
32609
33039
|
case "codex":
|
|
32610
33040
|
return path8.join(home, ".codex", "AGENTS.md");
|
|
33041
|
+
case "copilot":
|
|
33042
|
+
return null;
|
|
33043
|
+
case "opencode":
|
|
33044
|
+
return path8.join(home, ".opencode", "AGENTS.md");
|
|
32611
33045
|
case "claude":
|
|
32612
33046
|
return path8.join(home, ".claude", "CLAUDE.md");
|
|
32613
33047
|
case "cline":
|
|
@@ -32622,6 +33056,8 @@ function globalRulesPathForEditor(editor) {
|
|
|
32622
33056
|
return path8.join(home, ".gemini", "GEMINI.md");
|
|
32623
33057
|
case "cursor":
|
|
32624
33058
|
return null;
|
|
33059
|
+
case "windsurf":
|
|
33060
|
+
return path8.join(home, ".codeium", "windsurf", "memories", "global_rules.md");
|
|
32625
33061
|
default:
|
|
32626
33062
|
return null;
|
|
32627
33063
|
}
|
|
@@ -32687,6 +33123,16 @@ async function isClineInstalled() {
|
|
|
32687
33123
|
];
|
|
32688
33124
|
return anyPathExists(candidates);
|
|
32689
33125
|
}
|
|
33126
|
+
async function isCopilotInstalled() {
|
|
33127
|
+
const home = homedir6();
|
|
33128
|
+
const candidates = [
|
|
33129
|
+
path8.join(home, ".copilot"),
|
|
33130
|
+
path8.join(home, ".config", "github-copilot"),
|
|
33131
|
+
path8.join(home, ".vscode", "extensions", "github.copilot-chat"),
|
|
33132
|
+
path8.join(home, ".vscode", "extensions", "github.copilot")
|
|
33133
|
+
];
|
|
33134
|
+
return anyPathExists(candidates);
|
|
33135
|
+
}
|
|
32690
33136
|
async function isKiloInstalled() {
|
|
32691
33137
|
const home = homedir6();
|
|
32692
33138
|
const candidates = [path8.join(home, ".kilocode"), path8.join(home, ".config", "kilocode")];
|
|
@@ -32725,6 +33171,29 @@ async function isCursorInstalled() {
|
|
|
32725
33171
|
}
|
|
32726
33172
|
return anyPathExists(candidates);
|
|
32727
33173
|
}
|
|
33174
|
+
async function isWindsurfInstalled() {
|
|
33175
|
+
const home = homedir6();
|
|
33176
|
+
const candidates = [path8.join(home, ".codeium", "windsurf")];
|
|
33177
|
+
if (process.platform === "darwin") {
|
|
33178
|
+
candidates.push("/Applications/Windsurf.app");
|
|
33179
|
+
candidates.push(path8.join(home, "Applications", "Windsurf.app"));
|
|
33180
|
+
candidates.push(path8.join(home, "Library", "Application Support", "Windsurf"));
|
|
33181
|
+
} else if (process.platform === "win32") {
|
|
33182
|
+
const localApp = process.env.LOCALAPPDATA;
|
|
33183
|
+
const programFiles = process.env.ProgramFiles;
|
|
33184
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"];
|
|
33185
|
+
if (localApp) candidates.push(path8.join(localApp, "Programs", "Windsurf", "Windsurf.exe"));
|
|
33186
|
+
if (localApp) candidates.push(path8.join(localApp, "Windsurf", "Windsurf.exe"));
|
|
33187
|
+
if (programFiles) candidates.push(path8.join(programFiles, "Windsurf", "Windsurf.exe"));
|
|
33188
|
+
if (programFilesX86) candidates.push(path8.join(programFilesX86, "Windsurf", "Windsurf.exe"));
|
|
33189
|
+
} else {
|
|
33190
|
+
candidates.push("/usr/bin/windsurf");
|
|
33191
|
+
candidates.push("/usr/local/bin/windsurf");
|
|
33192
|
+
candidates.push("/opt/Windsurf");
|
|
33193
|
+
candidates.push("/opt/windsurf");
|
|
33194
|
+
}
|
|
33195
|
+
return anyPathExists(candidates);
|
|
33196
|
+
}
|
|
32728
33197
|
async function isAntigravityInstalled() {
|
|
32729
33198
|
const home = homedir6();
|
|
32730
33199
|
const candidates = [path8.join(home, ".gemini")];
|
|
@@ -32754,12 +33223,16 @@ async function isEditorInstalled(editor) {
|
|
|
32754
33223
|
switch (editor) {
|
|
32755
33224
|
case "codex":
|
|
32756
33225
|
return isCodexInstalled();
|
|
33226
|
+
case "copilot":
|
|
33227
|
+
return isCopilotInstalled();
|
|
32757
33228
|
case "opencode":
|
|
32758
33229
|
return isOpenCodeInstalled();
|
|
32759
33230
|
case "claude":
|
|
32760
33231
|
return isClaudeInstalled();
|
|
32761
33232
|
case "cursor":
|
|
32762
33233
|
return isCursorInstalled();
|
|
33234
|
+
case "windsurf":
|
|
33235
|
+
return isWindsurfInstalled();
|
|
32763
33236
|
case "cline":
|
|
32764
33237
|
return isClineInstalled();
|
|
32765
33238
|
case "kilo":
|
|
@@ -33559,7 +34032,7 @@ Code: ${device.user_code}`);
|
|
|
33559
34032
|
);
|
|
33560
34033
|
}
|
|
33561
34034
|
}
|
|
33562
|
-
const NO_HOOKS_EDITORS2 = ["codex", "opencode", "aider", "antigravity"];
|
|
34035
|
+
const NO_HOOKS_EDITORS2 = ["codex", "copilot", "opencode", "aider", "antigravity"];
|
|
33563
34036
|
const getModeForEditor = (editor) => NO_HOOKS_EDITORS2.includes(editor) ? "full" : "bootstrap";
|
|
33564
34037
|
const detectedPlanName = await client.getPlanName();
|
|
33565
34038
|
const detectedGraphTier = await client.getGraphTier();
|
|
@@ -33586,9 +34059,11 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33586
34059
|
}
|
|
33587
34060
|
const editors = [
|
|
33588
34061
|
"codex",
|
|
34062
|
+
"copilot",
|
|
33589
34063
|
"opencode",
|
|
33590
34064
|
"claude",
|
|
33591
34065
|
"cursor",
|
|
34066
|
+
"windsurf",
|
|
33592
34067
|
"cline",
|
|
33593
34068
|
"kilo",
|
|
33594
34069
|
"roo",
|
|
@@ -33658,6 +34133,28 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33658
34133
|
)
|
|
33659
34134
|
) || mcpChoiceDefault;
|
|
33660
34135
|
const mcpScope = mcpChoice === "2" && hasCodex && !hasProjectMcpEditors ? "skip" : mcpChoice === "4" ? "skip" : mcpChoice === "1" ? "global" : mcpChoice === "2" ? "project" : "both";
|
|
34136
|
+
const copilotSelected = configuredEditors.includes("copilot");
|
|
34137
|
+
let enforceCopilotCanonicalPair = copilotSelected && mcpScope !== "skip";
|
|
34138
|
+
if (enforceCopilotCanonicalPair) {
|
|
34139
|
+
const confirmCopilotPair = normalizeInput(
|
|
34140
|
+
await rl.question(
|
|
34141
|
+
"Configure Copilot canonical MCP files (~/.copilot/mcp-config.json + .vscode/mcp.json)? [Y/n]: "
|
|
34142
|
+
)
|
|
34143
|
+
).toLowerCase();
|
|
34144
|
+
enforceCopilotCanonicalPair = confirmCopilotPair !== "n" && confirmCopilotPair !== "no";
|
|
34145
|
+
}
|
|
34146
|
+
if (enforceCopilotCanonicalPair) {
|
|
34147
|
+
console.log(
|
|
34148
|
+
"\nCopilot automation enabled: setup will write both ~/.copilot/mcp-config.json and project .vscode/mcp.json (no separate Copilot command needed)."
|
|
34149
|
+
);
|
|
34150
|
+
console.log(
|
|
34151
|
+
"If you installed the Rust runtime, replace the generated command with `contextstream-mcp` in both files."
|
|
34152
|
+
);
|
|
34153
|
+
} else if (copilotSelected && mcpScope !== "skip") {
|
|
34154
|
+
console.log(
|
|
34155
|
+
"\nCopilot canonical pair disabled for this run. You can rerun setup anytime to generate both files together."
|
|
34156
|
+
);
|
|
34157
|
+
}
|
|
33661
34158
|
const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, contextPackEnabled });
|
|
33662
34159
|
const mcpServerClaude = buildContextStreamMcpServer({ apiUrl, apiKey, contextPackEnabled });
|
|
33663
34160
|
const mcpServerOpenCode = buildContextStreamOpenCodeLocalServer({
|
|
@@ -33674,7 +34171,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33674
34171
|
" OpenCode reads CONTEXTSTREAM_API_KEY from your environment. Export it before launching OpenCode."
|
|
33675
34172
|
);
|
|
33676
34173
|
};
|
|
33677
|
-
const needsGlobalMcpConfig = mcpScope === "global" || mcpScope === "both" || mcpScope === "project" && hasCodex;
|
|
34174
|
+
const needsGlobalMcpConfig = mcpScope === "global" || mcpScope === "both" || mcpScope === "project" && hasCodex || enforceCopilotCanonicalPair;
|
|
33678
34175
|
if (needsGlobalMcpConfig) {
|
|
33679
34176
|
console.log("\nInstalling global MCP config...");
|
|
33680
34177
|
for (const editor of configuredEditors) {
|
|
@@ -33696,6 +34193,18 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33696
34193
|
console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
|
|
33697
34194
|
continue;
|
|
33698
34195
|
}
|
|
34196
|
+
if (editor === "copilot") {
|
|
34197
|
+
const filePath = path8.join(homedir6(), ".copilot", "mcp-config.json");
|
|
34198
|
+
if (dryRun) {
|
|
34199
|
+
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
34200
|
+
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
34201
|
+
continue;
|
|
34202
|
+
}
|
|
34203
|
+
const status = await upsertJsonMcpConfig(filePath, mcpServer);
|
|
34204
|
+
writeActions.push({ kind: "mcp-config", target: filePath, status });
|
|
34205
|
+
console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
|
|
34206
|
+
continue;
|
|
34207
|
+
}
|
|
33699
34208
|
if (editor === "claude") {
|
|
33700
34209
|
const desktopPath = claudeDesktopConfigPath();
|
|
33701
34210
|
if (desktopPath) {
|
|
@@ -33751,6 +34260,18 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33751
34260
|
console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
|
|
33752
34261
|
continue;
|
|
33753
34262
|
}
|
|
34263
|
+
if (editor === "windsurf") {
|
|
34264
|
+
const filePath = path8.join(homedir6(), ".codeium", "windsurf", "mcp_config.json");
|
|
34265
|
+
if (dryRun) {
|
|
34266
|
+
writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
|
|
34267
|
+
console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
|
|
34268
|
+
continue;
|
|
34269
|
+
}
|
|
34270
|
+
const status = await upsertJsonMcpConfig(filePath, mcpServer);
|
|
34271
|
+
writeActions.push({ kind: "mcp-config", target: filePath, status });
|
|
34272
|
+
console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
|
|
34273
|
+
continue;
|
|
34274
|
+
}
|
|
33754
34275
|
if (editor === "cline") {
|
|
33755
34276
|
console.log(
|
|
33756
34277
|
`- ${EDITOR_LABELS[editor]}: MCP config is managed via the extension UI (skipping global).`
|
|
@@ -33776,11 +34297,14 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33776
34297
|
const HOOKS_SUPPORTED_EDITORS = {
|
|
33777
34298
|
claude: "claude",
|
|
33778
34299
|
cursor: "cursor",
|
|
34300
|
+
windsurf: "windsurf",
|
|
33779
34301
|
cline: "cline",
|
|
33780
34302
|
roo: "roo",
|
|
33781
34303
|
kilo: "kilo",
|
|
33782
34304
|
codex: null,
|
|
33783
34305
|
// No hooks API
|
|
34306
|
+
copilot: null,
|
|
34307
|
+
// No hooks API
|
|
33784
34308
|
opencode: null,
|
|
33785
34309
|
// No hooks API
|
|
33786
34310
|
aider: null,
|
|
@@ -33809,6 +34333,16 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33809
34333
|
writeActions.push({ kind: "hooks", target: script, status: "created" });
|
|
33810
34334
|
console.log(`- ${EDITOR_LABELS[editor]}: installed ${path8.basename(script)}`);
|
|
33811
34335
|
}
|
|
34336
|
+
if (editor === "cursor") {
|
|
34337
|
+
console.log(
|
|
34338
|
+
" Cursor hooks enabled: preToolUse, beforeSubmitPrompt, before/after MCP+shell, beforeReadFile, afterFileEdit, session lifecycle"
|
|
34339
|
+
);
|
|
34340
|
+
}
|
|
34341
|
+
if (editor === "windsurf") {
|
|
34342
|
+
console.log(
|
|
34343
|
+
" Windsurf hooks enabled: pre_mcp_tool_use, pre_user_prompt, pre/post code+command hooks, post_cascade_response_with_transcript"
|
|
34344
|
+
);
|
|
34345
|
+
}
|
|
33812
34346
|
} catch (err) {
|
|
33813
34347
|
const message = err instanceof Error ? err.message : String(err);
|
|
33814
34348
|
console.log(`- ${EDITOR_LABELS[editor]}: failed to install hooks: ${message}`);
|
|
@@ -33816,6 +34350,19 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33816
34350
|
}
|
|
33817
34351
|
console.log(" Disable hooks anytime with CONTEXTSTREAM_HOOK_ENABLED=false");
|
|
33818
34352
|
}
|
|
34353
|
+
const noHookEditors = configuredEditors.filter((e) => HOOKS_SUPPORTED_EDITORS[e] === null);
|
|
34354
|
+
if (noHookEditors.length > 0) {
|
|
34355
|
+
console.log("\nEditors without lifecycle hooks (rules-only enforcement):");
|
|
34356
|
+
for (const editor of noHookEditors) {
|
|
34357
|
+
if (editor === "antigravity") {
|
|
34358
|
+
console.log(
|
|
34359
|
+
`- ${EDITOR_LABELS[editor]}: no hooks API available; relying on strict ContextStream rules + index/search discipline.`
|
|
34360
|
+
);
|
|
34361
|
+
} else {
|
|
34362
|
+
console.log(`- ${EDITOR_LABELS[editor]}: no hooks API available; using rules-only enforcement.`);
|
|
34363
|
+
}
|
|
34364
|
+
}
|
|
34365
|
+
}
|
|
33819
34366
|
console.log("\nCode Privacy:");
|
|
33820
34367
|
console.log(" \u2713 Encrypted in transit (TLS 1.3) and at rest (AES-256)");
|
|
33821
34368
|
console.log(" \u2713 Isolated per workspace \u2014 no cross-tenant access");
|
|
@@ -33823,12 +34370,6 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33823
34370
|
if (scope === "global" || scope === "both") {
|
|
33824
34371
|
console.log("\nInstalling global rules...");
|
|
33825
34372
|
for (const editor of configuredEditors) {
|
|
33826
|
-
if (editor === "opencode") {
|
|
33827
|
-
console.log(
|
|
33828
|
-
`- ${EDITOR_LABELS[editor]}: rules are not auto-generated yet (MCP config only).`
|
|
33829
|
-
);
|
|
33830
|
-
continue;
|
|
33831
|
-
}
|
|
33832
34373
|
const filePath = globalRulesPathForEditor(editor);
|
|
33833
34374
|
if (!filePath) {
|
|
33834
34375
|
console.log(
|
|
@@ -33859,7 +34400,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33859
34400
|
}
|
|
33860
34401
|
}
|
|
33861
34402
|
const projectPaths = /* @__PURE__ */ new Set();
|
|
33862
|
-
const needsProjects = scope === "project" || scope === "both" || (mcpScope === "project" || mcpScope === "both") && hasProjectMcpEditors;
|
|
34403
|
+
const needsProjects = scope === "project" || scope === "both" || (mcpScope === "project" || mcpScope === "both") && hasProjectMcpEditors || enforceCopilotCanonicalPair;
|
|
33863
34404
|
if (needsProjects) {
|
|
33864
34405
|
console.log("\nProject setup...");
|
|
33865
34406
|
const addCwd = normalizeInput(
|
|
@@ -33942,8 +34483,10 @@ Applying to ${projects.length} project(s)...`);
|
|
|
33942
34483
|
status: "dry-run"
|
|
33943
34484
|
});
|
|
33944
34485
|
}
|
|
33945
|
-
if (mcpScope === "project" || mcpScope === "both") {
|
|
34486
|
+
if (mcpScope === "project" || mcpScope === "both" || enforceCopilotCanonicalPair) {
|
|
33946
34487
|
for (const editor of configuredEditors) {
|
|
34488
|
+
const shouldWriteProjectMcp = mcpScope === "project" || mcpScope === "both" || enforceCopilotCanonicalPair && editor === "copilot";
|
|
34489
|
+
if (!shouldWriteProjectMcp) continue;
|
|
33947
34490
|
try {
|
|
33948
34491
|
if (editor === "cursor") {
|
|
33949
34492
|
const cursorPath = path8.join(projectPath, ".cursor", "mcp.json");
|
|
@@ -33959,6 +34502,12 @@ Applying to ${projects.length} project(s)...`);
|
|
|
33959
34502
|
}
|
|
33960
34503
|
continue;
|
|
33961
34504
|
}
|
|
34505
|
+
if (editor === "windsurf") {
|
|
34506
|
+
console.log(
|
|
34507
|
+
`- ${EDITOR_LABELS[editor]}: uses global MCP config only (${path8.join(homedir6(), ".codeium", "windsurf", "mcp_config.json")}).`
|
|
34508
|
+
);
|
|
34509
|
+
continue;
|
|
34510
|
+
}
|
|
33962
34511
|
if (editor === "claude") {
|
|
33963
34512
|
const mcpPath = path8.join(projectPath, ".mcp.json");
|
|
33964
34513
|
if (dryRun) {
|
|
@@ -33984,6 +34533,16 @@ Applying to ${projects.length} project(s)...`);
|
|
|
33984
34533
|
printOpenCodeEnvNote();
|
|
33985
34534
|
continue;
|
|
33986
34535
|
}
|
|
34536
|
+
if (editor === "copilot") {
|
|
34537
|
+
const vsCodePath = path8.join(projectPath, ".vscode", "mcp.json");
|
|
34538
|
+
if (dryRun) {
|
|
34539
|
+
writeActions.push({ kind: "mcp-config", target: vsCodePath, status: "dry-run" });
|
|
34540
|
+
} else {
|
|
34541
|
+
const status = await upsertJsonVsCodeMcpConfig(vsCodePath, vsCodeServer);
|
|
34542
|
+
writeActions.push({ kind: "mcp-config", target: vsCodePath, status });
|
|
34543
|
+
}
|
|
34544
|
+
continue;
|
|
34545
|
+
}
|
|
33987
34546
|
if (editor === "kilo") {
|
|
33988
34547
|
const kiloPath = path8.join(projectPath, ".kilocode", "mcp.json");
|
|
33989
34548
|
if (dryRun) {
|
|
@@ -34015,7 +34574,6 @@ Applying to ${projects.length} project(s)...`);
|
|
|
34015
34574
|
for (const editor of selectedEditors) {
|
|
34016
34575
|
if (scope !== "project" && scope !== "both") continue;
|
|
34017
34576
|
if (!configuredEditors.includes(editor)) continue;
|
|
34018
|
-
if (editor === "opencode") continue;
|
|
34019
34577
|
const rule = generateRuleContent(editor, {
|
|
34020
34578
|
workspaceName,
|
|
34021
34579
|
workspaceId: workspaceId && workspaceId !== "dry-run" ? workspaceId : void 0,
|
|
@@ -34139,6 +34697,16 @@ Applying to ${projects.length} project(s)...`);
|
|
|
34139
34697
|
console.log(
|
|
34140
34698
|
"- For UI-based MCP setup (Cline/Kilo/Roo global), see https://contextstream.io/docs/mcp"
|
|
34141
34699
|
);
|
|
34700
|
+
if (copilotSelected) {
|
|
34701
|
+
const nodeCommand = IS_WINDOWS ? "cmd /c npx --prefer-online -y @contextstream/mcp-server@latest" : "npx --prefer-online -y @contextstream/mcp-server@latest";
|
|
34702
|
+
console.log("\nGitHub Copilot + VS Code checklist:");
|
|
34703
|
+
console.log("- Confirm ~/.copilot/mcp-config.json has a contextstream server entry.");
|
|
34704
|
+
console.log("- Confirm .vscode/mcp.json has a contextstream server entry.");
|
|
34705
|
+
console.log(`- Node runtime command: ${nodeCommand}`);
|
|
34706
|
+
console.log(
|
|
34707
|
+
"- If Rust MCP is installed, replace the command in both files with `contextstream-mcp` and set args to []."
|
|
34708
|
+
);
|
|
34709
|
+
}
|
|
34142
34710
|
console.log("");
|
|
34143
34711
|
console.log(
|
|
34144
34712
|
"You're all set! ContextStream gives your AI persistent memory, semantic code search, and cross-session context."
|