@envseal/cli 0.1.4 → 0.1.5
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/dist/bin.js +2 -2
- package/dist/cli-utils.js +4 -4
- package/dist/commands/doctor.js +19 -2
- package/dist/commands/init.js +70 -42
- package/dist/host-wiring/agents-md-content.d.ts +9 -0
- package/dist/host-wiring/agents-md-content.js +73 -0
- package/dist/host-wiring/agents-md.d.ts +20 -0
- package/dist/host-wiring/agents-md.js +50 -0
- package/dist/host-wiring/aider-conf.d.ts +8 -0
- package/dist/host-wiring/aider-conf.js +37 -0
- package/dist/host-wiring/aider.d.ts +14 -0
- package/dist/host-wiring/aider.js +90 -0
- package/dist/host-wiring/apply.d.ts +22 -0
- package/dist/host-wiring/apply.js +165 -0
- package/dist/host-wiring/codex.d.ts +8 -0
- package/dist/host-wiring/codex.js +57 -0
- package/dist/host-wiring/continue.d.ts +9 -0
- package/dist/host-wiring/continue.js +54 -0
- package/dist/host-wiring/copilot.d.ts +10 -0
- package/dist/host-wiring/copilot.js +99 -0
- package/dist/host-wiring/cursor-rules.d.ts +9 -0
- package/dist/host-wiring/cursor-rules.js +31 -0
- package/dist/host-wiring/cursor.d.ts +31 -0
- package/dist/host-wiring/cursor.js +39 -0
- package/dist/host-wiring/goose.d.ts +16 -0
- package/dist/host-wiring/goose.js +57 -0
- package/dist/host-wiring/inspect.d.ts +24 -0
- package/dist/host-wiring/inspect.js +102 -0
- package/dist/host-wiring/mcp.d.ts +55 -0
- package/dist/host-wiring/mcp.js +238 -0
- package/dist/host-wiring/zed.d.ts +10 -0
- package/dist/host-wiring/zed.js +97 -0
- package/dist/host.d.ts +28 -0
- package/dist/host.js +131 -16
- package/package.json +8 -8
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { mergeAiderConf } from './aider.js';
|
|
4
|
+
import { mergeAgentsMd } from './agents-md.js';
|
|
5
|
+
import { writeCodexProjectConfig } from './codex.js';
|
|
6
|
+
import { writeContinueProjectFiles, continueSnippetYaml } from './continue.js';
|
|
7
|
+
import { mergeCopilotSettings } from './copilot.js';
|
|
8
|
+
import { writeCursorHostFiles } from './cursor.js';
|
|
9
|
+
import { writeGooseMarker, gooseYamlSnippet } from './goose.js';
|
|
10
|
+
import { mergeMcpServersFile } from './mcp.js';
|
|
11
|
+
import { mergeZedSettings } from './zed.js';
|
|
12
|
+
const RELOAD = 'Reload MCP / restart the host, then run `envseal doctor`.';
|
|
13
|
+
function mcpServersWrite(id, path, relative, platform, extraHint) {
|
|
14
|
+
const result = mergeMcpServersFile(path, platform);
|
|
15
|
+
const skipped = result.action === 'skipped'
|
|
16
|
+
? ` Could not merge ${relative} (invalid JSON). Fix the file and re-run envseal init.`
|
|
17
|
+
: '';
|
|
18
|
+
return {
|
|
19
|
+
id,
|
|
20
|
+
action: result.action,
|
|
21
|
+
path: result.path,
|
|
22
|
+
hint: skipped || `${extraHint ?? ''} ${RELOAD}`.trim(),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function applyHostWiring(root, hostIds, platform = process.platform) {
|
|
26
|
+
const agentsMd = mergeAgentsMd(root);
|
|
27
|
+
const hosts = [];
|
|
28
|
+
let cursor;
|
|
29
|
+
const unique = [...new Set(hostIds)];
|
|
30
|
+
const writesMcpHost = unique.some((id) => [
|
|
31
|
+
'cursor',
|
|
32
|
+
'claude-code',
|
|
33
|
+
'windsurf',
|
|
34
|
+
'cline',
|
|
35
|
+
'zed',
|
|
36
|
+
'jetbrains',
|
|
37
|
+
'copilot',
|
|
38
|
+
'continue',
|
|
39
|
+
'codex',
|
|
40
|
+
'goose',
|
|
41
|
+
'aider',
|
|
42
|
+
].includes(id));
|
|
43
|
+
for (const id of unique) {
|
|
44
|
+
switch (id) {
|
|
45
|
+
case 'cursor': {
|
|
46
|
+
cursor = writeCursorHostFiles(root, platform);
|
|
47
|
+
hosts.push({
|
|
48
|
+
id,
|
|
49
|
+
action: cursor.mcp,
|
|
50
|
+
path: cursor.mcpPath,
|
|
51
|
+
hint: cursor.mcp === 'skipped'
|
|
52
|
+
? 'Could not merge .cursor/mcp.json (invalid JSON). Fix the file and re-run envseal init.'
|
|
53
|
+
: cursor.reloadHint,
|
|
54
|
+
extra: { mcp: cursor.mcp, rules: cursor.rules, rulesPath: cursor.rulesPath },
|
|
55
|
+
});
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case 'claude-code': {
|
|
59
|
+
mkdirSync(join(root, '.claude'), { recursive: true });
|
|
60
|
+
hosts.push(mcpServersWrite(id, join(root, '.mcp.json'), '.mcp.json', platform, 'Wrote project .mcp.json (Tier B protocol). Restart Claude Code so it picks up envseal-mcp. Install plugins/claude-code for Tier A hooks — doctor reports A only when hooks are visible.'));
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case 'windsurf': {
|
|
64
|
+
hosts.push(mcpServersWrite(id, join(root, '.windsurf', 'mcp_config.json'), '.windsurf/mcp_config.json', platform, '[VERIFY] Windsurf MCP schema has shifted across releases.'));
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case 'cline': {
|
|
68
|
+
hosts.push(mcpServersWrite(id, join(root, '.cline', 'mcp_settings.json'), '.cline/mcp_settings.json', platform, '[VERIFY] Cline MCP schema.'));
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case 'zed': {
|
|
72
|
+
const result = mergeZedSettings(root, platform);
|
|
73
|
+
hosts.push({
|
|
74
|
+
id,
|
|
75
|
+
action: result.action,
|
|
76
|
+
path: result.path,
|
|
77
|
+
hint: result.action === 'skipped'
|
|
78
|
+
? 'Could not merge .zed/settings.json (invalid JSON). Fix the file and re-run envseal init. [VERIFY]'
|
|
79
|
+
: `[VERIFY] Zed MCP key shape. ${RELOAD}`,
|
|
80
|
+
});
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case 'jetbrains': {
|
|
84
|
+
hosts.push(mcpServersWrite(id, join(root, '.idea', 'mcp.json'), '.idea/mcp.json', platform, '[VERIFY] JetBrains MCP location varies by product.'));
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case 'copilot': {
|
|
88
|
+
const result = mergeCopilotSettings(root, platform);
|
|
89
|
+
hosts.push({
|
|
90
|
+
id,
|
|
91
|
+
action: result.action,
|
|
92
|
+
path: result.path,
|
|
93
|
+
hint: result.action === 'skipped'
|
|
94
|
+
? 'Could not merge .vscode/settings.json (invalid JSON). Fix the file and re-run envseal init. [VERIFY]'
|
|
95
|
+
: `[VERIFY] github.copilot.mcp schema. ${RELOAD}`,
|
|
96
|
+
});
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case 'continue': {
|
|
100
|
+
const result = writeContinueProjectFiles(root, platform);
|
|
101
|
+
hosts.push({
|
|
102
|
+
id,
|
|
103
|
+
action: result.action,
|
|
104
|
+
path: result.path,
|
|
105
|
+
hint: `${result.printOnlyHint}\n\nMerge into ~/.continue/config.yaml:\n${continueSnippetYaml(platform)}`,
|
|
106
|
+
});
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'codex': {
|
|
110
|
+
const result = writeCodexProjectConfig(root, platform);
|
|
111
|
+
hosts.push({
|
|
112
|
+
id,
|
|
113
|
+
action: result.action,
|
|
114
|
+
path: result.path,
|
|
115
|
+
hint: `[VERIFY] Codex may still read ~/.codex/config.toml; envseal does not write $HOME. ${RELOAD}`,
|
|
116
|
+
});
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case 'goose': {
|
|
120
|
+
const result = writeGooseMarker(root);
|
|
121
|
+
hosts.push({
|
|
122
|
+
id,
|
|
123
|
+
action: 'printed',
|
|
124
|
+
path: result.path,
|
|
125
|
+
hint: `Goose is not OOTB. ${result.addHint}\n\nOr merge (unverified yaml):\n${gooseYamlSnippet(platform)}`,
|
|
126
|
+
});
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case 'aider': {
|
|
130
|
+
const result = mergeAiderConf(root);
|
|
131
|
+
hosts.push({
|
|
132
|
+
id,
|
|
133
|
+
action: result.action,
|
|
134
|
+
path: result.path,
|
|
135
|
+
hint: 'Merged Aider config so .env is not on the read list. Use /run envseal ensure.',
|
|
136
|
+
});
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case 'generic':
|
|
140
|
+
case 'unknown':
|
|
141
|
+
case 'openhands':
|
|
142
|
+
hosts.push({
|
|
143
|
+
id,
|
|
144
|
+
action: 'printed',
|
|
145
|
+
hint: id === 'openhands'
|
|
146
|
+
? 'OpenHands is deployment-dependent (binary inside sandbox, TTY for ensure). Layer 1 AGENTS.md is the working path.'
|
|
147
|
+
: 'Layer 1 only (AGENTS.md). Any agent that reads instruction files can run envseal ensure / envseal run --.',
|
|
148
|
+
});
|
|
149
|
+
break;
|
|
150
|
+
default:
|
|
151
|
+
hosts.push({
|
|
152
|
+
id,
|
|
153
|
+
action: 'printed',
|
|
154
|
+
hint: 'Unknown host: Layer 1 AGENTS.md only. Re-run from the IDE or `envseal init --host <name>`.',
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
agentsMd,
|
|
160
|
+
hosts,
|
|
161
|
+
cursor,
|
|
162
|
+
bareTerminal: !writesMcpHost,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type McpInspection, type McpWriteAction } from './mcp.js';
|
|
2
|
+
export declare function codexTomlBlock(platform?: NodeJS.Platform): string;
|
|
3
|
+
export declare function writeCodexProjectConfig(root: string, platform?: NodeJS.Platform): {
|
|
4
|
+
action: McpWriteAction;
|
|
5
|
+
path: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function inspectCodexProject(root: string): McpInspection;
|
|
8
|
+
//# sourceMappingURL=codex.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { mcpLaunch } from './mcp.js';
|
|
4
|
+
export function codexTomlBlock(platform = process.platform) {
|
|
5
|
+
const launch = mcpLaunch(platform);
|
|
6
|
+
const args = launch.args.map((a) => `"${a}"`).join(', ');
|
|
7
|
+
return `[mcp_servers.envseal-mcp]
|
|
8
|
+
command = "${launch.command}"
|
|
9
|
+
args = [${args}]
|
|
10
|
+
`;
|
|
11
|
+
}
|
|
12
|
+
export function writeCodexProjectConfig(root, platform = process.platform) {
|
|
13
|
+
const path = join(root, '.codex', 'config.toml');
|
|
14
|
+
const block = `# envseal MCP — [VERIFY] Codex schema is still moving. Prefer project
|
|
15
|
+
# .codex/ over ~/.codex/config.toml (global cwd is $HOME).
|
|
16
|
+
${codexTomlBlock(platform)}`;
|
|
17
|
+
if (!existsSync(path)) {
|
|
18
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
19
|
+
writeFileSync(path, block, 'utf8');
|
|
20
|
+
return { action: 'created', path };
|
|
21
|
+
}
|
|
22
|
+
const existing = readFileSync(path, 'utf8');
|
|
23
|
+
if (/mcp_servers\.envseal-mcp|envseal-mcp/.test(existing)) {
|
|
24
|
+
return { action: 'unchanged', path };
|
|
25
|
+
}
|
|
26
|
+
const trimmed = existing.replace(/\s+$/u, '');
|
|
27
|
+
writeFileSync(path, `${trimmed}\n\n${block}`, 'utf8');
|
|
28
|
+
return { action: 'merged', path };
|
|
29
|
+
}
|
|
30
|
+
export function inspectCodexProject(root) {
|
|
31
|
+
const path = join(root, '.codex', 'config.toml');
|
|
32
|
+
const hint = 'Run `envseal init` to write project .codex/config.toml. [VERIFY] Codex may still only read ~/.codex/config.toml — envseal does not write $HOME.';
|
|
33
|
+
if (!existsSync(path)) {
|
|
34
|
+
return {
|
|
35
|
+
wired: false,
|
|
36
|
+
status: 'absent',
|
|
37
|
+
commandOk: null,
|
|
38
|
+
message: `.codex/config.toml is missing. ${hint}`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const text = readFileSync(path, 'utf8');
|
|
42
|
+
if (!/envseal-mcp/.test(text)) {
|
|
43
|
+
return {
|
|
44
|
+
wired: false,
|
|
45
|
+
status: 'missing',
|
|
46
|
+
commandOk: null,
|
|
47
|
+
message: `.codex/config.toml has no envseal-mcp. ${hint}`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
wired: true,
|
|
52
|
+
status: 'wired',
|
|
53
|
+
commandOk: null,
|
|
54
|
+
message: 'Project .codex/config.toml names envseal-mcp. [VERIFY: Codex may ignore project files.]',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type McpInspection, type McpWriteAction } from './mcp.js';
|
|
2
|
+
export declare function continueSnippetYaml(platform?: NodeJS.Platform): string;
|
|
3
|
+
export declare function writeContinueProjectFiles(root: string, platform?: NodeJS.Platform): {
|
|
4
|
+
action: McpWriteAction;
|
|
5
|
+
path: string;
|
|
6
|
+
printOnlyHint: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function inspectContinueProject(root: string): McpInspection;
|
|
9
|
+
//# sourceMappingURL=continue.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { mcpLaunch } from './mcp.js';
|
|
4
|
+
export function continueSnippetYaml(platform = process.platform) {
|
|
5
|
+
const launch = mcpLaunch(platform);
|
|
6
|
+
const args = launch.args.map((a) => `"${a}"`).join(', ');
|
|
7
|
+
return `mcpServers:
|
|
8
|
+
- name: envseal-mcp
|
|
9
|
+
command: ${launch.command}
|
|
10
|
+
args: [${args}]
|
|
11
|
+
`;
|
|
12
|
+
}
|
|
13
|
+
export function writeContinueProjectFiles(root, platform = process.platform) {
|
|
14
|
+
const dir = join(root, '.continue');
|
|
15
|
+
mkdirSync(dir, { recursive: true });
|
|
16
|
+
const path = join(dir, 'config.yaml');
|
|
17
|
+
const snippet = continueSnippetYaml(platform);
|
|
18
|
+
const printOnlyHint = 'Continue loads MCP from ~/.continue/config.yaml (global), not this project file. Merge the printed block into the global config; envseal does not write $HOME files. [VERIFY]';
|
|
19
|
+
if (!existsSync(path)) {
|
|
20
|
+
const body = `# Project copy of the envseal MCP snippet. Continue currently reads
|
|
21
|
+
# ~/.continue/config.yaml — merge the mcpServers block there. Do not copy this
|
|
22
|
+
# into $HOME from envseal init; this file exists so doctor can see the host.
|
|
23
|
+
#
|
|
24
|
+
# [VERIFY: recent Continue builds moved to a new HUB config schema; if a
|
|
25
|
+
# top-level mcpServers list is not accepted, use experimental.mcpServers.]
|
|
26
|
+
|
|
27
|
+
${snippet}`;
|
|
28
|
+
writeFileSync(path, body, 'utf8');
|
|
29
|
+
return { action: 'created', path, printOnlyHint };
|
|
30
|
+
}
|
|
31
|
+
const existing = readFileSync(path, 'utf8');
|
|
32
|
+
if (/envseal-mcp/.test(existing) && /@envseal\/mcp-server|npx/.test(existing)) {
|
|
33
|
+
return { action: 'unchanged', path, printOnlyHint };
|
|
34
|
+
}
|
|
35
|
+
if (/envseal-mcp/.test(existing)) {
|
|
36
|
+
return { action: 'unchanged', path, printOnlyHint };
|
|
37
|
+
}
|
|
38
|
+
const trimmed = existing.replace(/\s+$/u, '');
|
|
39
|
+
writeFileSync(path, `${trimmed}\n\n${snippet}`, 'utf8');
|
|
40
|
+
return { action: 'merged', path, printOnlyHint };
|
|
41
|
+
}
|
|
42
|
+
export function inspectContinueProject(root) {
|
|
43
|
+
const path = join(root, '.continue', 'config.yaml');
|
|
44
|
+
const hasSnippet = existsSync(path) && /envseal-mcp/.test(readFileSync(path, 'utf8'));
|
|
45
|
+
// Continue's documented MCP file is ~/.continue/config.yaml. envseal never
|
|
46
|
+
// writes $HOME, so project MCP is not OOTB regardless of the snippet file.
|
|
47
|
+
return {
|
|
48
|
+
wired: false,
|
|
49
|
+
status: hasSnippet ? 'missing' : 'absent',
|
|
50
|
+
commandOk: null,
|
|
51
|
+
message: 'Continue is not OOTB: merge the envseal mcpServers block into ~/.continue/config.yaml (envseal does not write $HOME). Project .continue/ is a detection marker + copy-paste snippet. [VERIFY]',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=continue.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type McpInspection, type McpWriteAction } from './mcp.js';
|
|
2
|
+
export declare function mergeCopilotSettings(root: string, platform?: NodeJS.Platform): {
|
|
3
|
+
action: McpWriteAction;
|
|
4
|
+
path: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function inspectCopilotSettings(root: string, options?: {
|
|
7
|
+
probe?: boolean;
|
|
8
|
+
platform?: NodeJS.Platform;
|
|
9
|
+
}): McpInspection;
|
|
10
|
+
//# sourceMappingURL=copilot.d.ts.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { ENVSEAL_MCP_SERVER_NAME, isRecord, mcpLaunch, parseJsonObject, writeJson, isEmptyEnvsealStub, isStockNpxLaunch, looksLikeEnvsealServer, probeVersion, } from './mcp.js';
|
|
4
|
+
function asServerList(value) {
|
|
5
|
+
if (value === undefined)
|
|
6
|
+
return undefined;
|
|
7
|
+
if (!Array.isArray(value))
|
|
8
|
+
return undefined;
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
function entryName(entry) {
|
|
12
|
+
if (!isRecord(entry))
|
|
13
|
+
return undefined;
|
|
14
|
+
if (typeof entry.name === 'string')
|
|
15
|
+
return entry.name;
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
export function mergeCopilotSettings(root, platform = process.platform) {
|
|
19
|
+
const path = join(root, '.vscode', 'settings.json');
|
|
20
|
+
const launch = { name: ENVSEAL_MCP_SERVER_NAME, ...mcpLaunch(platform) };
|
|
21
|
+
if (!existsSync(path)) {
|
|
22
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
23
|
+
writeJson(path, { 'github.copilot.mcp': [launch] });
|
|
24
|
+
return { action: 'created', path };
|
|
25
|
+
}
|
|
26
|
+
const parsed = parseJsonObject(readFileSync(path, 'utf8'));
|
|
27
|
+
if (parsed === null) {
|
|
28
|
+
return { action: 'skipped', path };
|
|
29
|
+
}
|
|
30
|
+
const existing = asServerList(parsed['github.copilot.mcp']);
|
|
31
|
+
if (parsed['github.copilot.mcp'] !== undefined && existing === undefined) {
|
|
32
|
+
return { action: 'skipped', path };
|
|
33
|
+
}
|
|
34
|
+
const list = existing === undefined ? [] : [...existing];
|
|
35
|
+
const index = list.findIndex((item) => entryName(item) === ENVSEAL_MCP_SERVER_NAME);
|
|
36
|
+
if (index === -1) {
|
|
37
|
+
list.push(launch);
|
|
38
|
+
writeJson(path, { ...parsed, 'github.copilot.mcp': list });
|
|
39
|
+
return { action: 'merged', path };
|
|
40
|
+
}
|
|
41
|
+
const current = list[index];
|
|
42
|
+
if (isEmptyEnvsealStub(current) || isStockNpxLaunch(current)) {
|
|
43
|
+
if (isStockNpxLaunch(current) && isRecord(current) && current.command === launch.command) {
|
|
44
|
+
return { action: 'unchanged', path };
|
|
45
|
+
}
|
|
46
|
+
list[index] = isRecord(current) ? { ...current, ...launch } : launch;
|
|
47
|
+
writeJson(path, { ...parsed, 'github.copilot.mcp': list });
|
|
48
|
+
return { action: 'merged', path };
|
|
49
|
+
}
|
|
50
|
+
return { action: 'unchanged', path };
|
|
51
|
+
}
|
|
52
|
+
export function inspectCopilotSettings(root, options = {}) {
|
|
53
|
+
const path = join(root, '.vscode', 'settings.json');
|
|
54
|
+
const hint = 'Run `envseal init` to merge github.copilot.mcp with envseal-mcp (npx @envseal/mcp-server). [VERIFY]';
|
|
55
|
+
if (!existsSync(path)) {
|
|
56
|
+
return {
|
|
57
|
+
wired: false,
|
|
58
|
+
status: 'absent',
|
|
59
|
+
commandOk: null,
|
|
60
|
+
message: `.vscode/settings.json is missing. ${hint}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const parsed = parseJsonObject(readFileSync(path, 'utf8'));
|
|
64
|
+
if (parsed === null) {
|
|
65
|
+
return {
|
|
66
|
+
wired: false,
|
|
67
|
+
status: 'unreadable',
|
|
68
|
+
commandOk: null,
|
|
69
|
+
message: `.vscode/settings.json is not valid JSON. ${hint}`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const list = asServerList(parsed['github.copilot.mcp']);
|
|
73
|
+
if (list === undefined || list.length === 0) {
|
|
74
|
+
return {
|
|
75
|
+
wired: false,
|
|
76
|
+
status: 'missing',
|
|
77
|
+
commandOk: null,
|
|
78
|
+
message: `.vscode/settings.json has no github.copilot.mcp envseal entry. ${hint}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const entry = list.find((item) => entryName(item) === ENVSEAL_MCP_SERVER_NAME);
|
|
82
|
+
if (entry === undefined || isEmptyEnvsealStub(entry) || !looksLikeEnvsealServer(entry)) {
|
|
83
|
+
return {
|
|
84
|
+
wired: false,
|
|
85
|
+
status: entry === undefined ? 'missing' : 'stub',
|
|
86
|
+
commandOk: null,
|
|
87
|
+
message: `.vscode/settings.json github.copilot.mcp has no working envseal-mcp. ${hint}`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const rec = entry;
|
|
91
|
+
const commandOk = options.probe === true ? probeVersion(rec) : null;
|
|
92
|
+
let message = 'Copilot MCP is wired (project .vscode/settings.json github.copilot.mcp). [VERIFY]';
|
|
93
|
+
if (commandOk === false) {
|
|
94
|
+
message =
|
|
95
|
+
'Copilot MCP is configured, but the launch command did not report a version. Run `envseal init`. [VERIFY]';
|
|
96
|
+
}
|
|
97
|
+
return { wired: true, status: 'wired', commandOk, message };
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=copilot.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advisory Cursor rule file shipped by `envseal init`.
|
|
3
|
+
*
|
|
4
|
+
* Keep this string identical to `plugins/cursor/rules/envseal.mdc`. The
|
|
5
|
+
* cursor-wiring test fails if they drift. Embedding (not reading plugins/ at
|
|
6
|
+
* runtime) is what makes published `@envseal/cli` able to write the file.
|
|
7
|
+
*/
|
|
8
|
+
export declare const CURSOR_RULES_MDC = "---\ndescription: envseal \u2014 never read .env, never echo secrets; use env_describe/env_request instead.\nalwaysApply: true\n---\n\nCursor rules are advisory, not enforced. Treat these as hard rules regardless.\n\n- Never read, write, or print the contents of `.env` or any `.env.*` file (`.env.example` is the only exception \u2014 it contains placeholders, never values).\n- Never run `printenv`, bare `env`, `export -p`, `set`, `env | grep ...`, or `echo $VAR` / `echo $KEY...` to inspect or display environment variables. Every one of these puts secret values into the transcript.\n- Never `cat`, `head`, `tail`, `less`, `grep`, `xxd`, or `base64` a secrets file (`*.pem`, `*.key`, `credentials.json`, `secrets.json`, `.envseal/*`).\n- Never ask the user to paste an API key into the chat.\n\nInstead, use the envseal broker via MCP:\n\n- `env_describe` \u2014 read-only status of the declared keys (present, format-valid, last verified). It never returns values and there is no way to make it do so.\n- `env_request` \u2014 collect the missing keys from the user through a secure prompt. It returns a ticket, never a value; follow up with `env_await`.\n- `env_declare` \u2014 declare a key this project needs, with format and provider metadata.\n- `env_verify` \u2014 test that a key actually works (classified result only).\n- `env_use` \u2014 run a command with secrets injected into the child process; output is redacted.\n- `env_revoke` \u2014 remove a key and get the rotation URL.\n\nIf a required key is missing, `env_request` it. Never read `.env` to find it, and never print it back.\n";
|
|
9
|
+
//# sourceMappingURL=cursor-rules.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advisory Cursor rule file shipped by `envseal init`.
|
|
3
|
+
*
|
|
4
|
+
* Keep this string identical to `plugins/cursor/rules/envseal.mdc`. The
|
|
5
|
+
* cursor-wiring test fails if they drift. Embedding (not reading plugins/ at
|
|
6
|
+
* runtime) is what makes published `@envseal/cli` able to write the file.
|
|
7
|
+
*/
|
|
8
|
+
export const CURSOR_RULES_MDC = `---
|
|
9
|
+
description: envseal — never read .env, never echo secrets; use env_describe/env_request instead.
|
|
10
|
+
alwaysApply: true
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Cursor rules are advisory, not enforced. Treat these as hard rules regardless.
|
|
14
|
+
|
|
15
|
+
- Never read, write, or print the contents of \`.env\` or any \`.env.*\` file (\`.env.example\` is the only exception — it contains placeholders, never values).
|
|
16
|
+
- Never run \`printenv\`, bare \`env\`, \`export -p\`, \`set\`, \`env | grep ...\`, or \`echo $VAR\` / \`echo $KEY...\` to inspect or display environment variables. Every one of these puts secret values into the transcript.
|
|
17
|
+
- Never \`cat\`, \`head\`, \`tail\`, \`less\`, \`grep\`, \`xxd\`, or \`base64\` a secrets file (\`*.pem\`, \`*.key\`, \`credentials.json\`, \`secrets.json\`, \`.envseal/*\`).
|
|
18
|
+
- Never ask the user to paste an API key into the chat.
|
|
19
|
+
|
|
20
|
+
Instead, use the envseal broker via MCP:
|
|
21
|
+
|
|
22
|
+
- \`env_describe\` — read-only status of the declared keys (present, format-valid, last verified). It never returns values and there is no way to make it do so.
|
|
23
|
+
- \`env_request\` — collect the missing keys from the user through a secure prompt. It returns a ticket, never a value; follow up with \`env_await\`.
|
|
24
|
+
- \`env_declare\` — declare a key this project needs, with format and provider metadata.
|
|
25
|
+
- \`env_verify\` — test that a key actually works (classified result only).
|
|
26
|
+
- \`env_use\` — run a command with secrets injected into the child process; output is redacted.
|
|
27
|
+
- \`env_revoke\` — remove a key and get the rotation URL.
|
|
28
|
+
|
|
29
|
+
If a required key is missing, \`env_request\` it. Never read \`.env\` to find it, and never print it back.
|
|
30
|
+
`;
|
|
31
|
+
//# sourceMappingURL=cursor-rules.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type McpInspection, type McpLaunch, type McpWriteAction } from './mcp.js';
|
|
2
|
+
export { ENVSEAL_MCP_PACKAGE, ENVSEAL_MCP_SERVER_NAME, isEmptyEnvsealStub, isStockNpxLaunch, } from './mcp.js';
|
|
3
|
+
export type CursorMcpLaunch = McpLaunch;
|
|
4
|
+
export type CursorMcpWriteAction = McpWriteAction;
|
|
5
|
+
export type CursorRulesWriteAction = 'created' | 'unchanged';
|
|
6
|
+
export type CursorMcpStatus = McpInspection['status'];
|
|
7
|
+
export type CursorMcpInspection = McpInspection;
|
|
8
|
+
export type CursorWiringResult = {
|
|
9
|
+
mcp: CursorMcpWriteAction;
|
|
10
|
+
rules: CursorRulesWriteAction;
|
|
11
|
+
mcpPath: string;
|
|
12
|
+
rulesPath: string;
|
|
13
|
+
/** Human one-liner: reload MCP. Empty when mcp write was skipped. */
|
|
14
|
+
reloadHint: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function cursorMcpLaunch(platform?: NodeJS.Platform): CursorMcpLaunch;
|
|
17
|
+
export declare function cursorMcpSnippetJson(platform?: NodeJS.Platform): string;
|
|
18
|
+
export declare function writeCursorMcpJson(root: string, platform?: NodeJS.Platform): {
|
|
19
|
+
action: CursorMcpWriteAction;
|
|
20
|
+
path: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function writeCursorRules(root: string): {
|
|
23
|
+
action: CursorRulesWriteAction;
|
|
24
|
+
path: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function writeCursorHostFiles(root: string, platform?: NodeJS.Platform): CursorWiringResult;
|
|
27
|
+
export declare function inspectCursorMcp(root: string, options?: {
|
|
28
|
+
probe?: boolean;
|
|
29
|
+
platform?: NodeJS.Platform;
|
|
30
|
+
}): CursorMcpInspection;
|
|
31
|
+
//# sourceMappingURL=cursor.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { CURSOR_RULES_MDC } from './cursor-rules.js';
|
|
4
|
+
import { inspectMcpServersFile, mcpLaunch, mcpSnippetJson, mergeMcpServersFile, } from './mcp.js';
|
|
5
|
+
export { ENVSEAL_MCP_PACKAGE, ENVSEAL_MCP_SERVER_NAME, isEmptyEnvsealStub, isStockNpxLaunch, } from './mcp.js';
|
|
6
|
+
const RELOAD_HINT = 'Reload MCP in Settings → MCP so Cursor picks up envseal-mcp.';
|
|
7
|
+
export function cursorMcpLaunch(platform = process.platform) {
|
|
8
|
+
return mcpLaunch(platform);
|
|
9
|
+
}
|
|
10
|
+
export function cursorMcpSnippetJson(platform = process.platform) {
|
|
11
|
+
return mcpSnippetJson(platform);
|
|
12
|
+
}
|
|
13
|
+
export function writeCursorMcpJson(root, platform = process.platform) {
|
|
14
|
+
return mergeMcpServersFile(join(root, '.cursor', 'mcp.json'), platform);
|
|
15
|
+
}
|
|
16
|
+
export function writeCursorRules(root) {
|
|
17
|
+
const path = join(root, '.cursor', 'rules', 'envseal.mdc');
|
|
18
|
+
if (existsSync(path)) {
|
|
19
|
+
return { action: 'unchanged', path };
|
|
20
|
+
}
|
|
21
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
22
|
+
writeFileSync(path, CURSOR_RULES_MDC, 'utf8');
|
|
23
|
+
return { action: 'created', path };
|
|
24
|
+
}
|
|
25
|
+
export function writeCursorHostFiles(root, platform = process.platform) {
|
|
26
|
+
const mcp = writeCursorMcpJson(root, platform);
|
|
27
|
+
const rules = writeCursorRules(root);
|
|
28
|
+
return {
|
|
29
|
+
mcp: mcp.action,
|
|
30
|
+
rules: rules.action,
|
|
31
|
+
mcpPath: mcp.path,
|
|
32
|
+
rulesPath: rules.path,
|
|
33
|
+
reloadHint: mcp.action === 'skipped' ? '' : RELOAD_HINT,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function inspectCursorMcp(root, options = {}) {
|
|
37
|
+
return inspectMcpServersFile(join(root, '.cursor', 'mcp.json'), '.cursor/mcp.json', options);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type McpInspection, type McpWriteAction } from './mcp.js';
|
|
2
|
+
export declare function gooseYamlSnippet(platform?: NodeJS.Platform): string;
|
|
3
|
+
export declare function gooseAddHint(platform?: NodeJS.Platform): string;
|
|
4
|
+
/**
|
|
5
|
+
* Goose is print-only: do not invent a verified project schema.
|
|
6
|
+
* Create `.goose/` so doctor can label the host; MCP stays unwired until the
|
|
7
|
+
* user runs the printed CLI / yaml themselves.
|
|
8
|
+
*/
|
|
9
|
+
export declare function writeGooseMarker(root: string): {
|
|
10
|
+
action: McpWriteAction;
|
|
11
|
+
path: string;
|
|
12
|
+
addHint: string;
|
|
13
|
+
yamlSnippet: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function inspectGooseProject(root: string): McpInspection;
|
|
16
|
+
//# sourceMappingURL=goose.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { mcpLaunch } from './mcp.js';
|
|
4
|
+
export function gooseYamlSnippet(platform = process.platform) {
|
|
5
|
+
const launch = mcpLaunch(platform);
|
|
6
|
+
const args = launch.args.map((a) => `"${a}"`).join(', ');
|
|
7
|
+
return `mcp:
|
|
8
|
+
servers:
|
|
9
|
+
envseal-mcp:
|
|
10
|
+
cmd: ${launch.command}
|
|
11
|
+
args: [${args}]
|
|
12
|
+
`;
|
|
13
|
+
}
|
|
14
|
+
export function gooseAddHint(platform = process.platform) {
|
|
15
|
+
const launch = mcpLaunch(platform);
|
|
16
|
+
const args = launch.args.join(' ');
|
|
17
|
+
return `goose mcp add envseal-mcp -- ${launch.command} ${args} # [VERIFY: exact flags for your build]`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Goose is print-only: do not invent a verified project schema.
|
|
21
|
+
* Create `.goose/` so doctor can label the host; MCP stays unwired until the
|
|
22
|
+
* user runs the printed CLI / yaml themselves.
|
|
23
|
+
*/
|
|
24
|
+
export function writeGooseMarker(root) {
|
|
25
|
+
const dir = join(root, '.goose');
|
|
26
|
+
const existed = existsSync(dir);
|
|
27
|
+
mkdirSync(dir, { recursive: true });
|
|
28
|
+
return {
|
|
29
|
+
action: existed ? 'unchanged' : 'created',
|
|
30
|
+
path: dir,
|
|
31
|
+
addHint: gooseAddHint(),
|
|
32
|
+
yamlSnippet: gooseYamlSnippet(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function inspectGooseProject(root) {
|
|
36
|
+
const files = [join(root, 'goose.config.yaml'), join(root, '.goose', 'config.yaml')];
|
|
37
|
+
for (const path of files) {
|
|
38
|
+
if (!existsSync(path))
|
|
39
|
+
continue;
|
|
40
|
+
const text = readFileSync(path, 'utf8');
|
|
41
|
+
if (/envseal-mcp/.test(text)) {
|
|
42
|
+
return {
|
|
43
|
+
wired: true,
|
|
44
|
+
status: 'wired',
|
|
45
|
+
commandOk: null,
|
|
46
|
+
message: `Goose config names envseal-mcp (${path}). [VERIFY]`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
wired: false,
|
|
52
|
+
status: 'missing',
|
|
53
|
+
commandOk: null,
|
|
54
|
+
message: 'Goose is not OOTB: run `goose mcp add` (see envseal init output) or merge the yaml snippet. envseal does not write ~/.config/goose/. [VERIFY]',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=goose.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type McpInspection } from './mcp.js';
|
|
2
|
+
export type AgentWiringMcp = 'ok' | 'missing' | 'spawn_failed';
|
|
3
|
+
export type AgentWiringInstructions = 'ok' | 'missing';
|
|
4
|
+
export type AgentWiring = {
|
|
5
|
+
mcp: AgentWiringMcp;
|
|
6
|
+
instructions: AgentWiringInstructions;
|
|
7
|
+
};
|
|
8
|
+
export type PrimaryHostInspection = {
|
|
9
|
+
wiring: AgentWiring;
|
|
10
|
+
mcp?: McpInspection;
|
|
11
|
+
/** Hosts where MCP is expected in a project file. */
|
|
12
|
+
mcpRequired: boolean;
|
|
13
|
+
/** Documented as print-only / not OOTB even after init. */
|
|
14
|
+
notOotb: boolean;
|
|
15
|
+
/** Aider: `.env` appears on the `read:` list. */
|
|
16
|
+
aiderUnsafe: boolean;
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function inspectPrimaryHostWiring(root: string, hostId: string, options?: {
|
|
20
|
+
probe?: boolean;
|
|
21
|
+
platform?: NodeJS.Platform;
|
|
22
|
+
}): PrimaryHostInspection;
|
|
23
|
+
export declare function wiringFailsDoctor(inspection: PrimaryHostInspection): boolean;
|
|
24
|
+
//# sourceMappingURL=inspect.d.ts.map
|