@contextstream/mcp-server 0.4.65 → 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 +935 -47
- 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: {
|
|
@@ -12919,6 +13296,37 @@ function normalizeTags(tags) {
|
|
|
12919
13296
|
);
|
|
12920
13297
|
return normalized.length > 0 ? normalized : void 0;
|
|
12921
13298
|
}
|
|
13299
|
+
function extractEventTags(item) {
|
|
13300
|
+
const tags = [];
|
|
13301
|
+
if (Array.isArray(item.tags)) {
|
|
13302
|
+
tags.push(...item.tags.filter((t) => typeof t === "string"));
|
|
13303
|
+
}
|
|
13304
|
+
const metaTags = item.metadata?.tags;
|
|
13305
|
+
if (Array.isArray(metaTags)) {
|
|
13306
|
+
tags.push(...metaTags.filter((t) => typeof t === "string"));
|
|
13307
|
+
}
|
|
13308
|
+
return tags;
|
|
13309
|
+
}
|
|
13310
|
+
function extractEffectiveEventType(item) {
|
|
13311
|
+
for (const field of ["event_type", "node_type", "type"]) {
|
|
13312
|
+
const val = item[field];
|
|
13313
|
+
if (typeof val === "string" && val.trim()) return val.trim();
|
|
13314
|
+
}
|
|
13315
|
+
for (const field of ["original_type", "node_type", "event_type", "type"]) {
|
|
13316
|
+
const val = item.metadata?.[field];
|
|
13317
|
+
if (typeof val === "string" && val.trim()) return val.trim();
|
|
13318
|
+
}
|
|
13319
|
+
return "unknown";
|
|
13320
|
+
}
|
|
13321
|
+
function isLessonResult(item) {
|
|
13322
|
+
const effectiveType = extractEffectiveEventType(item);
|
|
13323
|
+
if (effectiveType === "lesson") return true;
|
|
13324
|
+
const tags = extractEventTags(item);
|
|
13325
|
+
if (tags.some((t) => t === "lesson" || t === "lesson_system")) return true;
|
|
13326
|
+
const content = typeof item.content === "string" ? item.content : "";
|
|
13327
|
+
if (content.includes("### Prevention") && content.includes("### Trigger")) return true;
|
|
13328
|
+
return false;
|
|
13329
|
+
}
|
|
12922
13330
|
function pickString(value) {
|
|
12923
13331
|
if (typeof value !== "string") return null;
|
|
12924
13332
|
const trimmed = value.trim();
|
|
@@ -13472,6 +13880,8 @@ var ContextStreamClient = class _ContextStreamClient {
|
|
|
13472
13880
|
const query = new URLSearchParams();
|
|
13473
13881
|
if (params?.limit) query.set("limit", String(params.limit));
|
|
13474
13882
|
if (withDefaults.project_id) query.set("project_id", withDefaults.project_id);
|
|
13883
|
+
if (params?.event_type) query.set("event_type", params.event_type);
|
|
13884
|
+
if (params?.tags && params.tags.length > 0) query.set("tags", params.tags.join(","));
|
|
13475
13885
|
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
13476
13886
|
return request(this.config, `/memory/events/workspace/${withDefaults.workspace_id}${suffix}`, {
|
|
13477
13887
|
method: "GET"
|
|
@@ -15835,14 +16245,13 @@ ${context}`;
|
|
|
15835
16245
|
});
|
|
15836
16246
|
if (!searchResult?.results) return [];
|
|
15837
16247
|
const lessons = searchResult.results.filter((item) => {
|
|
15838
|
-
|
|
15839
|
-
const
|
|
15840
|
-
if (!isLesson) return false;
|
|
16248
|
+
if (!isLessonResult(item)) return false;
|
|
16249
|
+
const tags = extractEventTags(item);
|
|
15841
16250
|
const severityTag = tags.find((t) => t.startsWith("severity:"));
|
|
15842
16251
|
const severity = severityTag?.split(":")[1] || item.metadata?.importance || "medium";
|
|
15843
16252
|
return severity === "critical" || severity === "high";
|
|
15844
16253
|
}).slice(0, limit).map((item) => {
|
|
15845
|
-
const tags = item
|
|
16254
|
+
const tags = extractEventTags(item);
|
|
15846
16255
|
const severityTag = tags.find((t) => t.startsWith("severity:"));
|
|
15847
16256
|
const severity = severityTag?.split(":")[1] || item.metadata?.importance || "medium";
|
|
15848
16257
|
const category = tags.find(
|
|
@@ -15856,7 +16265,7 @@ ${context}`;
|
|
|
15856
16265
|
) || "unknown";
|
|
15857
16266
|
const content = item.content || "";
|
|
15858
16267
|
const preventionMatch = content.match(/### Prevention\n([\s\S]*?)(?:\n\n|\n\*\*|$)/);
|
|
15859
|
-
const prevention = preventionMatch?.[1]?.trim() || content.slice(0,
|
|
16268
|
+
const prevention = preventionMatch?.[1]?.trim() || content.slice(0, 1e3);
|
|
15860
16269
|
return {
|
|
15861
16270
|
title: item.title || "Lesson",
|
|
15862
16271
|
severity,
|
|
@@ -17111,6 +17520,55 @@ ${context}`;
|
|
|
17111
17520
|
return request(this.config, `/docs/${params.doc_id}`, { method: "DELETE" });
|
|
17112
17521
|
}
|
|
17113
17522
|
// -------------------------------------------------------------------------
|
|
17523
|
+
// Skill methods (portable instruction + action bundles)
|
|
17524
|
+
// -------------------------------------------------------------------------
|
|
17525
|
+
async listSkills(params) {
|
|
17526
|
+
const withDefaults = this.withDefaults(params || {});
|
|
17527
|
+
const query = new URLSearchParams();
|
|
17528
|
+
if (withDefaults.workspace_id) query.set("workspace_id", withDefaults.workspace_id);
|
|
17529
|
+
if (params?.project_id) query.set("project_id", params.project_id);
|
|
17530
|
+
if (params?.scope) query.set("scope", params.scope);
|
|
17531
|
+
if (params?.status) query.set("status", params.status);
|
|
17532
|
+
if (params?.category) query.set("category", params.category);
|
|
17533
|
+
if (params?.query) query.set("query", params.query);
|
|
17534
|
+
if (params?.is_personal !== void 0) query.set("is_personal", String(params.is_personal));
|
|
17535
|
+
if (params?.limit) query.set("limit", String(params.limit));
|
|
17536
|
+
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
17537
|
+
return request(this.config, `/skills${suffix}`, { method: "GET" });
|
|
17538
|
+
}
|
|
17539
|
+
async getSkill(skillId) {
|
|
17540
|
+
return request(this.config, `/skills/${skillId}`, { method: "GET" });
|
|
17541
|
+
}
|
|
17542
|
+
async createSkill(params) {
|
|
17543
|
+
return request(this.config, "/skills", { body: this.withDefaults(params) });
|
|
17544
|
+
}
|
|
17545
|
+
async updateSkill(skillId, params) {
|
|
17546
|
+
return request(this.config, `/skills/${skillId}`, {
|
|
17547
|
+
method: "PATCH",
|
|
17548
|
+
body: params
|
|
17549
|
+
});
|
|
17550
|
+
}
|
|
17551
|
+
async runSkill(skillId, params) {
|
|
17552
|
+
return request(this.config, `/skills/${skillId}/run`, {
|
|
17553
|
+
body: params || {}
|
|
17554
|
+
});
|
|
17555
|
+
}
|
|
17556
|
+
async deleteSkill(skillId) {
|
|
17557
|
+
return request(this.config, `/skills/${skillId}`, { method: "DELETE" });
|
|
17558
|
+
}
|
|
17559
|
+
async importSkills(params) {
|
|
17560
|
+
return request(this.config, "/skills/import", { body: this.withDefaults(params) });
|
|
17561
|
+
}
|
|
17562
|
+
async exportSkills(params) {
|
|
17563
|
+
const withDefaults = this.withDefaults(params || {});
|
|
17564
|
+
return request(this.config, "/skills/export", { body: withDefaults });
|
|
17565
|
+
}
|
|
17566
|
+
async shareSkill(skillId, scope) {
|
|
17567
|
+
return request(this.config, `/skills/${skillId}/share`, {
|
|
17568
|
+
body: { scope }
|
|
17569
|
+
});
|
|
17570
|
+
}
|
|
17571
|
+
// -------------------------------------------------------------------------
|
|
17114
17572
|
// Transcript methods (conversation session storage)
|
|
17115
17573
|
// -------------------------------------------------------------------------
|
|
17116
17574
|
/**
|
|
@@ -17202,14 +17660,14 @@ ${context}`;
|
|
|
17202
17660
|
};
|
|
17203
17661
|
|
|
17204
17662
|
// src/tools.ts
|
|
17205
|
-
init_files();
|
|
17206
|
-
init_rules_templates();
|
|
17207
|
-
init_version();
|
|
17208
17663
|
import * as fs5 from "node:fs";
|
|
17209
17664
|
import * as path6 from "node:path";
|
|
17210
17665
|
import { execFile } from "node:child_process";
|
|
17211
17666
|
import { homedir as homedir4 } from "node:os";
|
|
17212
17667
|
import { promisify as promisify2 } from "node:util";
|
|
17668
|
+
init_files();
|
|
17669
|
+
init_rules_templates();
|
|
17670
|
+
init_version();
|
|
17213
17671
|
|
|
17214
17672
|
// src/tool-catalog.ts
|
|
17215
17673
|
var TOOL_CATALOG = [
|
|
@@ -19313,6 +19771,8 @@ var CONSOLIDATED_TOOLS = /* @__PURE__ */ new Set([
|
|
|
19313
19771
|
// Consolidates slack_*, github_*, notion_*, integrations_*
|
|
19314
19772
|
"media",
|
|
19315
19773
|
// Consolidates media indexing, search, and clip retrieval for Remotion/FFmpeg
|
|
19774
|
+
"skill",
|
|
19775
|
+
// Skill management: list, get, create, update, run, delete, import, export, share
|
|
19316
19776
|
"help"
|
|
19317
19777
|
// Consolidates session_tools, auth_me, mcp_server_version, etc.
|
|
19318
19778
|
]);
|
|
@@ -19332,6 +19792,7 @@ function mapToolToConsolidatedDomain(toolName) {
|
|
|
19332
19792
|
return "integration";
|
|
19333
19793
|
}
|
|
19334
19794
|
if (toolName.startsWith("media_")) return "media";
|
|
19795
|
+
if (toolName.startsWith("skill_")) return "skill";
|
|
19335
19796
|
if (toolName === "session_tools" || toolName === "auth_me" || toolName === "mcp_server_version" || toolName === "tools_enable_bundle") {
|
|
19336
19797
|
return "help";
|
|
19337
19798
|
}
|
|
@@ -19427,6 +19888,93 @@ function toStructured(data) {
|
|
|
19427
19888
|
}
|
|
19428
19889
|
return void 0;
|
|
19429
19890
|
}
|
|
19891
|
+
function formatSkillDetail(skill) {
|
|
19892
|
+
const lines = [
|
|
19893
|
+
`**${skill.title || skill.name || "?"}** (${skill.name || "?"})`,
|
|
19894
|
+
`- ID: ${skill.id || "?"}`,
|
|
19895
|
+
`- Scope: ${skill.scope || "personal"} | Status: ${skill.status || "active"}`
|
|
19896
|
+
];
|
|
19897
|
+
if (skill.description) lines.push(`- Description: ${skill.description}`);
|
|
19898
|
+
if (skill.trigger_patterns?.length) lines.push(`- Triggers: ${skill.trigger_patterns.join(", ")}`);
|
|
19899
|
+
if (skill.categories?.length) lines.push(`- Categories: ${skill.categories.join(", ")}`);
|
|
19900
|
+
if (skill.priority != null) lines.push(`- Priority: ${skill.priority}`);
|
|
19901
|
+
if (skill.version) lines.push(`- Version: ${skill.version}`);
|
|
19902
|
+
if (skill.instruction_body) {
|
|
19903
|
+
const body = String(skill.instruction_body);
|
|
19904
|
+
lines.push(`
|
|
19905
|
+
### Instruction
|
|
19906
|
+
${body.length > 2e3 ? body.slice(0, 2e3) + "\u2026" : body}`);
|
|
19907
|
+
}
|
|
19908
|
+
return lines.join("\n");
|
|
19909
|
+
}
|
|
19910
|
+
function formatRunResult(result) {
|
|
19911
|
+
if (result.instruction) return result.instruction;
|
|
19912
|
+
if (result.output) return `Skill output:
|
|
19913
|
+
${result.output}`;
|
|
19914
|
+
return JSON.stringify(result, null, 2);
|
|
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
|
+
}
|
|
19430
19978
|
function readStatNumber(payload, key) {
|
|
19431
19979
|
if (!payload || typeof payload !== "object") return void 0;
|
|
19432
19980
|
const direct = payload[key];
|
|
@@ -21750,6 +22298,178 @@ Access: Free`,
|
|
|
21750
22298
|
};
|
|
21751
22299
|
}
|
|
21752
22300
|
);
|
|
22301
|
+
registerTool(
|
|
22302
|
+
"skill",
|
|
22303
|
+
{
|
|
22304
|
+
title: "Manage reusable skills",
|
|
22305
|
+
description: `Manage and execute reusable skills (instruction + action bundles). Skills are portable across projects, sessions, and tools.
|
|
22306
|
+
|
|
22307
|
+
Actions:
|
|
22308
|
+
- list: Browse skills (filter by scope, status, category)
|
|
22309
|
+
- get: Get skill details by ID or name
|
|
22310
|
+
- create: Define a new skill with name, instruction, and triggers
|
|
22311
|
+
- update: Modify an existing skill
|
|
22312
|
+
- run: Execute a skill (by ID or name)
|
|
22313
|
+
- delete: Remove a skill
|
|
22314
|
+
- import: Import skills from file or content (supports markdown, JSON, cursorrules, claude_md)
|
|
22315
|
+
- export: Export skills in various formats
|
|
22316
|
+
- share: Change skill visibility scope`,
|
|
22317
|
+
inputSchema: external_exports.object({
|
|
22318
|
+
action: external_exports.enum(["list", "get", "create", "update", "run", "delete", "import", "export", "share"]).describe("The action to perform"),
|
|
22319
|
+
skill_id: external_exports.string().optional().describe("Skill ID (UUID)"),
|
|
22320
|
+
name: external_exports.string().optional().describe("Skill name (slug, e.g. 'deploy-checker')"),
|
|
22321
|
+
title: external_exports.string().optional().describe("Skill display title"),
|
|
22322
|
+
description: external_exports.string().optional().describe("Skill description"),
|
|
22323
|
+
instruction_body: external_exports.string().optional().describe("Markdown instruction text (the prompt)"),
|
|
22324
|
+
trigger_patterns: external_exports.array(external_exports.string()).optional().describe("Keywords/phrases for auto-activation"),
|
|
22325
|
+
trigger_regex: external_exports.string().optional().describe("Optional regex for advanced trigger matching"),
|
|
22326
|
+
categories: external_exports.array(external_exports.string()).optional().describe("Tags for discovery/filtering"),
|
|
22327
|
+
actions: external_exports.any().optional().describe("Action steps array [{type, tool, params, ...}]"),
|
|
22328
|
+
params: external_exports.any().optional().describe("Parameters passed to skill execution"),
|
|
22329
|
+
dry_run: external_exports.boolean().optional().describe("Preview execution without running"),
|
|
22330
|
+
scope: external_exports.enum(["personal", "team", "public", "all"]).optional().describe("Visibility scope"),
|
|
22331
|
+
status: external_exports.enum(["active", "draft", "archived"]).optional().describe("Skill status"),
|
|
22332
|
+
is_personal: external_exports.boolean().optional().describe("Whether skill is personal"),
|
|
22333
|
+
priority: external_exports.number().optional().describe("Skill priority 0-100 (higher = matched first)"),
|
|
22334
|
+
content: external_exports.string().optional().describe("Content string for import"),
|
|
22335
|
+
file_path: external_exports.string().optional().describe("Local file path for import"),
|
|
22336
|
+
format: external_exports.enum(["auto", "json", "markdown", "skills_md", "cursorrules", "claude_md", "aider", "zip"]).optional().describe("Import/export format"),
|
|
22337
|
+
source_tool: external_exports.string().optional().describe("Source tool name (for import provenance)"),
|
|
22338
|
+
source_file: external_exports.string().optional().describe("Source filename (for import provenance)"),
|
|
22339
|
+
skill_ids: external_exports.array(external_exports.string()).optional().describe("Skill IDs for export"),
|
|
22340
|
+
change_summary: external_exports.string().optional().describe("Summary of changes (for version history)"),
|
|
22341
|
+
workspace_id: external_exports.string().optional().describe("Workspace ID (UUID)"),
|
|
22342
|
+
project_id: external_exports.string().optional().describe("Project ID (UUID)"),
|
|
22343
|
+
query: external_exports.string().optional().describe("Search query"),
|
|
22344
|
+
category: external_exports.string().optional().describe("Filter by category tag"),
|
|
22345
|
+
limit: external_exports.number().optional().describe("Max results to return")
|
|
22346
|
+
})
|
|
22347
|
+
},
|
|
22348
|
+
async (input) => {
|
|
22349
|
+
const action = input.action;
|
|
22350
|
+
switch (action) {
|
|
22351
|
+
case "list": {
|
|
22352
|
+
const result = await client.listSkills({
|
|
22353
|
+
workspace_id: input.workspace_id,
|
|
22354
|
+
project_id: input.project_id,
|
|
22355
|
+
scope: input.scope,
|
|
22356
|
+
status: input.status,
|
|
22357
|
+
category: input.category,
|
|
22358
|
+
query: input.query,
|
|
22359
|
+
is_personal: input.is_personal,
|
|
22360
|
+
limit: input.limit
|
|
22361
|
+
});
|
|
22362
|
+
const items = result.items || [];
|
|
22363
|
+
let text = `Found ${items.length} skill(s).
|
|
22364
|
+
`;
|
|
22365
|
+
for (const item of items) {
|
|
22366
|
+
const name = item.name || "?";
|
|
22367
|
+
const title = item.title || "?";
|
|
22368
|
+
const scope = item.scope || "?";
|
|
22369
|
+
const status = item.status || "?";
|
|
22370
|
+
const id = item.id || "?";
|
|
22371
|
+
text += `- ${title} (${name}) [${scope}|${status}] id=${id}
|
|
22372
|
+
`;
|
|
22373
|
+
}
|
|
22374
|
+
return { content: [{ type: "text", text }] };
|
|
22375
|
+
}
|
|
22376
|
+
case "get": {
|
|
22377
|
+
const resolvedId = await resolveSkillIdForAction(client, input, "get");
|
|
22378
|
+
const skillData = await client.getSkill(resolvedId);
|
|
22379
|
+
const detail = formatSkillDetail(skillData);
|
|
22380
|
+
return { content: [{ type: "text", text: detail }] };
|
|
22381
|
+
}
|
|
22382
|
+
case "create": {
|
|
22383
|
+
if (!input.name) throw new Error("'name' is required for create");
|
|
22384
|
+
if (!input.instruction_body) throw new Error("'instruction_body' is required for create");
|
|
22385
|
+
const result = await client.createSkill({
|
|
22386
|
+
name: input.name,
|
|
22387
|
+
title: input.title || input.name,
|
|
22388
|
+
instruction_body: input.instruction_body,
|
|
22389
|
+
description: input.description,
|
|
22390
|
+
trigger_patterns: input.trigger_patterns,
|
|
22391
|
+
trigger_regex: input.trigger_regex,
|
|
22392
|
+
categories: input.categories,
|
|
22393
|
+
actions: input.actions,
|
|
22394
|
+
scope: input.scope,
|
|
22395
|
+
is_personal: input.is_personal,
|
|
22396
|
+
priority: input.priority,
|
|
22397
|
+
workspace_id: input.scope === "team" ? input.workspace_id : void 0,
|
|
22398
|
+
project_id: void 0,
|
|
22399
|
+
// Skills are account-level by default
|
|
22400
|
+
source_tool: input.source_tool,
|
|
22401
|
+
source_file: input.source_file
|
|
22402
|
+
});
|
|
22403
|
+
return { content: [{ type: "text", text: `Skill '${input.name}' created (id=${result.id || "?"}).` }] };
|
|
22404
|
+
}
|
|
22405
|
+
case "update": {
|
|
22406
|
+
const resolvedId = await resolveSkillIdForAction(client, input, "update");
|
|
22407
|
+
const result = await client.updateSkill(resolvedId, {
|
|
22408
|
+
title: input.title,
|
|
22409
|
+
description: input.description,
|
|
22410
|
+
instruction_body: input.instruction_body,
|
|
22411
|
+
trigger_patterns: input.trigger_patterns,
|
|
22412
|
+
trigger_regex: input.trigger_regex,
|
|
22413
|
+
categories: input.categories,
|
|
22414
|
+
actions: input.actions,
|
|
22415
|
+
scope: input.scope,
|
|
22416
|
+
status: input.status,
|
|
22417
|
+
is_personal: input.is_personal,
|
|
22418
|
+
priority: input.priority,
|
|
22419
|
+
change_summary: input.change_summary
|
|
22420
|
+
});
|
|
22421
|
+
return { content: [{ type: "text", text: `Skill ${resolvedId} updated (version=${result.version || 0}).` }] };
|
|
22422
|
+
}
|
|
22423
|
+
case "run": {
|
|
22424
|
+
const resolvedId = await resolveSkillIdForAction(client, input, "run");
|
|
22425
|
+
const result = await client.runSkill(resolvedId, {
|
|
22426
|
+
params: input.params,
|
|
22427
|
+
dry_run: input.dry_run
|
|
22428
|
+
});
|
|
22429
|
+
return { content: [{ type: "text", text: formatRunResult(result) }] };
|
|
22430
|
+
}
|
|
22431
|
+
case "delete": {
|
|
22432
|
+
const resolvedId = await resolveSkillIdForAction(client, input, "delete");
|
|
22433
|
+
await client.deleteSkill(resolvedId);
|
|
22434
|
+
return { content: [{ type: "text", text: `Skill ${resolvedId} deleted.` }] };
|
|
22435
|
+
}
|
|
22436
|
+
case "import": {
|
|
22437
|
+
let importContent = input.content;
|
|
22438
|
+
if (!importContent && input.file_path) {
|
|
22439
|
+
const { readFile: readFile4 } = await import("fs/promises");
|
|
22440
|
+
importContent = await readFile4(input.file_path, "utf-8");
|
|
22441
|
+
}
|
|
22442
|
+
if (!importContent) throw new Error("Either 'content' or 'file_path' is required for import");
|
|
22443
|
+
const result = await client.importSkills({
|
|
22444
|
+
content: importContent,
|
|
22445
|
+
format: input.format,
|
|
22446
|
+
source_tool: input.source_tool,
|
|
22447
|
+
source_file: input.source_file || input.file_path,
|
|
22448
|
+
scope: input.scope,
|
|
22449
|
+
workspace_id: input.workspace_id
|
|
22450
|
+
});
|
|
22451
|
+
return { content: [{ type: "text", text: `Import complete: ${result.imported || 0} imported, ${result.skipped || 0} skipped (duplicates).` }] };
|
|
22452
|
+
}
|
|
22453
|
+
case "export": {
|
|
22454
|
+
const result = await client.exportSkills({
|
|
22455
|
+
skill_ids: input.skill_ids,
|
|
22456
|
+
format: input.format,
|
|
22457
|
+
scope: input.scope,
|
|
22458
|
+
workspace_id: input.workspace_id
|
|
22459
|
+
});
|
|
22460
|
+
return { content: [{ type: "text", text: result.content || JSON.stringify(result, null, 2) }] };
|
|
22461
|
+
}
|
|
22462
|
+
case "share": {
|
|
22463
|
+
if (!input.scope) throw new Error("'scope' is required for share");
|
|
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}.` }] };
|
|
22467
|
+
}
|
|
22468
|
+
default:
|
|
22469
|
+
throw new Error(`Invalid skill action: '${action}'. Valid: list, get, create, update, run, delete, import, export, share`);
|
|
22470
|
+
}
|
|
22471
|
+
}
|
|
22472
|
+
);
|
|
21753
22473
|
registerTool(
|
|
21754
22474
|
"memory_bulk_ingest",
|
|
21755
22475
|
{
|
|
@@ -21772,15 +22492,24 @@ Access: Free`,
|
|
|
21772
22492
|
"memory_list_events",
|
|
21773
22493
|
{
|
|
21774
22494
|
title: "List memory events",
|
|
21775
|
-
description: "List memory events (optionally scoped)",
|
|
22495
|
+
description: "List memory events (optionally scoped). Supports tag-based and event_type filtering for precise provenance tracking.",
|
|
21776
22496
|
inputSchema: external_exports.object({
|
|
21777
22497
|
workspace_id: external_exports.string().uuid().optional(),
|
|
21778
22498
|
project_id: external_exports.string().uuid().optional(),
|
|
21779
|
-
limit: external_exports.number().optional()
|
|
22499
|
+
limit: external_exports.number().optional(),
|
|
22500
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Filter events that contain ALL of these tags"),
|
|
22501
|
+
event_type: external_exports.string().optional().describe("Filter by event type (e.g. decision, lesson, manual_note)")
|
|
21780
22502
|
})
|
|
21781
22503
|
},
|
|
21782
22504
|
async (input) => {
|
|
21783
22505
|
const result = await client.listMemoryEvents(input);
|
|
22506
|
+
if (input.tags && input.tags.length > 0 && result.items) {
|
|
22507
|
+
const requiredTags = input.tags;
|
|
22508
|
+
result.items = result.items.filter((item) => {
|
|
22509
|
+
const itemTags = extractEventTags(item);
|
|
22510
|
+
return requiredTags.every((tag) => itemTags.includes(tag));
|
|
22511
|
+
});
|
|
22512
|
+
}
|
|
21784
22513
|
return {
|
|
21785
22514
|
content: [{ type: "text", text: formatContent(result) }]
|
|
21786
22515
|
};
|
|
@@ -21834,16 +22563,24 @@ Access: Free`,
|
|
|
21834
22563
|
"memory_search",
|
|
21835
22564
|
{
|
|
21836
22565
|
title: "Memory-aware search",
|
|
21837
|
-
description: "Search memory events/notes",
|
|
22566
|
+
description: "Search memory events/notes. Supports optional tag-based pre-filtering.",
|
|
21838
22567
|
inputSchema: external_exports.object({
|
|
21839
22568
|
query: external_exports.string(),
|
|
21840
22569
|
workspace_id: external_exports.string().uuid().optional(),
|
|
21841
22570
|
project_id: external_exports.string().uuid().optional(),
|
|
21842
|
-
limit: external_exports.number().optional()
|
|
22571
|
+
limit: external_exports.number().optional(),
|
|
22572
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Filter results that contain ALL of these tags")
|
|
21843
22573
|
})
|
|
21844
22574
|
},
|
|
21845
22575
|
async (input) => {
|
|
21846
22576
|
const result = await client.memorySearch(input);
|
|
22577
|
+
if (input.tags && input.tags.length > 0 && result.results) {
|
|
22578
|
+
const requiredTags = input.tags;
|
|
22579
|
+
result.results = result.results.filter((item) => {
|
|
22580
|
+
const itemTags = extractEventTags(item);
|
|
22581
|
+
return requiredTags.every((tag) => itemTags.includes(tag));
|
|
22582
|
+
});
|
|
22583
|
+
}
|
|
21847
22584
|
return {
|
|
21848
22585
|
content: [{ type: "text", text: formatContent(result) }]
|
|
21849
22586
|
};
|
|
@@ -23631,9 +24368,8 @@ Returns lessons filtered by:
|
|
|
23631
24368
|
// Fetch more to filter
|
|
23632
24369
|
});
|
|
23633
24370
|
const lessons = (searchResult.results || []).filter((item) => {
|
|
23634
|
-
|
|
23635
|
-
const
|
|
23636
|
-
if (!isLesson) return false;
|
|
24371
|
+
if (!isLessonResult(item)) return false;
|
|
24372
|
+
const tags = extractEventTags(item);
|
|
23637
24373
|
if (input.category && !tags.includes(input.category)) {
|
|
23638
24374
|
return false;
|
|
23639
24375
|
}
|
|
@@ -23652,7 +24388,7 @@ Returns lessons filtered by:
|
|
|
23652
24388
|
};
|
|
23653
24389
|
}
|
|
23654
24390
|
const formattedLessons = lessons.map((lesson, i) => {
|
|
23655
|
-
const tags = lesson
|
|
24391
|
+
const tags = extractEventTags(lesson);
|
|
23656
24392
|
const severity = tags.find((t) => t.startsWith("severity:"))?.split(":")[1] || "medium";
|
|
23657
24393
|
const category = tags.find(
|
|
23658
24394
|
(t) => [
|
|
@@ -23671,7 +24407,7 @@ Returns lessons filtered by:
|
|
|
23671
24407
|
}[severity] || "\u26AA";
|
|
23672
24408
|
return `${i + 1}. ${severityEmoji} **${lesson.title}**
|
|
23673
24409
|
Category: ${category} | Severity: ${severity}
|
|
23674
|
-
${lesson.content?.slice(0,
|
|
24410
|
+
${lesson.content?.slice(0, 500)}...`;
|
|
23675
24411
|
}).join("\n\n");
|
|
23676
24412
|
return {
|
|
23677
24413
|
content: [
|
|
@@ -23903,6 +24639,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
23903
24639
|
).length;
|
|
23904
24640
|
const skippedCount = results.filter((r) => r.status.startsWith("skipped")).length;
|
|
23905
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;
|
|
23906
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.";
|
|
23907
24644
|
let hooksResults;
|
|
23908
24645
|
let hooksPrompt;
|
|
@@ -23911,7 +24648,8 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
23911
24648
|
cline: "cline",
|
|
23912
24649
|
roo: "roo",
|
|
23913
24650
|
kilo: "kilo",
|
|
23914
|
-
cursor: "cursor"
|
|
24651
|
+
cursor: "cursor",
|
|
24652
|
+
windsurf: "windsurf"
|
|
23915
24653
|
};
|
|
23916
24654
|
const hookSupportedEditors = editors.filter((e) => e in editorHookMap);
|
|
23917
24655
|
const shouldInstallHooks = hookSupportedEditors.length > 0 && input.install_hooks !== false;
|
|
@@ -23950,6 +24688,10 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
23950
24688
|
{ editor, file: "~/.cursor/hooks/contextstream-beforesubmit.py", status: "dry run - would create" },
|
|
23951
24689
|
{ editor, file: "~/.cursor/hooks.json", status: "dry run - would update" }
|
|
23952
24690
|
);
|
|
24691
|
+
} else if (editor === "windsurf") {
|
|
24692
|
+
hooksResults.push(
|
|
24693
|
+
{ editor, file: "~/.codeium/windsurf/hooks.json", status: "dry run - would update" }
|
|
24694
|
+
);
|
|
23953
24695
|
}
|
|
23954
24696
|
}
|
|
23955
24697
|
} else {
|
|
@@ -23978,6 +24720,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
23978
24720
|
...globalTargets.length > 0 ? { global_targets: globalTargets } : {},
|
|
23979
24721
|
...hooksResults ? { hooks_results: hooksResults } : {},
|
|
23980
24722
|
message: baseMessage,
|
|
24723
|
+
...copilotSetupNote ? { copilot_setup_note: copilotSetupNote } : {},
|
|
23981
24724
|
global_prompt: globalPrompt,
|
|
23982
24725
|
...hooksPrompt ? { hooks_prompt: hooksPrompt } : {}
|
|
23983
24726
|
};
|
|
@@ -24068,7 +24811,10 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
24068
24811
|
const summary = {
|
|
24069
24812
|
folder: folderPath,
|
|
24070
24813
|
results,
|
|
24071
|
-
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
|
+
} : {}
|
|
24072
24818
|
};
|
|
24073
24819
|
return {
|
|
24074
24820
|
content: [{ type: "text", text: formatContent(summary) }]
|
|
@@ -30226,6 +30972,7 @@ This will:
|
|
|
30226
30972
|
- Start a 5-day Pro trial
|
|
30227
30973
|
- Auto-configure your editor's MCP settings
|
|
30228
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
|
|
30229
30976
|
|
|
30230
30977
|
Preview first:
|
|
30231
30978
|
npx --prefer-online -y @contextstream/mcp-server@latest setup --dry-run
|
|
@@ -32067,9 +32814,11 @@ init_hooks_config();
|
|
|
32067
32814
|
init_files();
|
|
32068
32815
|
var EDITOR_LABELS = {
|
|
32069
32816
|
codex: "Codex CLI",
|
|
32817
|
+
copilot: "GitHub Copilot (VS Code)",
|
|
32070
32818
|
opencode: "OpenCode",
|
|
32071
32819
|
claude: "Claude Code",
|
|
32072
32820
|
cursor: "Cursor / VS Code",
|
|
32821
|
+
windsurf: "Windsurf",
|
|
32073
32822
|
cline: "Cline",
|
|
32074
32823
|
kilo: "Kilo Code",
|
|
32075
32824
|
roo: "Roo Code",
|
|
@@ -32077,7 +32826,7 @@ var EDITOR_LABELS = {
|
|
|
32077
32826
|
antigravity: "Antigravity (Google)"
|
|
32078
32827
|
};
|
|
32079
32828
|
function supportsProjectMcpConfig(editor) {
|
|
32080
|
-
return editor === "opencode" || editor === "
|
|
32829
|
+
return editor === "opencode" || editor === "copilot" || editor === "cursor" || editor === "claude" || editor === "kilo" || editor === "roo";
|
|
32081
32830
|
}
|
|
32082
32831
|
function normalizeInput(value) {
|
|
32083
32832
|
return value.trim();
|
|
@@ -32148,6 +32897,7 @@ var CONTEXTSTREAM_PREAMBLE_PATTERNS2 = [
|
|
|
32148
32897
|
/^#\s+codex cli instructions$/i,
|
|
32149
32898
|
/^#\s+claude code instructions$/i,
|
|
32150
32899
|
/^#\s+cursor rules$/i,
|
|
32900
|
+
/^#\s+windsurf rules$/i,
|
|
32151
32901
|
/^#\s+cline rules$/i,
|
|
32152
32902
|
/^#\s+kilo code rules$/i,
|
|
32153
32903
|
/^#\s+roo code rules$/i,
|
|
@@ -32288,6 +33038,10 @@ function globalRulesPathForEditor(editor) {
|
|
|
32288
33038
|
switch (editor) {
|
|
32289
33039
|
case "codex":
|
|
32290
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");
|
|
32291
33045
|
case "claude":
|
|
32292
33046
|
return path8.join(home, ".claude", "CLAUDE.md");
|
|
32293
33047
|
case "cline":
|
|
@@ -32302,6 +33056,8 @@ function globalRulesPathForEditor(editor) {
|
|
|
32302
33056
|
return path8.join(home, ".gemini", "GEMINI.md");
|
|
32303
33057
|
case "cursor":
|
|
32304
33058
|
return null;
|
|
33059
|
+
case "windsurf":
|
|
33060
|
+
return path8.join(home, ".codeium", "windsurf", "memories", "global_rules.md");
|
|
32305
33061
|
default:
|
|
32306
33062
|
return null;
|
|
32307
33063
|
}
|
|
@@ -32367,6 +33123,16 @@ async function isClineInstalled() {
|
|
|
32367
33123
|
];
|
|
32368
33124
|
return anyPathExists(candidates);
|
|
32369
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
|
+
}
|
|
32370
33136
|
async function isKiloInstalled() {
|
|
32371
33137
|
const home = homedir6();
|
|
32372
33138
|
const candidates = [path8.join(home, ".kilocode"), path8.join(home, ".config", "kilocode")];
|
|
@@ -32405,6 +33171,29 @@ async function isCursorInstalled() {
|
|
|
32405
33171
|
}
|
|
32406
33172
|
return anyPathExists(candidates);
|
|
32407
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
|
+
}
|
|
32408
33197
|
async function isAntigravityInstalled() {
|
|
32409
33198
|
const home = homedir6();
|
|
32410
33199
|
const candidates = [path8.join(home, ".gemini")];
|
|
@@ -32434,12 +33223,16 @@ async function isEditorInstalled(editor) {
|
|
|
32434
33223
|
switch (editor) {
|
|
32435
33224
|
case "codex":
|
|
32436
33225
|
return isCodexInstalled();
|
|
33226
|
+
case "copilot":
|
|
33227
|
+
return isCopilotInstalled();
|
|
32437
33228
|
case "opencode":
|
|
32438
33229
|
return isOpenCodeInstalled();
|
|
32439
33230
|
case "claude":
|
|
32440
33231
|
return isClaudeInstalled();
|
|
32441
33232
|
case "cursor":
|
|
32442
33233
|
return isCursorInstalled();
|
|
33234
|
+
case "windsurf":
|
|
33235
|
+
return isWindsurfInstalled();
|
|
32443
33236
|
case "cline":
|
|
32444
33237
|
return isClineInstalled();
|
|
32445
33238
|
case "kilo":
|
|
@@ -33239,7 +34032,7 @@ Code: ${device.user_code}`);
|
|
|
33239
34032
|
);
|
|
33240
34033
|
}
|
|
33241
34034
|
}
|
|
33242
|
-
const NO_HOOKS_EDITORS2 = ["codex", "opencode", "aider", "antigravity"];
|
|
34035
|
+
const NO_HOOKS_EDITORS2 = ["codex", "copilot", "opencode", "aider", "antigravity"];
|
|
33243
34036
|
const getModeForEditor = (editor) => NO_HOOKS_EDITORS2.includes(editor) ? "full" : "bootstrap";
|
|
33244
34037
|
const detectedPlanName = await client.getPlanName();
|
|
33245
34038
|
const detectedGraphTier = await client.getGraphTier();
|
|
@@ -33266,9 +34059,11 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33266
34059
|
}
|
|
33267
34060
|
const editors = [
|
|
33268
34061
|
"codex",
|
|
34062
|
+
"copilot",
|
|
33269
34063
|
"opencode",
|
|
33270
34064
|
"claude",
|
|
33271
34065
|
"cursor",
|
|
34066
|
+
"windsurf",
|
|
33272
34067
|
"cline",
|
|
33273
34068
|
"kilo",
|
|
33274
34069
|
"roo",
|
|
@@ -33338,6 +34133,28 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33338
34133
|
)
|
|
33339
34134
|
) || mcpChoiceDefault;
|
|
33340
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
|
+
}
|
|
33341
34158
|
const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, contextPackEnabled });
|
|
33342
34159
|
const mcpServerClaude = buildContextStreamMcpServer({ apiUrl, apiKey, contextPackEnabled });
|
|
33343
34160
|
const mcpServerOpenCode = buildContextStreamOpenCodeLocalServer({
|
|
@@ -33354,7 +34171,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33354
34171
|
" OpenCode reads CONTEXTSTREAM_API_KEY from your environment. Export it before launching OpenCode."
|
|
33355
34172
|
);
|
|
33356
34173
|
};
|
|
33357
|
-
const needsGlobalMcpConfig = mcpScope === "global" || mcpScope === "both" || mcpScope === "project" && hasCodex;
|
|
34174
|
+
const needsGlobalMcpConfig = mcpScope === "global" || mcpScope === "both" || mcpScope === "project" && hasCodex || enforceCopilotCanonicalPair;
|
|
33358
34175
|
if (needsGlobalMcpConfig) {
|
|
33359
34176
|
console.log("\nInstalling global MCP config...");
|
|
33360
34177
|
for (const editor of configuredEditors) {
|
|
@@ -33376,6 +34193,18 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33376
34193
|
console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
|
|
33377
34194
|
continue;
|
|
33378
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
|
+
}
|
|
33379
34208
|
if (editor === "claude") {
|
|
33380
34209
|
const desktopPath = claudeDesktopConfigPath();
|
|
33381
34210
|
if (desktopPath) {
|
|
@@ -33431,6 +34260,18 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33431
34260
|
console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
|
|
33432
34261
|
continue;
|
|
33433
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
|
+
}
|
|
33434
34275
|
if (editor === "cline") {
|
|
33435
34276
|
console.log(
|
|
33436
34277
|
`- ${EDITOR_LABELS[editor]}: MCP config is managed via the extension UI (skipping global).`
|
|
@@ -33456,11 +34297,14 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33456
34297
|
const HOOKS_SUPPORTED_EDITORS = {
|
|
33457
34298
|
claude: "claude",
|
|
33458
34299
|
cursor: "cursor",
|
|
34300
|
+
windsurf: "windsurf",
|
|
33459
34301
|
cline: "cline",
|
|
33460
34302
|
roo: "roo",
|
|
33461
34303
|
kilo: "kilo",
|
|
33462
34304
|
codex: null,
|
|
33463
34305
|
// No hooks API
|
|
34306
|
+
copilot: null,
|
|
34307
|
+
// No hooks API
|
|
33464
34308
|
opencode: null,
|
|
33465
34309
|
// No hooks API
|
|
33466
34310
|
aider: null,
|
|
@@ -33489,6 +34333,16 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33489
34333
|
writeActions.push({ kind: "hooks", target: script, status: "created" });
|
|
33490
34334
|
console.log(`- ${EDITOR_LABELS[editor]}: installed ${path8.basename(script)}`);
|
|
33491
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
|
+
}
|
|
33492
34346
|
} catch (err) {
|
|
33493
34347
|
const message = err instanceof Error ? err.message : String(err);
|
|
33494
34348
|
console.log(`- ${EDITOR_LABELS[editor]}: failed to install hooks: ${message}`);
|
|
@@ -33496,6 +34350,19 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33496
34350
|
}
|
|
33497
34351
|
console.log(" Disable hooks anytime with CONTEXTSTREAM_HOOK_ENABLED=false");
|
|
33498
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
|
+
}
|
|
33499
34366
|
console.log("\nCode Privacy:");
|
|
33500
34367
|
console.log(" \u2713 Encrypted in transit (TLS 1.3) and at rest (AES-256)");
|
|
33501
34368
|
console.log(" \u2713 Isolated per workspace \u2014 no cross-tenant access");
|
|
@@ -33503,12 +34370,6 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33503
34370
|
if (scope === "global" || scope === "both") {
|
|
33504
34371
|
console.log("\nInstalling global rules...");
|
|
33505
34372
|
for (const editor of configuredEditors) {
|
|
33506
|
-
if (editor === "opencode") {
|
|
33507
|
-
console.log(
|
|
33508
|
-
`- ${EDITOR_LABELS[editor]}: rules are not auto-generated yet (MCP config only).`
|
|
33509
|
-
);
|
|
33510
|
-
continue;
|
|
33511
|
-
}
|
|
33512
34373
|
const filePath = globalRulesPathForEditor(editor);
|
|
33513
34374
|
if (!filePath) {
|
|
33514
34375
|
console.log(
|
|
@@ -33539,7 +34400,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
33539
34400
|
}
|
|
33540
34401
|
}
|
|
33541
34402
|
const projectPaths = /* @__PURE__ */ new Set();
|
|
33542
|
-
const needsProjects = scope === "project" || scope === "both" || (mcpScope === "project" || mcpScope === "both") && hasProjectMcpEditors;
|
|
34403
|
+
const needsProjects = scope === "project" || scope === "both" || (mcpScope === "project" || mcpScope === "both") && hasProjectMcpEditors || enforceCopilotCanonicalPair;
|
|
33543
34404
|
if (needsProjects) {
|
|
33544
34405
|
console.log("\nProject setup...");
|
|
33545
34406
|
const addCwd = normalizeInput(
|
|
@@ -33622,8 +34483,10 @@ Applying to ${projects.length} project(s)...`);
|
|
|
33622
34483
|
status: "dry-run"
|
|
33623
34484
|
});
|
|
33624
34485
|
}
|
|
33625
|
-
if (mcpScope === "project" || mcpScope === "both") {
|
|
34486
|
+
if (mcpScope === "project" || mcpScope === "both" || enforceCopilotCanonicalPair) {
|
|
33626
34487
|
for (const editor of configuredEditors) {
|
|
34488
|
+
const shouldWriteProjectMcp = mcpScope === "project" || mcpScope === "both" || enforceCopilotCanonicalPair && editor === "copilot";
|
|
34489
|
+
if (!shouldWriteProjectMcp) continue;
|
|
33627
34490
|
try {
|
|
33628
34491
|
if (editor === "cursor") {
|
|
33629
34492
|
const cursorPath = path8.join(projectPath, ".cursor", "mcp.json");
|
|
@@ -33639,6 +34502,12 @@ Applying to ${projects.length} project(s)...`);
|
|
|
33639
34502
|
}
|
|
33640
34503
|
continue;
|
|
33641
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
|
+
}
|
|
33642
34511
|
if (editor === "claude") {
|
|
33643
34512
|
const mcpPath = path8.join(projectPath, ".mcp.json");
|
|
33644
34513
|
if (dryRun) {
|
|
@@ -33664,6 +34533,16 @@ Applying to ${projects.length} project(s)...`);
|
|
|
33664
34533
|
printOpenCodeEnvNote();
|
|
33665
34534
|
continue;
|
|
33666
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
|
+
}
|
|
33667
34546
|
if (editor === "kilo") {
|
|
33668
34547
|
const kiloPath = path8.join(projectPath, ".kilocode", "mcp.json");
|
|
33669
34548
|
if (dryRun) {
|
|
@@ -33695,7 +34574,6 @@ Applying to ${projects.length} project(s)...`);
|
|
|
33695
34574
|
for (const editor of selectedEditors) {
|
|
33696
34575
|
if (scope !== "project" && scope !== "both") continue;
|
|
33697
34576
|
if (!configuredEditors.includes(editor)) continue;
|
|
33698
|
-
if (editor === "opencode") continue;
|
|
33699
34577
|
const rule = generateRuleContent(editor, {
|
|
33700
34578
|
workspaceName,
|
|
33701
34579
|
workspaceId: workspaceId && workspaceId !== "dry-run" ? workspaceId : void 0,
|
|
@@ -33819,6 +34697,16 @@ Applying to ${projects.length} project(s)...`);
|
|
|
33819
34697
|
console.log(
|
|
33820
34698
|
"- For UI-based MCP setup (Cline/Kilo/Roo global), see https://contextstream.io/docs/mcp"
|
|
33821
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
|
+
}
|
|
33822
34710
|
console.log("");
|
|
33823
34711
|
console.log(
|
|
33824
34712
|
"You're all set! ContextStream gives your AI persistent memory, semantic code search, and cross-session context."
|