multicorn-shield 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -1
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -0
- package/dist/multicorn-proxy.js +527 -80
- package/dist/multicorn-shield.js +523 -79
- package/dist/shield-extension.js +8 -1
- package/package.json +4 -1
- package/plugins/opencode/multicorn-shield.ts +485 -0
package/dist/multicorn-shield.js
CHANGED
|
@@ -6,6 +6,7 @@ import { homedir } from 'os';
|
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import { createInterface } from 'readline';
|
|
9
|
+
import { parse, stringify } from 'yaml';
|
|
9
10
|
import { spawn } from 'child_process';
|
|
10
11
|
import { createHash } from 'crypto';
|
|
11
12
|
import 'stream';
|
|
@@ -938,6 +939,22 @@ require(${JSON.stringify(destPost)});
|
|
|
938
939
|
await writeFile(preWrapper, preContent, { encoding: "utf8", mode: 493 });
|
|
939
940
|
await writeFile(postWrapper, postContent, { encoding: "utf8", mode: 493 });
|
|
940
941
|
}
|
|
942
|
+
function getOpenCodeGlobalPluginsDir() {
|
|
943
|
+
return join(homedir(), ".config", "opencode", "plugins");
|
|
944
|
+
}
|
|
945
|
+
async function installOpenCodeNativePlugin() {
|
|
946
|
+
const root = multicornShieldPackageRoot();
|
|
947
|
+
const src = join(root, "plugins", "opencode", "multicorn-shield.ts");
|
|
948
|
+
if (!existsSync(src)) {
|
|
949
|
+
throw new Error(
|
|
950
|
+
`Could not find Shield OpenCode plugin at ${src}. If you use npm, install the latest multicorn-shield package.`
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
const destDir = getOpenCodeGlobalPluginsDir();
|
|
954
|
+
await mkdir(destDir, { recursive: true });
|
|
955
|
+
const dest = join(destDir, "multicorn-shield.ts");
|
|
956
|
+
await copyFile(src, dest);
|
|
957
|
+
}
|
|
941
958
|
async function promptClineIntegrationMode(ask) {
|
|
942
959
|
process.stderr.write("\n" + style.bold("Cline integration") + "\n");
|
|
943
960
|
process.stderr.write(
|
|
@@ -1297,6 +1314,23 @@ async function promptGeminiCliIntegrationMode(ask) {
|
|
|
1297
1314
|
}
|
|
1298
1315
|
return choice === 1 ? "native" : "hosted";
|
|
1299
1316
|
}
|
|
1317
|
+
async function promptOpencodeIntegrationMode(ask) {
|
|
1318
|
+
process.stderr.write("\n" + style.bold("OpenCode integration") + "\n");
|
|
1319
|
+
process.stderr.write(
|
|
1320
|
+
" " + style.violet("1") + ". Native plugin (recommended) - Shield checks primary-agent tool execution via OpenCode Hooks\n"
|
|
1321
|
+
);
|
|
1322
|
+
process.stderr.write(
|
|
1323
|
+
" " + style.violet("2") + ". Hosted proxy - govern MCP server traffic via opencode.json (full subagent coverage when tools use MCP through Shield)\n"
|
|
1324
|
+
);
|
|
1325
|
+
let choice = 0;
|
|
1326
|
+
while (choice === 0) {
|
|
1327
|
+
const input = await ask("Choose integration (1-2): ");
|
|
1328
|
+
const num = parseInt(input.trim(), 10);
|
|
1329
|
+
if (num === 1) choice = 1;
|
|
1330
|
+
if (num === 2) choice = 2;
|
|
1331
|
+
}
|
|
1332
|
+
return choice === 1 ? "native" : "hosted";
|
|
1333
|
+
}
|
|
1300
1334
|
function getClaudeDesktopConfigPath() {
|
|
1301
1335
|
switch (process.platform) {
|
|
1302
1336
|
case "win32":
|
|
@@ -1360,15 +1394,18 @@ function getClineMcpSettingsPath() {
|
|
|
1360
1394
|
);
|
|
1361
1395
|
}
|
|
1362
1396
|
}
|
|
1363
|
-
function getContinueConfigJsonPath() {
|
|
1364
|
-
return join(homedir(), ".continue", "config.json");
|
|
1365
|
-
}
|
|
1366
1397
|
var INIT_WIZARD_PLATFORM_REGISTRY = [
|
|
1367
1398
|
{ slug: "openclaw", displayName: "OpenClaw", section: "native" },
|
|
1368
1399
|
{ slug: "claude-code", displayName: "Claude Code", section: "native" },
|
|
1369
1400
|
{ slug: "windsurf", displayName: "Windsurf", section: "native" },
|
|
1370
1401
|
{ slug: "cline", displayName: "Cline", section: "native" },
|
|
1371
1402
|
{ slug: "gemini-cli", displayName: "Gemini CLI", section: "native" },
|
|
1403
|
+
{
|
|
1404
|
+
slug: "opencode",
|
|
1405
|
+
displayName: "OpenCode",
|
|
1406
|
+
section: "native",
|
|
1407
|
+
prereqUrl: "https://opencode.ai"
|
|
1408
|
+
},
|
|
1372
1409
|
{
|
|
1373
1410
|
slug: "cursor",
|
|
1374
1411
|
displayName: "Cursor",
|
|
@@ -1730,13 +1767,56 @@ function writeMcpAddedLine(shortName, filePath) {
|
|
|
1730
1767
|
style.green("\u2713") + ' MCP server "' + shortName + '" added to ' + style.cyan(filePath) + "\n"
|
|
1731
1768
|
);
|
|
1732
1769
|
}
|
|
1733
|
-
|
|
1770
|
+
function sanitiseYamlValue(value) {
|
|
1771
|
+
if (value.length === 0) {
|
|
1772
|
+
return "''";
|
|
1773
|
+
}
|
|
1774
|
+
const needsQuoting = /[:#\n{}]/.test(value) || value.includes("[") || value.includes("]") || value !== value.trim() || value.includes("'");
|
|
1775
|
+
if (!needsQuoting) {
|
|
1776
|
+
return value;
|
|
1777
|
+
}
|
|
1778
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
1779
|
+
}
|
|
1780
|
+
function gitignoreLikelyCoversPath(relPosixPath, gitignoreBody) {
|
|
1781
|
+
const norm = relPosixPath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1782
|
+
const lines = gitignoreBody.split(/\r?\n/);
|
|
1783
|
+
for (const raw of lines) {
|
|
1784
|
+
const line = raw.trim();
|
|
1785
|
+
if (!line || line.startsWith("#") || line.startsWith("!")) continue;
|
|
1786
|
+
const pat = line.replace(/^\//, "");
|
|
1787
|
+
if (pat === norm || pat === `./${norm}`) return true;
|
|
1788
|
+
if (!pat.includes("*")) {
|
|
1789
|
+
if (pat.endsWith("/")) {
|
|
1790
|
+
const dir = pat.slice(0, -1);
|
|
1791
|
+
if (norm === dir || norm.startsWith(`${dir}/`)) return true;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
return false;
|
|
1796
|
+
}
|
|
1797
|
+
async function warnIfApiKeyFileNotGitignored(workspaceRoot, relativePosixPath) {
|
|
1798
|
+
const gitignorePath = join(workspaceRoot, ".gitignore");
|
|
1799
|
+
let content;
|
|
1800
|
+
try {
|
|
1801
|
+
content = await readFile(gitignorePath, "utf8");
|
|
1802
|
+
} catch (e) {
|
|
1803
|
+
if (isErrnoException(e) && e.code === "ENOENT") return;
|
|
1804
|
+
throw e;
|
|
1805
|
+
}
|
|
1806
|
+
const norm = relativePosixPath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1807
|
+
if (gitignoreLikelyCoversPath(norm, content)) return;
|
|
1808
|
+
process.stderr.write(
|
|
1809
|
+
style.yellow("\u26A0") + " Config contains your API key. Add " + style.cyan(norm) + " to .gitignore to avoid committing credentials.\n"
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
async function mergeTopLevelKeyedJsonFile(filePath, topLevelKey, shortName, entry, options) {
|
|
1734
1813
|
let root = {};
|
|
1735
1814
|
try {
|
|
1736
1815
|
const raw = await readFile(filePath, "utf8");
|
|
1816
|
+
const toParse = options.stripJsonComments ? raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "") : raw;
|
|
1737
1817
|
let parsed;
|
|
1738
1818
|
try {
|
|
1739
|
-
parsed = JSON.parse(
|
|
1819
|
+
parsed = JSON.parse(toParse);
|
|
1740
1820
|
} catch {
|
|
1741
1821
|
return "parse-error";
|
|
1742
1822
|
}
|
|
@@ -1752,15 +1832,25 @@ async function mergeMcpServersObjectStyle(filePath, shortName, entry) {
|
|
|
1752
1832
|
throw e;
|
|
1753
1833
|
}
|
|
1754
1834
|
}
|
|
1755
|
-
const
|
|
1756
|
-
const
|
|
1757
|
-
|
|
1758
|
-
|
|
1835
|
+
const bucketRaw = root[topLevelKey];
|
|
1836
|
+
const bucket = typeof bucketRaw === "object" && bucketRaw !== null && !Array.isArray(bucketRaw) ? { ...bucketRaw } : {};
|
|
1837
|
+
if (options.onExisting === "skip" && bucket[shortName] !== void 0) {
|
|
1838
|
+
return "unchanged";
|
|
1839
|
+
}
|
|
1840
|
+
bucket[shortName] = entry;
|
|
1841
|
+
root[topLevelKey] = bucket;
|
|
1759
1842
|
await mkdir(dirname(filePath), { recursive: true });
|
|
1760
1843
|
await writeFile(filePath, JSON.stringify(root, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
1761
1844
|
writeMcpAddedLine(shortName, filePath);
|
|
1762
1845
|
return "ok";
|
|
1763
1846
|
}
|
|
1847
|
+
async function mergeMcpServersObjectStyle(filePath, shortName, entry) {
|
|
1848
|
+
const result = await mergeTopLevelKeyedJsonFile(filePath, "mcpServers", shortName, entry, {
|
|
1849
|
+
stripJsonComments: false,
|
|
1850
|
+
onExisting: "overwrite"
|
|
1851
|
+
});
|
|
1852
|
+
return result === "parse-error" ? "parse-error" : "ok";
|
|
1853
|
+
}
|
|
1764
1854
|
async function mergeClaudeDesktopHostedMcpRemote(shortName, proxyUrl, apiKey) {
|
|
1765
1855
|
const entry = {
|
|
1766
1856
|
command: "npx",
|
|
@@ -1768,8 +1858,30 @@ async function mergeClaudeDesktopHostedMcpRemote(shortName, proxyUrl, apiKey) {
|
|
|
1768
1858
|
};
|
|
1769
1859
|
return mergeMcpServersObjectStyle(getClaudeDesktopConfigPath(), shortName, entry);
|
|
1770
1860
|
}
|
|
1771
|
-
async function mergeContinueHostedMcp(shortName, proxyUrl, apiKey) {
|
|
1772
|
-
const
|
|
1861
|
+
async function mergeContinueHostedMcp(workspacePath, shortName, proxyUrl, apiKey) {
|
|
1862
|
+
const dir = join(workspacePath, ".continue", "mcpServers");
|
|
1863
|
+
const filePath = join(dir, `${shortName}.yaml`);
|
|
1864
|
+
const sn = sanitiseYamlValue(shortName);
|
|
1865
|
+
const urlEsc = sanitiseYamlValue(proxyUrl);
|
|
1866
|
+
const authEsc = sanitiseYamlValue(`Bearer ${apiKey}`);
|
|
1867
|
+
const yaml = `name: ${sn}
|
|
1868
|
+
version: 0.0.1
|
|
1869
|
+
schema: v1
|
|
1870
|
+
mcpServers:
|
|
1871
|
+
- name: ${sn}
|
|
1872
|
+
type: streamable-http
|
|
1873
|
+
url: ${urlEsc}
|
|
1874
|
+
headers:
|
|
1875
|
+
Authorization: ${authEsc}
|
|
1876
|
+
`;
|
|
1877
|
+
await mkdir(dir, { recursive: true });
|
|
1878
|
+
await writeFile(filePath, yaml, SECRET_JSON_FILE_OPTIONS);
|
|
1879
|
+
writeMcpAddedLine(shortName, filePath);
|
|
1880
|
+
await warnIfApiKeyFileNotGitignored(workspacePath, `.continue/mcpServers/${shortName}.yaml`);
|
|
1881
|
+
return "ok";
|
|
1882
|
+
}
|
|
1883
|
+
async function mergeCopilotVscodeMcp(workspacePath, shortName, proxyUrl, apiKey) {
|
|
1884
|
+
const filePath = join(workspacePath, ".vscode", "mcp.json");
|
|
1773
1885
|
let root = {};
|
|
1774
1886
|
try {
|
|
1775
1887
|
const raw = await readFile(filePath, "utf8");
|
|
@@ -1791,43 +1903,93 @@ async function mergeContinueHostedMcp(shortName, proxyUrl, apiKey) {
|
|
|
1791
1903
|
throw e;
|
|
1792
1904
|
}
|
|
1793
1905
|
}
|
|
1794
|
-
const
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
return "parse-error";
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
const servers = Array.isArray(rawServers) ? rawServers.map((s) => ({ ...s })) : [];
|
|
1803
|
-
const entry = {
|
|
1804
|
-
name: shortName,
|
|
1805
|
-
type: "streamable-http",
|
|
1906
|
+
const serversRaw = root["servers"];
|
|
1907
|
+
const servers = typeof serversRaw === "object" && serversRaw !== null && !Array.isArray(serversRaw) ? { ...serversRaw } : {};
|
|
1908
|
+
const existed = servers[shortName] !== void 0;
|
|
1909
|
+
servers[shortName] = {
|
|
1910
|
+
type: "http",
|
|
1806
1911
|
url: proxyUrl,
|
|
1807
|
-
headers: {
|
|
1808
|
-
Authorization: `Bearer ${apiKey}`
|
|
1809
|
-
}
|
|
1912
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1810
1913
|
};
|
|
1811
|
-
|
|
1812
|
-
if (idx >= 0) {
|
|
1813
|
-
servers[idx] = entry;
|
|
1814
|
-
} else {
|
|
1815
|
-
servers.push(entry);
|
|
1816
|
-
}
|
|
1817
|
-
root["mcpServers"] = servers;
|
|
1914
|
+
root["servers"] = servers;
|
|
1818
1915
|
await mkdir(dirname(filePath), { recursive: true });
|
|
1819
1916
|
await writeFile(filePath, JSON.stringify(root, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
1820
|
-
|
|
1917
|
+
if (existed) {
|
|
1918
|
+
process.stderr.write(
|
|
1919
|
+
style.dim(`Updated existing server entry for ${shortName} in .vscode/mcp.json`) + "\n"
|
|
1920
|
+
);
|
|
1921
|
+
} else {
|
|
1922
|
+
writeMcpAddedLine(shortName, filePath);
|
|
1923
|
+
}
|
|
1924
|
+
await warnIfApiKeyFileNotGitignored(workspacePath, ".vscode/mcp.json");
|
|
1821
1925
|
return "ok";
|
|
1822
1926
|
}
|
|
1823
1927
|
async function mergeKiloCodeProjectMcp(workspacePath, shortName, proxyUrl, apiKey) {
|
|
1824
|
-
const filePath = join(workspacePath, ".
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1928
|
+
const filePath = join(workspacePath, ".kilo", "kilo.jsonc");
|
|
1929
|
+
const result = await mergeTopLevelKeyedJsonFile(
|
|
1930
|
+
filePath,
|
|
1931
|
+
"mcp",
|
|
1932
|
+
shortName,
|
|
1933
|
+
{
|
|
1934
|
+
type: "remote",
|
|
1935
|
+
url: proxyUrl,
|
|
1936
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
1937
|
+
enabled: true
|
|
1938
|
+
},
|
|
1939
|
+
{
|
|
1940
|
+
stripJsonComments: true,
|
|
1941
|
+
onExisting: "skip"
|
|
1829
1942
|
}
|
|
1830
|
-
|
|
1943
|
+
);
|
|
1944
|
+
if (result === "parse-error") return "parse-error";
|
|
1945
|
+
if (result === "ok") {
|
|
1946
|
+
await warnIfApiKeyFileNotGitignored(workspacePath, ".kilo/kilo.jsonc");
|
|
1947
|
+
}
|
|
1948
|
+
return "ok";
|
|
1949
|
+
}
|
|
1950
|
+
var OPENCODE_CONFIG_SCHEMA_URL = "https://opencode.ai/config.json";
|
|
1951
|
+
async function injectOpencodeSchemaIntoConfigIfMissing(filePath) {
|
|
1952
|
+
try {
|
|
1953
|
+
const raw = await readFile(filePath, "utf8");
|
|
1954
|
+
const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
1955
|
+
let parsed;
|
|
1956
|
+
try {
|
|
1957
|
+
parsed = JSON.parse(stripped);
|
|
1958
|
+
} catch {
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return;
|
|
1962
|
+
const root = parsed;
|
|
1963
|
+
const existingSchema = root["$schema"];
|
|
1964
|
+
if (typeof existingSchema === "string" && existingSchema.length > 0) return;
|
|
1965
|
+
root["$schema"] = OPENCODE_CONFIG_SCHEMA_URL;
|
|
1966
|
+
await writeFile(filePath, JSON.stringify(root, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
1967
|
+
} catch {
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
async function mergeOpenCodeProjectMcp(workspacePath, shortName, proxyUrl, apiKey) {
|
|
1971
|
+
const filePath = join(workspacePath, "opencode.json");
|
|
1972
|
+
const result = await mergeTopLevelKeyedJsonFile(
|
|
1973
|
+
filePath,
|
|
1974
|
+
"mcp",
|
|
1975
|
+
shortName,
|
|
1976
|
+
{
|
|
1977
|
+
type: "remote",
|
|
1978
|
+
url: proxyUrl,
|
|
1979
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
1980
|
+
enabled: true
|
|
1981
|
+
},
|
|
1982
|
+
{
|
|
1983
|
+
stripJsonComments: true,
|
|
1984
|
+
onExisting: "skip"
|
|
1985
|
+
}
|
|
1986
|
+
);
|
|
1987
|
+
if (result === "parse-error") return "parse-error";
|
|
1988
|
+
await injectOpencodeSchemaIntoConfigIfMissing(filePath);
|
|
1989
|
+
if (result === "ok") {
|
|
1990
|
+
await warnIfApiKeyFileNotGitignored(workspacePath, "opencode.json");
|
|
1991
|
+
}
|
|
1992
|
+
return "ok";
|
|
1831
1993
|
}
|
|
1832
1994
|
function printHostedProxyJsonParseWarning(filePath) {
|
|
1833
1995
|
process.stderr.write(
|
|
@@ -1868,7 +2030,14 @@ function printHostedProxyPostWriteHints(platform, shortName) {
|
|
|
1868
2030
|
}
|
|
1869
2031
|
if (platform === "kilo-code") {
|
|
1870
2032
|
process.stderr.write(
|
|
1871
|
-
style.dim("Restart Kilo Code or reload the window so it picks up .
|
|
2033
|
+
style.dim("Restart Kilo Code or reload the window so it picks up .kilo/kilo.jsonc.") + "\n"
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
if (platform === "opencode") {
|
|
2037
|
+
process.stderr.write(
|
|
2038
|
+
style.dim(
|
|
2039
|
+
"Restart OpenCode or start a new session so it picks up opencode.json. For global MCP, merge the same snippet into ~/.config/opencode/opencode.json."
|
|
2040
|
+
) + "\n"
|
|
1872
2041
|
);
|
|
1873
2042
|
}
|
|
1874
2043
|
}
|
|
@@ -1885,15 +2054,40 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
|
|
|
1885
2054
|
return;
|
|
1886
2055
|
}
|
|
1887
2056
|
if (platform === "github-copilot") {
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
2057
|
+
try {
|
|
2058
|
+
const result = await mergeCopilotVscodeMcp(
|
|
2059
|
+
workspacePath,
|
|
2060
|
+
shortName,
|
|
2061
|
+
proxyUrlWithKeyWhenNeeded,
|
|
2062
|
+
apiKey
|
|
2063
|
+
);
|
|
2064
|
+
if (result === "ok") {
|
|
2065
|
+
printHostedProxyPostWriteHints(platform, shortName);
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
printHostedProxyJsonParseWarning(join(workspacePath, ".vscode", "mcp.json"));
|
|
2069
|
+
} catch (err) {
|
|
2070
|
+
process.stderr.write(
|
|
2071
|
+
`${style.yellow("!")} Could not auto-write config: ${err instanceof Error ? err.message : String(err)}
|
|
2072
|
+
`
|
|
2073
|
+
);
|
|
2074
|
+
}
|
|
1893
2075
|
printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
|
|
1894
2076
|
return;
|
|
1895
2077
|
}
|
|
1896
2078
|
if (platform === "goose") {
|
|
2079
|
+
try {
|
|
2080
|
+
const result = await mergeGooseConfig(shortName, proxyUrlWithKeyWhenNeeded, apiKey);
|
|
2081
|
+
if (result === "ok") {
|
|
2082
|
+
printHostedProxyPostWriteHints(platform, shortName);
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
} catch (err) {
|
|
2086
|
+
process.stderr.write(
|
|
2087
|
+
`${style.yellow("!")} Could not auto-write config: ${err instanceof Error ? err.message : String(err)}
|
|
2088
|
+
`
|
|
2089
|
+
);
|
|
2090
|
+
}
|
|
1897
2091
|
printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
|
|
1898
2092
|
return;
|
|
1899
2093
|
}
|
|
@@ -1940,12 +2134,29 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
|
|
|
1940
2134
|
apiKey
|
|
1941
2135
|
);
|
|
1942
2136
|
if (result === "parse-error") {
|
|
1943
|
-
printHostedProxyJsonParseWarning(join(workspacePath, ".
|
|
2137
|
+
printHostedProxyJsonParseWarning(join(workspacePath, ".kilo", "kilo.jsonc"));
|
|
2138
|
+
}
|
|
2139
|
+
} else if (platform === "opencode") {
|
|
2140
|
+
result = await mergeOpenCodeProjectMcp(
|
|
2141
|
+
workspacePath,
|
|
2142
|
+
shortName,
|
|
2143
|
+
proxyUrlWithKeyWhenNeeded,
|
|
2144
|
+
apiKey
|
|
2145
|
+
);
|
|
2146
|
+
if (result === "parse-error") {
|
|
2147
|
+
printHostedProxyJsonParseWarning(join(workspacePath, "opencode.json"));
|
|
1944
2148
|
}
|
|
1945
2149
|
} else if (platform === "continue-dev") {
|
|
1946
|
-
result = await mergeContinueHostedMcp(
|
|
2150
|
+
result = await mergeContinueHostedMcp(
|
|
2151
|
+
workspacePath,
|
|
2152
|
+
shortName,
|
|
2153
|
+
proxyUrlWithKeyWhenNeeded,
|
|
2154
|
+
apiKey
|
|
2155
|
+
);
|
|
1947
2156
|
if (result === "parse-error") {
|
|
1948
|
-
printHostedProxyJsonParseWarning(
|
|
2157
|
+
printHostedProxyJsonParseWarning(
|
|
2158
|
+
join(workspacePath, ".continue", "mcpServers", `${shortName}.yaml`)
|
|
2159
|
+
);
|
|
1949
2160
|
}
|
|
1950
2161
|
} else {
|
|
1951
2162
|
result = await mergeMcpServersObjectStyle(getCursorMcpJsonPath(), shortName, {
|
|
@@ -1969,16 +2180,95 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
|
|
|
1969
2180
|
}
|
|
1970
2181
|
printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
|
|
1971
2182
|
}
|
|
1972
|
-
function
|
|
1973
|
-
|
|
1974
|
-
|
|
2183
|
+
function printGooseConfigYamlParseErrorToStderr() {
|
|
2184
|
+
process.stderr.write(
|
|
2185
|
+
style.yellow("!") + " Could not parse ~/.config/goose/config.yaml - check for syntax errors or invalid YAML\n"
|
|
2186
|
+
);
|
|
2187
|
+
}
|
|
2188
|
+
function gooseExtensionYaml(shortName, proxyUrl, bearerHeader) {
|
|
2189
|
+
const sn = sanitiseYamlValue(shortName);
|
|
2190
|
+
const urlEsc = sanitiseYamlValue(proxyUrl);
|
|
2191
|
+
const authEsc = sanitiseYamlValue(bearerHeader);
|
|
2192
|
+
return ` ${sn}:
|
|
2193
|
+
enabled: true
|
|
1975
2194
|
type: streamable_http
|
|
1976
|
-
|
|
2195
|
+
name: ${sn}
|
|
2196
|
+
description: ''
|
|
2197
|
+
uri: ${urlEsc}
|
|
2198
|
+
envs: {}
|
|
2199
|
+
env_keys: []
|
|
1977
2200
|
headers:
|
|
1978
|
-
Authorization: ${
|
|
1979
|
-
enabled: true
|
|
2201
|
+
Authorization: ${authEsc}
|
|
1980
2202
|
timeout: 300
|
|
2203
|
+
socket: null
|
|
2204
|
+
bundled: null
|
|
2205
|
+
available_tools: []
|
|
2206
|
+
`;
|
|
2207
|
+
}
|
|
2208
|
+
function gooseHostedProxyYaml(shortName, proxyUrl, bearerHeader) {
|
|
2209
|
+
return `extensions:
|
|
2210
|
+
` + gooseExtensionYaml(shortName, proxyUrl, bearerHeader);
|
|
2211
|
+
}
|
|
2212
|
+
function isYamlPlainObject(value) {
|
|
2213
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2214
|
+
}
|
|
2215
|
+
async function mergeGooseConfig(shortName, proxyUrl, apiKey) {
|
|
2216
|
+
const filePath = join(homedir(), ".config", "goose", "config.yaml");
|
|
2217
|
+
const bearerHeader = `Bearer ${apiKey}`;
|
|
2218
|
+
let content = "";
|
|
2219
|
+
try {
|
|
2220
|
+
content = await readFile(filePath, "utf8");
|
|
2221
|
+
} catch (e) {
|
|
2222
|
+
if (isErrnoException(e) && e.code === "ENOENT") {
|
|
2223
|
+
content = "";
|
|
2224
|
+
} else {
|
|
2225
|
+
throw e;
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
let root;
|
|
2229
|
+
try {
|
|
2230
|
+
const data = content.trim().length === 0 ? {} : parse(content);
|
|
2231
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
2232
|
+
printGooseConfigYamlParseErrorToStderr();
|
|
2233
|
+
return "parse-error";
|
|
2234
|
+
}
|
|
2235
|
+
root = data;
|
|
2236
|
+
} catch {
|
|
2237
|
+
printGooseConfigYamlParseErrorToStderr();
|
|
2238
|
+
return "parse-error";
|
|
2239
|
+
}
|
|
2240
|
+
const extensionsRaw = root["extensions"];
|
|
2241
|
+
let extensions;
|
|
2242
|
+
if (isYamlPlainObject(extensionsRaw)) {
|
|
2243
|
+
extensions = { ...extensionsRaw };
|
|
2244
|
+
} else if (extensionsRaw === void 0) {
|
|
2245
|
+
extensions = {};
|
|
2246
|
+
} else {
|
|
2247
|
+
printGooseConfigYamlParseErrorToStderr();
|
|
2248
|
+
return "parse-error";
|
|
2249
|
+
}
|
|
2250
|
+
extensions[shortName] = {
|
|
2251
|
+
enabled: true,
|
|
2252
|
+
type: "streamable_http",
|
|
2253
|
+
name: shortName,
|
|
2254
|
+
description: "",
|
|
2255
|
+
uri: proxyUrl,
|
|
2256
|
+
envs: {},
|
|
2257
|
+
env_keys: [],
|
|
2258
|
+
headers: { Authorization: bearerHeader },
|
|
2259
|
+
timeout: 300,
|
|
2260
|
+
socket: null,
|
|
2261
|
+
bundled: null,
|
|
2262
|
+
available_tools: []
|
|
2263
|
+
};
|
|
2264
|
+
root["extensions"] = extensions;
|
|
2265
|
+
const out = stringify(root, { indent: 2, lineWidth: 0 });
|
|
2266
|
+
const body = out.endsWith("\n") ? out : `${out}
|
|
1981
2267
|
`;
|
|
2268
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
2269
|
+
await writeFile(filePath, body, SECRET_JSON_FILE_OPTIONS);
|
|
2270
|
+
writeMcpAddedLine(shortName, filePath);
|
|
2271
|
+
return "ok";
|
|
1982
2272
|
}
|
|
1983
2273
|
function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
1984
2274
|
const hostedInlinePlatforms = /* @__PURE__ */ new Set([
|
|
@@ -1990,7 +2280,8 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
1990
2280
|
"kilo-code",
|
|
1991
2281
|
"github-copilot",
|
|
1992
2282
|
"continue-dev",
|
|
1993
|
-
"goose"
|
|
2283
|
+
"goose",
|
|
2284
|
+
"opencode"
|
|
1994
2285
|
]);
|
|
1995
2286
|
const usesInlineKey = hostedInlinePlatforms.has(platform);
|
|
1996
2287
|
const authHeader = usesInlineKey ? `Bearer ${apiKey}` : "Bearer YOUR_SHIELD_API_KEY";
|
|
@@ -1999,14 +2290,12 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
1999
2290
|
if (platform === "github-copilot") {
|
|
2000
2291
|
snippetText = JSON.stringify(
|
|
2001
2292
|
{
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
Authorization: authHeader
|
|
2009
|
-
}
|
|
2293
|
+
servers: {
|
|
2294
|
+
[shortName]: {
|
|
2295
|
+
type: "http",
|
|
2296
|
+
url: urlInSnippet,
|
|
2297
|
+
headers: {
|
|
2298
|
+
Authorization: authHeader
|
|
2010
2299
|
}
|
|
2011
2300
|
}
|
|
2012
2301
|
}
|
|
@@ -2045,18 +2334,50 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
2045
2334
|
2
|
|
2046
2335
|
);
|
|
2047
2336
|
} else if (platform === "continue-dev") {
|
|
2337
|
+
const sn = sanitiseYamlValue(shortName);
|
|
2338
|
+
const urlEsc = sanitiseYamlValue(urlInSnippet);
|
|
2339
|
+
const authEsc = sanitiseYamlValue(authHeader);
|
|
2340
|
+
snippetText = `name: ${sn}
|
|
2341
|
+
version: 0.0.1
|
|
2342
|
+
schema: v1
|
|
2343
|
+
mcpServers:
|
|
2344
|
+
- name: ${sn}
|
|
2345
|
+
type: streamable-http
|
|
2346
|
+
url: ${urlEsc}
|
|
2347
|
+
headers:
|
|
2348
|
+
Authorization: ${authEsc}
|
|
2349
|
+
`;
|
|
2350
|
+
} else if (platform === "kilo-code") {
|
|
2048
2351
|
snippetText = JSON.stringify(
|
|
2049
2352
|
{
|
|
2050
|
-
|
|
2051
|
-
{
|
|
2052
|
-
|
|
2053
|
-
type: "streamable-http",
|
|
2353
|
+
mcp: {
|
|
2354
|
+
[shortName]: {
|
|
2355
|
+
type: "remote",
|
|
2054
2356
|
url: urlInSnippet,
|
|
2055
2357
|
headers: {
|
|
2056
2358
|
Authorization: authHeader
|
|
2057
|
-
}
|
|
2359
|
+
},
|
|
2360
|
+
enabled: true
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
},
|
|
2364
|
+
null,
|
|
2365
|
+
2
|
|
2366
|
+
);
|
|
2367
|
+
} else if (platform === "opencode") {
|
|
2368
|
+
snippetText = JSON.stringify(
|
|
2369
|
+
{
|
|
2370
|
+
$schema: OPENCODE_CONFIG_SCHEMA_URL,
|
|
2371
|
+
mcp: {
|
|
2372
|
+
[shortName]: {
|
|
2373
|
+
type: "remote",
|
|
2374
|
+
url: urlInSnippet,
|
|
2375
|
+
headers: {
|
|
2376
|
+
Authorization: authHeader
|
|
2377
|
+
},
|
|
2378
|
+
enabled: true
|
|
2058
2379
|
}
|
|
2059
|
-
|
|
2380
|
+
}
|
|
2060
2381
|
},
|
|
2061
2382
|
null,
|
|
2062
2383
|
2
|
|
@@ -2096,16 +2417,24 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
2096
2417
|
);
|
|
2097
2418
|
} else if (platform === "kilo-code") {
|
|
2098
2419
|
process.stderr.write(
|
|
2099
|
-
"\n" + style.dim(`Add this to ${join(resolve(process.cwd()), ".
|
|
2420
|
+
"\n" + style.dim(`Add this to ${join(resolve(process.cwd()), ".kilo", "kilo.jsonc")}:`) + "\n\n"
|
|
2421
|
+
);
|
|
2422
|
+
} else if (platform === "opencode") {
|
|
2423
|
+
process.stderr.write(
|
|
2424
|
+
"\n" + style.dim(
|
|
2425
|
+
"Add this to opencode.json in your project root (or ~/.config/opencode/opencode.json for global config). OpenCode detects configured MCP servers on the next session start."
|
|
2426
|
+
) + "\n\n"
|
|
2100
2427
|
);
|
|
2101
2428
|
} else if (platform === "github-copilot") {
|
|
2102
2429
|
process.stderr.write(
|
|
2103
2430
|
"\n" + style.dim(
|
|
2104
|
-
"
|
|
2431
|
+
"Create .vscode/mcp.json in your workspace root (create the .vscode folder if it does not exist). After saving, reload VS Code and confirm the server appears in Copilot Agent mode under Tools."
|
|
2105
2432
|
) + "\n\n"
|
|
2106
2433
|
);
|
|
2107
2434
|
} else if (platform === "continue-dev") {
|
|
2108
|
-
process.stderr.write(
|
|
2435
|
+
process.stderr.write(
|
|
2436
|
+
"\n" + style.dim(`Save this as .continue/mcpServers/${shortName}.yaml in your workspace root.`) + "\n\n"
|
|
2437
|
+
);
|
|
2109
2438
|
} else if (platform === "goose") {
|
|
2110
2439
|
process.stderr.write(
|
|
2111
2440
|
"\n" + style.dim("Add this to ~/.config/goose/config.yaml under the extensions key.") + "\n\n"
|
|
@@ -2159,6 +2488,11 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
2159
2488
|
if (platform === "goose") {
|
|
2160
2489
|
process.stderr.write(style.dim("Start a new Goose session after updating config.") + "\n");
|
|
2161
2490
|
}
|
|
2491
|
+
if (platform === "opencode") {
|
|
2492
|
+
process.stderr.write(
|
|
2493
|
+
style.dim("Restart OpenCode or start a new session after saving opencode.json.") + "\n"
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2162
2496
|
}
|
|
2163
2497
|
function agentDisplayNameDedupeKey(name) {
|
|
2164
2498
|
return name.trim().normalize("NFKC").toLowerCase();
|
|
@@ -2282,7 +2616,7 @@ async function runInit(explicitBaseUrl, options) {
|
|
|
2282
2616
|
}
|
|
2283
2617
|
await warnIfInstalledShieldIsOutdated();
|
|
2284
2618
|
const configuredAgents = [];
|
|
2285
|
-
let currentAgents = collectAgentsFromConfig(existing);
|
|
2619
|
+
let currentAgents = mergeAgentsForUniqueNames(collectAgentsFromConfig(existing));
|
|
2286
2620
|
let lastConfig = {
|
|
2287
2621
|
apiKey,
|
|
2288
2622
|
baseUrl: resolvedBaseUrl,
|
|
@@ -2719,6 +3053,95 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2719
3053
|
setupSucceeded = true;
|
|
2720
3054
|
}
|
|
2721
3055
|
}
|
|
3056
|
+
} else if (selectedPlatform === "opencode") {
|
|
3057
|
+
const opencodeMode = await promptOpencodeIntegrationMode(ask);
|
|
3058
|
+
if (opencodeMode === "native") {
|
|
3059
|
+
try {
|
|
3060
|
+
await installOpenCodeNativePlugin();
|
|
3061
|
+
process.stderr.write("\n" + style.bold("Shield OpenCode plugin installed") + "\n\n");
|
|
3062
|
+
process.stderr.write(
|
|
3063
|
+
style.dim("Plugin file: ") + style.cyan(getOpenCodeGlobalPluginsDir()) + "\n"
|
|
3064
|
+
);
|
|
3065
|
+
process.stderr.write("\n");
|
|
3066
|
+
process.stderr.write(
|
|
3067
|
+
style.dim(
|
|
3068
|
+
"Shield plugin saved under ~/.config/opencode/plugins/. Restart OpenCode. Every tool call from the primary agent will be checked by Shield."
|
|
3069
|
+
) + "\n"
|
|
3070
|
+
);
|
|
3071
|
+
process.stderr.write("\n");
|
|
3072
|
+
process.stderr.write(
|
|
3073
|
+
style.dim(
|
|
3074
|
+
"Note: Tool calls delegated to subagents via the task tool are not intercepted by this plugin (OpenCode limitation). Use the hosted proxy path for MCP traffic you route through Shield for broader coverage."
|
|
3075
|
+
) + "\n"
|
|
3076
|
+
);
|
|
3077
|
+
configuredAgents.push({
|
|
3078
|
+
selection,
|
|
3079
|
+
platform: selectedPlatform,
|
|
3080
|
+
platformLabel: selectedLabel,
|
|
3081
|
+
agentName,
|
|
3082
|
+
opencodeCliIntegration: "native"
|
|
3083
|
+
});
|
|
3084
|
+
setupSucceeded = true;
|
|
3085
|
+
} catch (error) {
|
|
3086
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
3087
|
+
process.stderr.write(style.red("\u2717 ") + detail + "\n");
|
|
3088
|
+
}
|
|
3089
|
+
} else {
|
|
3090
|
+
const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
|
|
3091
|
+
let proxyUrl = "";
|
|
3092
|
+
let created = false;
|
|
3093
|
+
while (!created) {
|
|
3094
|
+
const spinner = withSpinner("Creating proxy config...");
|
|
3095
|
+
try {
|
|
3096
|
+
proxyUrl = await createProxyConfig(
|
|
3097
|
+
resolvedBaseUrl,
|
|
3098
|
+
apiKey,
|
|
3099
|
+
agentName,
|
|
3100
|
+
targetUrl,
|
|
3101
|
+
shortName,
|
|
3102
|
+
selectedPlatform,
|
|
3103
|
+
upstreamHeaders
|
|
3104
|
+
);
|
|
3105
|
+
spinner.stop(true, "Proxy config created!");
|
|
3106
|
+
created = true;
|
|
3107
|
+
} catch (error) {
|
|
3108
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
3109
|
+
spinner.stop(false, detail);
|
|
3110
|
+
const retry = await ask("Try again? (Y/n) ");
|
|
3111
|
+
if (retry.trim().toLowerCase() === "n") {
|
|
3112
|
+
break;
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
if (created && proxyUrl.length > 0) {
|
|
3117
|
+
process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
|
|
3118
|
+
process.stderr.write(
|
|
3119
|
+
" " + style.cyan(formatHostedProxyUrlForStderr(selectedPlatform, proxyUrl, apiKey)) + "\n"
|
|
3120
|
+
);
|
|
3121
|
+
await applyHostedProxyMcpConfig(
|
|
3122
|
+
selectedPlatform,
|
|
3123
|
+
proxyUrl,
|
|
3124
|
+
shortName,
|
|
3125
|
+
apiKey,
|
|
3126
|
+
initWorkspacePath
|
|
3127
|
+
);
|
|
3128
|
+
process.stderr.write(
|
|
3129
|
+
"\n" + style.dim(
|
|
3130
|
+
"Add this to opencode.json in your project root (or ~/.config/opencode/opencode.json for global config). OpenCode detects configured MCP servers on the next session start."
|
|
3131
|
+
) + "\n"
|
|
3132
|
+
);
|
|
3133
|
+
configuredAgents.push({
|
|
3134
|
+
selection,
|
|
3135
|
+
platform: selectedPlatform,
|
|
3136
|
+
platformLabel: selectedLabel,
|
|
3137
|
+
agentName,
|
|
3138
|
+
shortName,
|
|
3139
|
+
proxyUrl,
|
|
3140
|
+
opencodeCliIntegration: "hosted"
|
|
3141
|
+
});
|
|
3142
|
+
setupSucceeded = true;
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
2722
3145
|
} else if (selectedPlatform === "cline") {
|
|
2723
3146
|
const clineMode = await promptClineIntegrationMode(ask);
|
|
2724
3147
|
if (clineMode === "native") {
|
|
@@ -2943,23 +3366,27 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2943
3366
|
);
|
|
2944
3367
|
}
|
|
2945
3368
|
if (configuredPlatforms.has("kilo-code")) {
|
|
3369
|
+
const kiloLabel = mcpPromptLabel2("kilo-code");
|
|
2946
3370
|
blocks.push(
|
|
2947
|
-
"\n" + style.bold("Kilo Code") +
|
|
3371
|
+
"\n" + style.bold("Kilo Code") + '\n \u2192 Restart the editor or reload the window if the MCP server does not appear\n \u2192 Confirm connection: Settings \u2192 Agent Behaviour \u2192 MCP Servers\n \u2192 Try it: paste this into Kilo Code:\n "Use the ' + kiloLabel + ' MCP server to list my GitHub repositories"\n'
|
|
2948
3372
|
);
|
|
2949
3373
|
}
|
|
2950
3374
|
if (configuredPlatforms.has("github-copilot")) {
|
|
3375
|
+
const copilotLabel = mcpPromptLabel2("github-copilot");
|
|
2951
3376
|
blocks.push(
|
|
2952
|
-
"\n" + style.bold("GitHub Copilot") +
|
|
3377
|
+
"\n" + style.bold("GitHub Copilot") + '\n \u2192 Reload the editor window if the MCP server does not appear\n \u2192 Confirm connection: open Copilot chat in Agent mode and confirm the server appears under Tools\n \u2192 Try it: paste this into GitHub Copilot:\n "Use the ' + copilotLabel + ' MCP server to list my GitHub repositories"\n'
|
|
2953
3378
|
);
|
|
2954
3379
|
}
|
|
2955
3380
|
if (configuredPlatforms.has("continue-dev")) {
|
|
3381
|
+
const continueLabel = mcpPromptLabel2("continue-dev");
|
|
2956
3382
|
blocks.push(
|
|
2957
|
-
"\n" + style.bold("Continue") + "\n \u2192 If needed, install Continue from " + style.cyan("https://docs.continue.dev/ide-extensions/install") +
|
|
3383
|
+
"\n" + style.bold("Continue") + "\n \u2192 If needed, install Continue from " + style.cyan("https://docs.continue.dev/ide-extensions/install") + '\n \u2192 Reload VS Code and open Continue agent mode\n \u2192 Confirm connection: Settings \u2192 Tools \u2192 MCP Servers\n \u2192 Try it: paste this into Continue:\n "Use the ' + continueLabel + ' MCP server to list my GitHub repositories"\n'
|
|
2958
3384
|
);
|
|
2959
3385
|
}
|
|
2960
3386
|
if (configuredPlatforms.has("goose")) {
|
|
3387
|
+
const gooseLabel = mcpPromptLabel2("goose");
|
|
2961
3388
|
blocks.push(
|
|
2962
|
-
"\n" + style.bold("Goose") +
|
|
3389
|
+
"\n" + style.bold("Goose") + '\n \u2192 Start a new Goose session after updating config\n \u2192 Confirm connection: check the Extensions page in the sidebar\n \u2192 Try it: paste this into Goose:\n "Use the ' + gooseLabel + ' MCP server to list my GitHub repositories"\n'
|
|
2963
3390
|
);
|
|
2964
3391
|
}
|
|
2965
3392
|
const windsurfNativeConfigured = configuredAgents.some(
|
|
@@ -3010,6 +3437,23 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3010
3437
|
"\n" + style.bold("Gemini CLI (hosted)") + "\n \u2192 Try it: make a request in Gemini CLI - Shield will intercept the first tool call and ask for your consent\n"
|
|
3011
3438
|
);
|
|
3012
3439
|
}
|
|
3440
|
+
const opencodeNativeConfigured = configuredAgents.some(
|
|
3441
|
+
(a) => a.platform === "opencode" && a.opencodeCliIntegration === "native"
|
|
3442
|
+
);
|
|
3443
|
+
const opencodeHostedConfigured = configuredAgents.some(
|
|
3444
|
+
(a) => a.platform === "opencode" && a.opencodeCliIntegration === "hosted"
|
|
3445
|
+
);
|
|
3446
|
+
if (opencodeNativeConfigured) {
|
|
3447
|
+
blocks.push(
|
|
3448
|
+
"\n" + style.bold("OpenCode (native)") + "\n \u2192 If you just installed OpenCode, open a new terminal tab (or run: source ~/.zshrc)\n \u2192 Restart OpenCode so it loads ~/.config/opencode/plugins/multicorn-shield.ts\n \u2192 Try it: trigger a primary-agent tool call - Shield will intercept the first actionable tool and ask for your consent\n"
|
|
3449
|
+
);
|
|
3450
|
+
}
|
|
3451
|
+
if (opencodeHostedConfigured) {
|
|
3452
|
+
const ocLabel = mcpPromptLabel2("opencode");
|
|
3453
|
+
blocks.push(
|
|
3454
|
+
"\n" + style.bold("OpenCode (hosted)") + '\n \u2192 Restart OpenCode or start a new session after saving opencode.json\n \u2192 Try it: paste this into OpenCode:\n "Use the ' + ocLabel + ' MCP server to list allowed directories"\n'
|
|
3455
|
+
);
|
|
3456
|
+
}
|
|
3013
3457
|
if (configuredPlatforms.has("other-mcp")) {
|
|
3014
3458
|
blocks.push(
|
|
3015
3459
|
"\n" + style.bold("Local MCP / Other") + "\n \u2192 Run your configured wrap command (for example " + style.cyan("npx multicorn-shield --wrap ...") + ")\n \u2192 Try it: make a request in your coding agent - Shield will intercept the first tool call and ask for your consent\n"
|
|
@@ -3915,7 +4359,7 @@ async function restoreClaudeDesktopMcpFromBackup() {
|
|
|
3915
4359
|
|
|
3916
4360
|
// package.json
|
|
3917
4361
|
var package_default = {
|
|
3918
|
-
version: "1.
|
|
4362
|
+
version: "1.8.0"};
|
|
3919
4363
|
|
|
3920
4364
|
// src/package-meta.ts
|
|
3921
4365
|
var PACKAGE_VERSION = package_default.version;
|