@envseal/cli 0.1.3 → 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.
Files changed (39) hide show
  1. package/dist/bin.js +3 -3
  2. package/dist/cli-utils.d.ts +1 -0
  3. package/dist/cli-utils.js +8 -5
  4. package/dist/commands/doctor.js +26 -10
  5. package/dist/commands/init.js +70 -42
  6. package/dist/commands/revoke.d.ts +1 -1
  7. package/dist/commands/revoke.js +40 -2
  8. package/dist/exit-codes.js +2 -0
  9. package/dist/host-wiring/agents-md-content.d.ts +9 -0
  10. package/dist/host-wiring/agents-md-content.js +73 -0
  11. package/dist/host-wiring/agents-md.d.ts +20 -0
  12. package/dist/host-wiring/agents-md.js +50 -0
  13. package/dist/host-wiring/aider-conf.d.ts +8 -0
  14. package/dist/host-wiring/aider-conf.js +37 -0
  15. package/dist/host-wiring/aider.d.ts +14 -0
  16. package/dist/host-wiring/aider.js +90 -0
  17. package/dist/host-wiring/apply.d.ts +22 -0
  18. package/dist/host-wiring/apply.js +165 -0
  19. package/dist/host-wiring/codex.d.ts +8 -0
  20. package/dist/host-wiring/codex.js +57 -0
  21. package/dist/host-wiring/continue.d.ts +9 -0
  22. package/dist/host-wiring/continue.js +54 -0
  23. package/dist/host-wiring/copilot.d.ts +10 -0
  24. package/dist/host-wiring/copilot.js +99 -0
  25. package/dist/host-wiring/cursor-rules.d.ts +9 -0
  26. package/dist/host-wiring/cursor-rules.js +31 -0
  27. package/dist/host-wiring/cursor.d.ts +31 -0
  28. package/dist/host-wiring/cursor.js +39 -0
  29. package/dist/host-wiring/goose.d.ts +16 -0
  30. package/dist/host-wiring/goose.js +57 -0
  31. package/dist/host-wiring/inspect.d.ts +24 -0
  32. package/dist/host-wiring/inspect.js +102 -0
  33. package/dist/host-wiring/mcp.d.ts +55 -0
  34. package/dist/host-wiring/mcp.js +238 -0
  35. package/dist/host-wiring/zed.d.ts +10 -0
  36. package/dist/host-wiring/zed.js +97 -0
  37. package/dist/host.d.ts +28 -0
  38. package/dist/host.js +131 -16
  39. package/package.json +8 -8
@@ -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
@@ -0,0 +1,102 @@
1
+ import { join } from 'node:path';
2
+ import { inspectAiderConf } from './aider.js';
3
+ import { inspectAgentsMd } from './agents-md.js';
4
+ import { inspectCodexProject } from './codex.js';
5
+ import { inspectContinueProject } from './continue.js';
6
+ import { inspectCopilotSettings } from './copilot.js';
7
+ import { inspectCursorMcp } from './cursor.js';
8
+ import { inspectGooseProject } from './goose.js';
9
+ import { inspectMcpServersFile, mcpWiringState } from './mcp.js';
10
+ import { inspectZedSettings } from './zed.js';
11
+ const MCP_REQUIRED = new Set([
12
+ 'cursor',
13
+ 'claude-code',
14
+ 'windsurf',
15
+ 'cline',
16
+ 'zed',
17
+ 'jetbrains',
18
+ 'copilot',
19
+ 'continue',
20
+ 'codex',
21
+ 'goose',
22
+ ]);
23
+ const NOT_OOTB = new Set(['continue', 'goose']);
24
+ function inspectPrimaryMcp(root, hostId, options) {
25
+ switch (hostId) {
26
+ case 'cursor':
27
+ return inspectCursorMcp(root, options);
28
+ case 'claude-code':
29
+ return inspectMcpServersFile(join(root, '.mcp.json'), '.mcp.json', options);
30
+ case 'windsurf':
31
+ return inspectMcpServersFile(join(root, '.windsurf', 'mcp_config.json'), '.windsurf/mcp_config.json', options);
32
+ case 'cline':
33
+ return inspectMcpServersFile(join(root, '.cline', 'mcp_settings.json'), '.cline/mcp_settings.json', options);
34
+ case 'zed':
35
+ return inspectZedSettings(root, options);
36
+ case 'jetbrains':
37
+ return inspectMcpServersFile(join(root, '.idea', 'mcp.json'), '.idea/mcp.json', options);
38
+ case 'copilot':
39
+ return inspectCopilotSettings(root, options);
40
+ case 'continue':
41
+ return inspectContinueProject(root);
42
+ case 'codex':
43
+ return inspectCodexProject(root);
44
+ case 'goose':
45
+ return inspectGooseProject(root);
46
+ default:
47
+ return undefined;
48
+ }
49
+ }
50
+ export function inspectPrimaryHostWiring(root, hostId, options = {}) {
51
+ const instructions = inspectAgentsMd(root).instructions;
52
+ const mcpRequired = MCP_REQUIRED.has(hostId);
53
+ const notOotb = NOT_OOTB.has(hostId);
54
+ if (hostId === 'aider') {
55
+ const aider = inspectAiderConf(root);
56
+ return {
57
+ wiring: { mcp: 'ok', instructions },
58
+ mcpRequired: false,
59
+ notOotb: false,
60
+ aiderUnsafe: !aider.wired,
61
+ message: aider.message,
62
+ };
63
+ }
64
+ if (!mcpRequired) {
65
+ return {
66
+ wiring: { mcp: 'ok', instructions },
67
+ mcpRequired: false,
68
+ notOotb: false,
69
+ aiderUnsafe: false,
70
+ message: instructions === 'ok'
71
+ ? 'Layer 1 instructions present (AGENTS.md).'
72
+ : 'AGENTS.md is missing the envseal imperative (never read .env; use envseal ensure / envseal run). Run `envseal init`.',
73
+ };
74
+ }
75
+ const inspection = inspectPrimaryMcp(root, hostId, options) ?? {
76
+ wired: false,
77
+ status: 'missing',
78
+ commandOk: null,
79
+ message: `No project MCP config for host ${hostId}. Run \`envseal init\`.`,
80
+ };
81
+ return {
82
+ wiring: {
83
+ mcp: mcpWiringState(inspection),
84
+ instructions,
85
+ },
86
+ mcp: inspection,
87
+ mcpRequired: true,
88
+ notOotb,
89
+ aiderUnsafe: false,
90
+ message: inspection.message,
91
+ };
92
+ }
93
+ export function wiringFailsDoctor(inspection) {
94
+ if (inspection.mcpRequired && inspection.wiring.mcp !== 'ok')
95
+ return true;
96
+ if (inspection.wiring.instructions === 'missing')
97
+ return true;
98
+ if (inspection.aiderUnsafe)
99
+ return true;
100
+ return false;
101
+ }
102
+ //# sourceMappingURL=inspect.js.map
@@ -0,0 +1,55 @@
1
+ export declare const ENVSEAL_MCP_PACKAGE = "@envseal/mcp-server";
2
+ export declare const ENVSEAL_MCP_SERVER_NAME = "envseal-mcp";
3
+ export declare const NPX_ARGS: readonly ["-y", "@envseal/mcp-server"];
4
+ export type McpLaunch = {
5
+ command: string;
6
+ args: string[];
7
+ };
8
+ export type McpWriteAction = 'created' | 'merged' | 'unchanged' | 'skipped';
9
+ export type McpStatus = 'absent' | 'unreadable' | 'missing' | 'stub' | 'wired';
10
+ export type McpInspection = {
11
+ wired: boolean;
12
+ status: McpStatus;
13
+ message: string;
14
+ /** null when the launch command was not probed (npx is not side-effect free). */
15
+ commandOk: boolean | null;
16
+ };
17
+ /**
18
+ * Launch argv a host can spawn without a global `envseal-mcp` on PATH.
19
+ * Project MCP uses the workspace as cwd — never bake `--project` in.
20
+ */
21
+ export declare function mcpLaunch(platform?: NodeJS.Platform): McpLaunch;
22
+ export declare function mcpSnippetJson(platform?: NodeJS.Platform): string;
23
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
24
+ export declare function argsList(value: unknown): string[] | undefined;
25
+ /** Old shipped stub: `envseal-mcp` with no args. Host launchers do not search node_modules/.bin. */
26
+ export declare function isEmptyEnvsealStub(entry: unknown): boolean;
27
+ /** Our npx snippet, either platform variant, and nothing else in command/args. */
28
+ export declare function isStockNpxLaunch(entry: unknown): boolean;
29
+ /** True when an entry would actually start @envseal/mcp-server (npx or a non-empty command). */
30
+ export declare function looksLikeEnvsealServer(entry: unknown): boolean;
31
+ export declare function nextEnvsealEntry(current: unknown, launch: McpLaunch): McpLaunch | Record<string, unknown> | null;
32
+ export declare function parseJsonObject(text: string): Record<string, unknown> | null;
33
+ export declare function writeJson(path: string, value: unknown): void;
34
+ /**
35
+ * Merge envseal-mcp into a file whose top-level key is `mcpServers`.
36
+ * Never writes user-global configs; the caller passes a project path.
37
+ */
38
+ export declare function mergeMcpServersFile(path: string, platform?: NodeJS.Platform): {
39
+ action: McpWriteAction;
40
+ path: string;
41
+ };
42
+ export declare function classifyEntry(entry: unknown): 'missing' | 'stub' | 'wired';
43
+ /**
44
+ * `--version` is side-effect free on the envseal-mcp binary. `npx -y` is not
45
+ * (it may hit the network), so the default snippet is not probed.
46
+ */
47
+ export declare function shouldProbeLaunch(entry: Record<string, unknown>): boolean;
48
+ export declare function probeVersion(entry: Record<string, unknown>): boolean | null;
49
+ export declare function inspectMcpServersFile(path: string, label: string, options?: {
50
+ probe?: boolean;
51
+ platform?: NodeJS.Platform;
52
+ }): McpInspection;
53
+ /** Map an inspection to the doctor `agentWiring.mcp` field. */
54
+ export declare function mcpWiringState(inspection: McpInspection): 'ok' | 'missing' | 'spawn_failed';
55
+ //# sourceMappingURL=mcp.d.ts.map
@@ -0,0 +1,238 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ export const ENVSEAL_MCP_PACKAGE = '@envseal/mcp-server';
5
+ export const ENVSEAL_MCP_SERVER_NAME = 'envseal-mcp';
6
+ export const NPX_ARGS = ['-y', ENVSEAL_MCP_PACKAGE];
7
+ /**
8
+ * Launch argv a host can spawn without a global `envseal-mcp` on PATH.
9
+ * Project MCP uses the workspace as cwd — never bake `--project` in.
10
+ */
11
+ export function mcpLaunch(platform = process.platform) {
12
+ return {
13
+ command: platform === 'win32' ? 'npx.cmd' : 'npx',
14
+ args: [...NPX_ARGS],
15
+ };
16
+ }
17
+ export function mcpSnippetJson(platform = process.platform) {
18
+ const launch = mcpLaunch(platform);
19
+ return JSON.stringify({
20
+ mcpServers: { [ENVSEAL_MCP_SERVER_NAME]: launch },
21
+ });
22
+ }
23
+ export function isRecord(value) {
24
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
25
+ }
26
+ export function argsList(value) {
27
+ if (value === undefined)
28
+ return undefined;
29
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string'))
30
+ return undefined;
31
+ return value;
32
+ }
33
+ /** Old shipped stub: `envseal-mcp` with no args. Host launchers do not search node_modules/.bin. */
34
+ export function isEmptyEnvsealStub(entry) {
35
+ if (!isRecord(entry))
36
+ return true;
37
+ const command = entry.command;
38
+ if (typeof command !== 'string' || command.trim() === '')
39
+ return true;
40
+ if (command !== 'envseal-mcp')
41
+ return false;
42
+ const args = argsList(entry.args);
43
+ return args === undefined || args.length === 0;
44
+ }
45
+ /** Our npx snippet, either platform variant, and nothing else in command/args. */
46
+ export function isStockNpxLaunch(entry) {
47
+ if (!isRecord(entry))
48
+ return false;
49
+ const command = entry.command;
50
+ if (command !== 'npx' && command !== 'npx.cmd')
51
+ return false;
52
+ const args = argsList(entry.args);
53
+ return (args !== undefined && args.length === NPX_ARGS.length && NPX_ARGS.every((a, i) => a === args[i]));
54
+ }
55
+ /** True when an entry would actually start @envseal/mcp-server (npx or a non-empty command). */
56
+ export function looksLikeEnvsealServer(entry) {
57
+ if (entry === undefined || isEmptyEnvsealStub(entry))
58
+ return false;
59
+ if (!isRecord(entry))
60
+ return false;
61
+ const command = entry.command;
62
+ if (typeof command !== 'string' || command.trim() === '')
63
+ return false;
64
+ if (isStockNpxLaunch(entry))
65
+ return true;
66
+ const joined = `${command} ${(argsList(entry.args) ?? []).join(' ')}`;
67
+ return /@envseal\/mcp-server|envseal-mcp/i.test(joined);
68
+ }
69
+ export function nextEnvsealEntry(current, launch) {
70
+ if (current === undefined || isEmptyEnvsealStub(current)) {
71
+ return launch;
72
+ }
73
+ if (isStockNpxLaunch(current) && isRecord(current)) {
74
+ if (current.command === launch.command)
75
+ return null;
76
+ return { ...current, command: launch.command, args: launch.args };
77
+ }
78
+ return null;
79
+ }
80
+ export function parseJsonObject(text) {
81
+ try {
82
+ const parsed = JSON.parse(text);
83
+ return isRecord(parsed) ? parsed : null;
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ export function writeJson(path, value) {
90
+ mkdirSync(dirname(path), { recursive: true });
91
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
92
+ }
93
+ /**
94
+ * Merge envseal-mcp into a file whose top-level key is `mcpServers`.
95
+ * Never writes user-global configs; the caller passes a project path.
96
+ */
97
+ export function mergeMcpServersFile(path, platform = process.platform) {
98
+ const launch = mcpLaunch(platform);
99
+ if (!existsSync(path)) {
100
+ writeJson(path, { mcpServers: { [ENVSEAL_MCP_SERVER_NAME]: launch } });
101
+ return { action: 'created', path };
102
+ }
103
+ const parsed = parseJsonObject(readFileSync(path, 'utf8'));
104
+ if (parsed === null) {
105
+ return { action: 'skipped', path };
106
+ }
107
+ const existingServers = parsed.mcpServers;
108
+ if (existingServers !== undefined && !isRecord(existingServers)) {
109
+ return { action: 'skipped', path };
110
+ }
111
+ const servers = existingServers === undefined ? {} : { ...existingServers };
112
+ const next = nextEnvsealEntry(servers[ENVSEAL_MCP_SERVER_NAME], launch);
113
+ if (next === null) {
114
+ return { action: 'unchanged', path };
115
+ }
116
+ servers[ENVSEAL_MCP_SERVER_NAME] = next;
117
+ writeJson(path, { ...parsed, mcpServers: servers });
118
+ return { action: 'merged', path };
119
+ }
120
+ export function classifyEntry(entry) {
121
+ if (entry === undefined)
122
+ return 'missing';
123
+ if (isEmptyEnvsealStub(entry))
124
+ return 'stub';
125
+ if (!isRecord(entry))
126
+ return 'stub';
127
+ const command = entry.command;
128
+ if (typeof command !== 'string' || command.trim() === '')
129
+ return 'stub';
130
+ return 'wired';
131
+ }
132
+ /**
133
+ * `--version` is side-effect free on the envseal-mcp binary. `npx -y` is not
134
+ * (it may hit the network), so the default snippet is not probed.
135
+ */
136
+ export function shouldProbeLaunch(entry) {
137
+ const command = entry.command;
138
+ if (typeof command !== 'string')
139
+ return false;
140
+ if (command === 'npx' || command === 'npx.cmd')
141
+ return false;
142
+ return true;
143
+ }
144
+ export function probeVersion(entry) {
145
+ if (!shouldProbeLaunch(entry))
146
+ return null;
147
+ const command = entry.command;
148
+ if (typeof command !== 'string')
149
+ return null;
150
+ const args = argsList(entry.args) ?? [];
151
+ const result = spawnSync(command, [...args, '--version'], {
152
+ encoding: 'utf8',
153
+ timeout: 5000,
154
+ windowsHide: true,
155
+ stdio: ['ignore', 'pipe', 'pipe'],
156
+ });
157
+ if (result.error)
158
+ return false;
159
+ if (result.status !== 0)
160
+ return false;
161
+ const out = `${result.stdout ?? ''}${result.stderr ?? ''}`;
162
+ return /envseal-mcp/i.test(out);
163
+ }
164
+ function initHint(platform, label) {
165
+ return `Run \`envseal init\` to write it, or merge ${mcpSnippetJson(platform)} into ${label}.`;
166
+ }
167
+ export function inspectMcpServersFile(path, label, options = {}) {
168
+ const platform = options.platform ?? process.platform;
169
+ const hint = initHint(platform, label);
170
+ if (!existsSync(path)) {
171
+ return {
172
+ wired: false,
173
+ status: 'absent',
174
+ commandOk: null,
175
+ message: `${label} is missing. ${hint}`,
176
+ };
177
+ }
178
+ const parsed = parseJsonObject(readFileSync(path, 'utf8'));
179
+ if (parsed === null) {
180
+ return {
181
+ wired: false,
182
+ status: 'unreadable',
183
+ commandOk: null,
184
+ message: `${label} is not valid JSON. ${hint}`,
185
+ };
186
+ }
187
+ const servers = parsed.mcpServers;
188
+ if (servers === undefined || (isRecord(servers) && Object.keys(servers).length === 0)) {
189
+ return {
190
+ wired: false,
191
+ status: 'missing',
192
+ commandOk: null,
193
+ message: `${label} has no envseal-mcp (empty mcpServers). ${hint}`,
194
+ };
195
+ }
196
+ if (!isRecord(servers)) {
197
+ return {
198
+ wired: false,
199
+ status: 'unreadable',
200
+ commandOk: null,
201
+ message: `${label} mcpServers is not an object. ${hint}`,
202
+ };
203
+ }
204
+ const entry = servers[ENVSEAL_MCP_SERVER_NAME];
205
+ const kind = classifyEntry(entry);
206
+ if (kind === 'missing') {
207
+ return {
208
+ wired: false,
209
+ status: 'missing',
210
+ commandOk: null,
211
+ message: `${label} has no envseal-mcp. ${hint}`,
212
+ };
213
+ }
214
+ if (kind === 'stub') {
215
+ return {
216
+ wired: false,
217
+ status: 'stub',
218
+ commandOk: null,
219
+ message: `${label} envseal-mcp is the empty envseal-mcp stub (not on PATH for the host). ${hint}`,
220
+ };
221
+ }
222
+ const rec = entry;
223
+ const commandOk = options.probe === true ? probeVersion(rec) : null;
224
+ let message = `MCP is wired (${label}).`;
225
+ if (commandOk === false) {
226
+ message = `MCP is configured in ${label}, but the launch command did not report a version. Run \`envseal init\` if the host cannot connect.`;
227
+ }
228
+ return { wired: true, status: 'wired', commandOk, message };
229
+ }
230
+ /** Map an inspection to the doctor `agentWiring.mcp` field. */
231
+ export function mcpWiringState(inspection) {
232
+ if (inspection.commandOk === false)
233
+ return 'spawn_failed';
234
+ if (inspection.wired)
235
+ return 'ok';
236
+ return 'missing';
237
+ }
238
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1,10 @@
1
+ import { type McpInspection, type McpWriteAction } from './mcp.js';
2
+ export declare function mergeZedSettings(root: string, platform?: NodeJS.Platform): {
3
+ action: McpWriteAction;
4
+ path: string;
5
+ };
6
+ export declare function inspectZedSettings(root: string, options?: {
7
+ probe?: boolean;
8
+ platform?: NodeJS.Platform;
9
+ }): McpInspection;
10
+ //# sourceMappingURL=zed.d.ts.map
@@ -0,0 +1,97 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { ENVSEAL_MCP_SERVER_NAME, isRecord, mcpLaunch, nextEnvsealEntry, parseJsonObject, writeJson, classifyEntry, probeVersion, } from './mcp.js';
4
+ function zedHint(platform) {
5
+ const launch = mcpLaunch(platform);
6
+ return `Run \`envseal init\` to merge { "mcp": { "${ENVSEAL_MCP_SERVER_NAME}": ${JSON.stringify(launch)} } } into .zed/settings.json. [VERIFY]`;
7
+ }
8
+ export function mergeZedSettings(root, platform = process.platform) {
9
+ const path = join(root, '.zed', 'settings.json');
10
+ const launch = mcpLaunch(platform);
11
+ if (!existsSync(path)) {
12
+ writeJson(path, { mcp: { [ENVSEAL_MCP_SERVER_NAME]: launch } });
13
+ return { action: 'created', path };
14
+ }
15
+ const parsed = parseJsonObject(readFileSync(path, 'utf8'));
16
+ if (parsed === null) {
17
+ return { action: 'skipped', path };
18
+ }
19
+ const existingMcp = parsed.mcp;
20
+ if (existingMcp !== undefined && !isRecord(existingMcp)) {
21
+ return { action: 'skipped', path };
22
+ }
23
+ const mcp = existingMcp === undefined ? {} : { ...existingMcp };
24
+ const next = nextEnvsealEntry(mcp[ENVSEAL_MCP_SERVER_NAME], launch);
25
+ if (next === null) {
26
+ return { action: 'unchanged', path };
27
+ }
28
+ mcp[ENVSEAL_MCP_SERVER_NAME] = next;
29
+ writeJson(path, { ...parsed, mcp });
30
+ return { action: 'merged', path };
31
+ }
32
+ export function inspectZedSettings(root, options = {}) {
33
+ const platform = options.platform ?? process.platform;
34
+ const path = join(root, '.zed', 'settings.json');
35
+ const hint = zedHint(platform);
36
+ if (!existsSync(path)) {
37
+ return {
38
+ wired: false,
39
+ status: 'absent',
40
+ commandOk: null,
41
+ message: `.zed/settings.json is missing. ${hint}`,
42
+ };
43
+ }
44
+ const parsed = parseJsonObject(readFileSync(path, 'utf8'));
45
+ if (parsed === null) {
46
+ return {
47
+ wired: false,
48
+ status: 'unreadable',
49
+ commandOk: null,
50
+ message: `.zed/settings.json is not valid JSON. ${hint}`,
51
+ };
52
+ }
53
+ const mcp = parsed.mcp;
54
+ if (mcp === undefined || (isRecord(mcp) && Object.keys(mcp).length === 0)) {
55
+ return {
56
+ wired: false,
57
+ status: 'missing',
58
+ commandOk: null,
59
+ message: `.zed/settings.json has no envseal-mcp under mcp. ${hint}`,
60
+ };
61
+ }
62
+ if (!isRecord(mcp)) {
63
+ return {
64
+ wired: false,
65
+ status: 'unreadable',
66
+ commandOk: null,
67
+ message: `.zed/settings.json mcp is not an object. ${hint}`,
68
+ };
69
+ }
70
+ const entry = mcp[ENVSEAL_MCP_SERVER_NAME];
71
+ const kind = classifyEntry(entry);
72
+ if (kind === 'missing') {
73
+ return {
74
+ wired: false,
75
+ status: 'missing',
76
+ commandOk: null,
77
+ message: `.zed/settings.json has no envseal-mcp. ${hint}`,
78
+ };
79
+ }
80
+ if (kind === 'stub') {
81
+ return {
82
+ wired: false,
83
+ status: 'stub',
84
+ commandOk: null,
85
+ message: `.zed/settings.json envseal-mcp is the empty stub. ${hint}`,
86
+ };
87
+ }
88
+ const rec = entry;
89
+ const commandOk = options.probe === true ? probeVersion(rec) : null;
90
+ let message = 'Zed MCP is wired (project .zed/settings.json). [VERIFY: schema]';
91
+ if (commandOk === false) {
92
+ message =
93
+ 'Zed MCP is configured, but the launch command did not report a version. Run `envseal init`. [VERIFY]';
94
+ }
95
+ return { wired: true, status: 'wired', commandOk, message };
96
+ }
97
+ //# sourceMappingURL=zed.js.map
package/dist/host.d.ts CHANGED
@@ -6,5 +6,33 @@ export interface HostInfo {
6
6
  reason: string;
7
7
  recommendation: string;
8
8
  }
9
+ /** Host ids `init --host` accepts. `openhands` is Layer 1 only. */
10
+ export declare const KNOWN_HOST_IDS: readonly ["claude-code", "cursor", "continue", "aider", "windsurf", "cline", "zed", "codex", "jetbrains", "goose", "copilot", "generic", "unknown", "openhands"];
11
+ export type HostId = (typeof KNOWN_HOST_IDS)[number];
12
+ export declare function aiderMarkerExists(root: string): boolean;
13
+ export declare function copilotSettingsExist(root: string): boolean;
14
+ /**
15
+ * Every project-local host marker in this repo. Used by `init` so a dual-host
16
+ * tree (`.cursor/` + `.claude/`) gets both configs. Does not include AGENTS.md
17
+ * (Layer 1 is always applied) and does not scan `$HOME`.
18
+ */
19
+ export declare function collectProjectHostIds(root: string): string[];
20
+ /**
21
+ * This-process host only. Used by `init` when the project has no markers.
22
+ * Never treats a global `~/.cursor` / `~/.codex` install as this project.
23
+ */
24
+ export declare function detectProcessHostId(): string | undefined;
25
+ export declare function parseHostOverride(raw: string): string[] | {
26
+ error: string;
27
+ };
28
+ /**
29
+ * Which hosts `init` should write Layer 2 config for.
30
+ * `--host` is an escape hatch (comma-separated ok). Never crawls `$HOME`.
31
+ */
32
+ export declare function resolveInitHostIds(root: string, hostOverride?: string): {
33
+ ids: string[];
34
+ source: 'flag' | 'project' | 'process' | 'none';
35
+ error?: string;
36
+ };
9
37
  export declare function detectHost(root: string): HostInfo;
10
38
  //# sourceMappingURL=host.d.ts.map