@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
package/dist/bin.js
CHANGED
|
@@ -13,7 +13,7 @@ import { doctor } from './commands/doctor.js';
|
|
|
13
13
|
import { revoke } from './commands/revoke.js';
|
|
14
14
|
import { mcp } from './commands/mcp.js';
|
|
15
15
|
import { init } from './commands/init.js';
|
|
16
|
-
const VERSION = '0.1.
|
|
16
|
+
const VERSION = '0.1.5';
|
|
17
17
|
async function main() {
|
|
18
18
|
const argv = process.argv.slice(2);
|
|
19
19
|
if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
|
|
@@ -137,7 +137,7 @@ function showHelp() {
|
|
|
137
137
|
Usage: envseal <command> [options]
|
|
138
138
|
|
|
139
139
|
Commands:
|
|
140
|
-
init [--host <name>] Initialize
|
|
140
|
+
init [--host <name>] Initialize manifest, AGENTS.md, and host MCP
|
|
141
141
|
ensure [--check] Prompt for all missing required keys
|
|
142
142
|
(--check: report only, exit 0/1, never prompt)
|
|
143
143
|
set <KEY> Prompt for a single key
|
package/dist/cli-utils.js
CHANGED
|
@@ -161,9 +161,9 @@ export function parseArgs(argv) {
|
|
|
161
161
|
const COMMAND_USAGE = {
|
|
162
162
|
init: `Usage: envseal init [--host <name>] [--json] [--project <path>]
|
|
163
163
|
|
|
164
|
-
Initialize env.schema.jsonc,
|
|
164
|
+
Initialize env.schema.jsonc, merge AGENTS.md (Layer 1), and write project host MCP/config for every matching marker.
|
|
165
165
|
|
|
166
|
-
--host <name>
|
|
166
|
+
--host <name> Write this host's project config (comma-separated ok). Valid values: claude-code, cursor, continue, aider, windsurf, cline, zed, codex, jetbrains, goose, copilot, generic, unknown, openhands.
|
|
167
167
|
--json Output as JSON.
|
|
168
168
|
--project <path> Project root (default: auto-detect).`,
|
|
169
169
|
ensure: `Usage: envseal ensure [--check] [--json] [--project <path>]
|
|
@@ -202,8 +202,8 @@ Asks for confirmation first; --yes (or ENVSEAL_ASSUME_YES=1) pre-approves it.
|
|
|
202
202
|
--project <path> Project root (default: auto-detect).`,
|
|
203
203
|
doctor: `Usage: envseal doctor [--json] [--project <path>]
|
|
204
204
|
|
|
205
|
-
Audit the project configuration: detected host and tier,
|
|
206
|
-
file permissions, missing required keys.
|
|
205
|
+
Audit the project configuration: detected host and tier, agent wiring (MCP +
|
|
206
|
+
AGENTS.md), gitignore coverage, file permissions, missing required keys.
|
|
207
207
|
|
|
208
208
|
--json Output as JSON.
|
|
209
209
|
--project <path> Project root (default: auto-detect).`,
|
package/dist/commands/doctor.js
CHANGED
|
@@ -7,6 +7,7 @@ import { EXIT } from '../exit-codes.js';
|
|
|
7
7
|
import { detectHost } from '../host.js';
|
|
8
8
|
import { createBroker } from '../cli-utils.js';
|
|
9
9
|
import { finish } from '../exit.js';
|
|
10
|
+
import { inspectPrimaryHostWiring, wiringFailsDoctor } from '../host-wiring/inspect.js';
|
|
10
11
|
export async function doctor(root, json) {
|
|
11
12
|
try {
|
|
12
13
|
// An audit of a project with no configuration would report an empty,
|
|
@@ -35,6 +36,7 @@ export async function doctor(root, json) {
|
|
|
35
36
|
envFileOk = (stats.mode & 0o077) === 0;
|
|
36
37
|
}
|
|
37
38
|
const host = detectHost(root);
|
|
39
|
+
const inspection = inspectPrimaryHostWiring(root, host.id, { probe: true });
|
|
38
40
|
const output = {
|
|
39
41
|
projectRoot: root,
|
|
40
42
|
manifestPath,
|
|
@@ -45,6 +47,7 @@ export async function doctor(root, json) {
|
|
|
45
47
|
reason: host.reason,
|
|
46
48
|
recommendation: host.recommendation,
|
|
47
49
|
},
|
|
50
|
+
agentWiring: inspection.wiring,
|
|
48
51
|
gitignore: {
|
|
49
52
|
exists: existsSync(gitignorePath),
|
|
50
53
|
covers: gitignoreCovers,
|
|
@@ -57,12 +60,27 @@ export async function doctor(root, json) {
|
|
|
57
60
|
hookFailClosed,
|
|
58
61
|
missingRequiredCount: status.missingRequired.length,
|
|
59
62
|
missingRequired: status.missingRequired,
|
|
63
|
+
...(inspection.mcp === undefined
|
|
64
|
+
? {}
|
|
65
|
+
: {
|
|
66
|
+
mcp: {
|
|
67
|
+
wired: inspection.mcp.wired,
|
|
68
|
+
status: inspection.mcp.status,
|
|
69
|
+
message: inspection.mcp.message,
|
|
70
|
+
commandOk: inspection.mcp.commandOk,
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
60
73
|
};
|
|
61
74
|
if (!json) {
|
|
62
75
|
console.log(`Project root: ${root}`);
|
|
63
76
|
console.log(`Host: ${host.name} (Tier ${host.tier})`);
|
|
64
77
|
console.log(` ${host.reason}`);
|
|
65
78
|
console.log(` ${host.recommendation}`);
|
|
79
|
+
console.log(`Agent wiring: MCP ${inspection.wiring.mcp}, instructions ${inspection.wiring.instructions}`);
|
|
80
|
+
if (inspection.notOotb) {
|
|
81
|
+
console.log(' This host is not OOTB (print-only MCP). Layer 1 AGENTS.md is the working path.');
|
|
82
|
+
}
|
|
83
|
+
console.log(` ${inspection.message}`);
|
|
66
84
|
console.log(`Gitignore covers .env: ${gitignoreCovers ? 'yes' : 'no'}`);
|
|
67
85
|
console.log(`Hook on internal error: ${hookFailClosed ? 'fail-closed' : 'fail-open (default)'}`);
|
|
68
86
|
console.log(`Missing required keys: ${status.missingRequired.length}`);
|
|
@@ -75,8 +93,7 @@ export async function doctor(root, json) {
|
|
|
75
93
|
else {
|
|
76
94
|
emit(json, '', output);
|
|
77
95
|
}
|
|
78
|
-
|
|
79
|
-
if (status.missingRequired.length > 0) {
|
|
96
|
+
if (status.missingRequired.length > 0 || wiringFailsDoctor(inspection)) {
|
|
80
97
|
finish(EXIT.UNSATISFIED);
|
|
81
98
|
return;
|
|
82
99
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,32 +1,16 @@
|
|
|
1
1
|
import { projectPaths, loadManifest, declareEntries, scanManifestEntry } from '@envseal/core';
|
|
2
2
|
import { SepError } from '@envseal/protocol';
|
|
3
3
|
import { emit, fail } from '../output.js';
|
|
4
|
-
import { detectHost } from '../host.js';
|
|
4
|
+
import { detectHost, resolveInitHostIds } from '../host.js';
|
|
5
5
|
import { scanForEnvKeys, entryForKey } from '../scan.js';
|
|
6
6
|
import { EXIT } from '../exit-codes.js';
|
|
7
7
|
import { finish } from '../exit.js';
|
|
8
|
-
|
|
9
|
-
// silently, recording a host detection would never report and printing a tier
|
|
10
|
-
// computed for a fiction.
|
|
11
|
-
const KNOWN_HOST_IDS = [
|
|
12
|
-
'claude-code',
|
|
13
|
-
'cursor',
|
|
14
|
-
'continue',
|
|
15
|
-
'aider',
|
|
16
|
-
'windsurf',
|
|
17
|
-
'cline',
|
|
18
|
-
'zed',
|
|
19
|
-
'codex',
|
|
20
|
-
'jetbrains',
|
|
21
|
-
'goose',
|
|
22
|
-
'copilot',
|
|
23
|
-
'generic',
|
|
24
|
-
'unknown',
|
|
25
|
-
];
|
|
8
|
+
import { applyHostWiring } from '../host-wiring/apply.js';
|
|
26
9
|
export async function init(root, json, hostOverride) {
|
|
27
10
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
11
|
+
const resolved = resolveInitHostIds(root, hostOverride);
|
|
12
|
+
if (resolved.error !== undefined) {
|
|
13
|
+
console.error(`Error: ${resolved.error}`);
|
|
30
14
|
finish(EXIT.USAGE);
|
|
31
15
|
return;
|
|
32
16
|
}
|
|
@@ -60,13 +44,19 @@ export async function init(root, json, hostOverride) {
|
|
|
60
44
|
// below must be true, not aspirational.
|
|
61
45
|
const result = declareEntries(paths, entries);
|
|
62
46
|
const manifest = loadManifest(paths);
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
47
|
+
const wiring = applyHostWiring(root, resolved.ids);
|
|
48
|
+
// Evidence after write: --host cursor on a bare tree now has `.cursor/`.
|
|
49
|
+
// Never invent a fake tier from the flag alone.
|
|
50
|
+
const detected = detectHost(root);
|
|
51
|
+
const cursorEntry = wiring.hosts.find((h) => h.id === 'cursor');
|
|
52
|
+
const cursorWiring = wiring.cursor;
|
|
66
53
|
const output = {
|
|
67
54
|
manifestPath: paths.manifest,
|
|
68
|
-
host:
|
|
69
|
-
protectionTier:
|
|
55
|
+
host: detected.id,
|
|
56
|
+
protectionTier: detected.tier,
|
|
57
|
+
requestedHosts: resolved.source === 'flag' ? resolved.ids : undefined,
|
|
58
|
+
wiredHosts: resolved.ids,
|
|
59
|
+
wiringSource: resolved.source,
|
|
70
60
|
scanned: discovered.length,
|
|
71
61
|
added: result.added,
|
|
72
62
|
updated: result.updated,
|
|
@@ -74,6 +64,25 @@ export async function init(root, json, hostOverride) {
|
|
|
74
64
|
secretKeys: discovered.filter((d) => d.secret).map((d) => d.key),
|
|
75
65
|
configKeys: discovered.filter((d) => !d.secret).map((d) => d.key),
|
|
76
66
|
entries: manifest?.entries.length ?? 0,
|
|
67
|
+
agentsMd: {
|
|
68
|
+
action: wiring.agentsMd.action,
|
|
69
|
+
path: wiring.agentsMd.path,
|
|
70
|
+
},
|
|
71
|
+
hostWiring: wiring.hosts.map((h) => ({
|
|
72
|
+
id: h.id,
|
|
73
|
+
action: h.action,
|
|
74
|
+
path: h.path,
|
|
75
|
+
})),
|
|
76
|
+
...(cursorWiring === undefined
|
|
77
|
+
? {}
|
|
78
|
+
: {
|
|
79
|
+
cursorWiring: {
|
|
80
|
+
mcp: cursorWiring.mcp,
|
|
81
|
+
rules: cursorWiring.rules,
|
|
82
|
+
mcpPath: cursorWiring.mcpPath,
|
|
83
|
+
rulesPath: cursorWiring.rulesPath,
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
77
86
|
};
|
|
78
87
|
if (json) {
|
|
79
88
|
emit(json, '', output);
|
|
@@ -96,23 +105,42 @@ export async function init(root, json, hostOverride) {
|
|
|
96
105
|
console.log(` Config (not prompted): ${config.map((s) => s.key).join(', ')}`);
|
|
97
106
|
}
|
|
98
107
|
}
|
|
99
|
-
console.log(`
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
console.log(' Override recorded; envseal doctor reports what is actually detected.');
|
|
108
|
+
console.log(` AGENTS.md: ${wiring.agentsMd.action} (Layer 1 — envseal ensure / envseal run --)`);
|
|
109
|
+
console.log(` Detected host: ${detected.name} (protection tier ${detected.tier})`);
|
|
110
|
+
console.log(` ${detected.reason}`);
|
|
111
|
+
if (detected.recommendation)
|
|
112
|
+
console.log(` ${detected.recommendation}`);
|
|
113
|
+
if (resolved.source === 'flag') {
|
|
114
|
+
console.log(` Requested host(s): ${resolved.ids.join(', ')}. Override recorded; envseal doctor reports what is actually detected.`);
|
|
107
115
|
}
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
console.log(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
if (resolved.source === 'none') {
|
|
117
|
+
console.log(' No project host markers and this process is not an IDE.');
|
|
118
|
+
console.log(' Wrote AGENTS.md only. Re-run from the IDE, or `envseal init --host cursor`.');
|
|
119
|
+
}
|
|
120
|
+
else if (resolved.ids.length > 0) {
|
|
121
|
+
console.log(` Wired host(s): ${resolved.ids.join(', ')} (${resolved.source})`);
|
|
122
|
+
}
|
|
123
|
+
for (const entry of wiring.hosts) {
|
|
124
|
+
if (entry.hint) {
|
|
125
|
+
for (const line of entry.hint.split('\n')) {
|
|
126
|
+
console.log(` ${line}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (cursorEntry === undefined && !wiring.bareTerminal) {
|
|
131
|
+
console.log(' Reload MCP / restart the host, then run `envseal doctor`.');
|
|
132
|
+
}
|
|
133
|
+
else if (cursorWiring !== undefined && cursorWiring.mcp !== 'skipped' && wiring.hosts.length === 1) {
|
|
134
|
+
// Cursor entry already printed reloadHint.
|
|
135
|
+
}
|
|
136
|
+
else if (wiring.bareTerminal && resolved.source === 'none') {
|
|
137
|
+
// Already printed the re-run hint.
|
|
138
|
+
}
|
|
139
|
+
else if (wiring.hosts.some((h) => h.id !== 'cursor')) {
|
|
140
|
+
// Per-host hints already cover reload; keep a single closer.
|
|
141
|
+
}
|
|
142
|
+
if (resolved.ids.includes('claude-code')) {
|
|
143
|
+
console.log(' Claude Code: protocol connected (Tier B) via .mcp.json. Plugin = Tier A.');
|
|
116
144
|
}
|
|
117
145
|
}
|
|
118
146
|
catch (error) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 1 instruction file shipped by `envseal init`.
|
|
3
|
+
*
|
|
4
|
+
* Keep this string identical to `plugins/generic/AGENTS.md`. The host-wiring
|
|
5
|
+
* test fails if they drift. Embedding (not reading plugins/ at runtime) is
|
|
6
|
+
* what makes published `@envseal/cli` able to write the file.
|
|
7
|
+
*/
|
|
8
|
+
export declare const AGENTS_MD_CONTENT = "# envseal \u2014 instructions for any coding agent\n\nThis repository uses envseal to provision secrets. The secret values must never\nappear in this chat, in your tool output, in files you write, or in commands you\nrun. Follow these rules without exception.\n\n## You must never\n\n- Read, write, or copy the contents of any `.env` or `.env.*` file. (`.env.example`\n is the only exception: it holds placeholders, never values.)\n- Run `printenv`, bare `env`, `export -p`, `set`, or `echo $VAR` / `echo $KEY...`\n to inspect or display environment variables.\n- Use `cat`, `head`, `tail`, `less`, `grep`, `xxd`, `base64`, `sed`, or any other\n reader on a secrets file: `*.pem`, `*.key`, `id_rsa*`, `credentials.json`,\n `secrets.json` / `secrets.yaml` / `secrets.toml`, `.envseal/*`.\n- Ask the user to paste an API key, token, or connection string into the chat.\n- Echo an environment variable into a transcript, log, diff, or tool result.\n\n## How to check which keys exist\n\n```sh\nenvseal status # human-readable\nenvseal status --json # machine-readable; never contains values\n```\n\nStatuses are presence + metadata only (format-valid, length bucket, fingerprint,\nlast verified). There is no way to make envseal print a value.\n\n## How to provision a missing key\n\n```sh\nenvseal ensure # prompts the user for every missing required key\nenvseal set KEY # prompts for a single key\n```\n\nNever ask the user to add a key to `.env` and tell you about it. Run\n`envseal ensure` instead, and wait for its exit code.\n\n## How to run a command that needs secrets\n\n```sh\nenvseal run -- <command...>\n```\n\nSecrets are injected only into that child process and its stdout/stderr are\nredacted. Never run the command plain and never try to read the key yourself.\n\n## How to verify a key actually works\n\n```sh\nenvseal verify # classified results: ok, auth_failed, rate_limited, ...\n```\n\n## How to ask the user what is wrong\n\n```sh\nenvseal doctor # project root, detected host + tier, gitignore, missing keys\n```\n\n## Exit codes (for scripts)\n\n`0` ok \u00B7 `1` required keys missing \u00B7 `2` usage error \u00B7 `3` cancelled \u00B7\n`4` no interactive surface \u00B7 `5` sink failure \u00B7 `6` verification failed.\nUse them; never parse human text for secrets.\n";
|
|
9
|
+
//# sourceMappingURL=agents-md-content.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 1 instruction file shipped by `envseal init`.
|
|
3
|
+
*
|
|
4
|
+
* Keep this string identical to `plugins/generic/AGENTS.md`. The host-wiring
|
|
5
|
+
* test fails if they drift. Embedding (not reading plugins/ at runtime) is
|
|
6
|
+
* what makes published `@envseal/cli` able to write the file.
|
|
7
|
+
*/
|
|
8
|
+
export const AGENTS_MD_CONTENT = `# envseal — instructions for any coding agent
|
|
9
|
+
|
|
10
|
+
This repository uses envseal to provision secrets. The secret values must never
|
|
11
|
+
appear in this chat, in your tool output, in files you write, or in commands you
|
|
12
|
+
run. Follow these rules without exception.
|
|
13
|
+
|
|
14
|
+
## You must never
|
|
15
|
+
|
|
16
|
+
- Read, write, or copy the contents of any \`.env\` or \`.env.*\` file. (\`.env.example\`
|
|
17
|
+
is the only exception: it holds placeholders, never values.)
|
|
18
|
+
- Run \`printenv\`, bare \`env\`, \`export -p\`, \`set\`, or \`echo $VAR\` / \`echo $KEY...\`
|
|
19
|
+
to inspect or display environment variables.
|
|
20
|
+
- Use \`cat\`, \`head\`, \`tail\`, \`less\`, \`grep\`, \`xxd\`, \`base64\`, \`sed\`, or any other
|
|
21
|
+
reader on a secrets file: \`*.pem\`, \`*.key\`, \`id_rsa*\`, \`credentials.json\`,
|
|
22
|
+
\`secrets.json\` / \`secrets.yaml\` / \`secrets.toml\`, \`.envseal/*\`.
|
|
23
|
+
- Ask the user to paste an API key, token, or connection string into the chat.
|
|
24
|
+
- Echo an environment variable into a transcript, log, diff, or tool result.
|
|
25
|
+
|
|
26
|
+
## How to check which keys exist
|
|
27
|
+
|
|
28
|
+
\`\`\`sh
|
|
29
|
+
envseal status # human-readable
|
|
30
|
+
envseal status --json # machine-readable; never contains values
|
|
31
|
+
\`\`\`
|
|
32
|
+
|
|
33
|
+
Statuses are presence + metadata only (format-valid, length bucket, fingerprint,
|
|
34
|
+
last verified). There is no way to make envseal print a value.
|
|
35
|
+
|
|
36
|
+
## How to provision a missing key
|
|
37
|
+
|
|
38
|
+
\`\`\`sh
|
|
39
|
+
envseal ensure # prompts the user for every missing required key
|
|
40
|
+
envseal set KEY # prompts for a single key
|
|
41
|
+
\`\`\`
|
|
42
|
+
|
|
43
|
+
Never ask the user to add a key to \`.env\` and tell you about it. Run
|
|
44
|
+
\`envseal ensure\` instead, and wait for its exit code.
|
|
45
|
+
|
|
46
|
+
## How to run a command that needs secrets
|
|
47
|
+
|
|
48
|
+
\`\`\`sh
|
|
49
|
+
envseal run -- <command...>
|
|
50
|
+
\`\`\`
|
|
51
|
+
|
|
52
|
+
Secrets are injected only into that child process and its stdout/stderr are
|
|
53
|
+
redacted. Never run the command plain and never try to read the key yourself.
|
|
54
|
+
|
|
55
|
+
## How to verify a key actually works
|
|
56
|
+
|
|
57
|
+
\`\`\`sh
|
|
58
|
+
envseal verify # classified results: ok, auth_failed, rate_limited, ...
|
|
59
|
+
\`\`\`
|
|
60
|
+
|
|
61
|
+
## How to ask the user what is wrong
|
|
62
|
+
|
|
63
|
+
\`\`\`sh
|
|
64
|
+
envseal doctor # project root, detected host + tier, gitignore, missing keys
|
|
65
|
+
\`\`\`
|
|
66
|
+
|
|
67
|
+
## Exit codes (for scripts)
|
|
68
|
+
|
|
69
|
+
\`0\` ok · \`1\` required keys missing · \`2\` usage error · \`3\` cancelled ·
|
|
70
|
+
\`4\` no interactive surface · \`5\` sink failure · \`6\` verification failed.
|
|
71
|
+
Use them; never parse human text for secrets.
|
|
72
|
+
`;
|
|
73
|
+
//# sourceMappingURL=agents-md-content.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type AgentsMdAction = 'created' | 'merged' | 'unchanged';
|
|
2
|
+
/**
|
|
3
|
+
* The envseal imperative: never read .env, use ensure/run instead of a paste.
|
|
4
|
+
* Doctor and init both use this so "instructions exist" is not just a filename.
|
|
5
|
+
*/
|
|
6
|
+
export declare function hasEnvsealImperative(text: string): boolean;
|
|
7
|
+
export declare function inspectAgentsMd(root: string): {
|
|
8
|
+
path: string;
|
|
9
|
+
exists: boolean;
|
|
10
|
+
instructions: 'ok' | 'missing';
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Merge plugins/generic/AGENTS.md into project-root AGENTS.md.
|
|
14
|
+
* Creates the file, or appends an envseal section; never clobbers unrelated content.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mergeAgentsMd(root: string): {
|
|
17
|
+
action: AgentsMdAction;
|
|
18
|
+
path: string;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=agents-md.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { AGENTS_MD_CONTENT } from './agents-md-content.js';
|
|
4
|
+
/**
|
|
5
|
+
* The envseal imperative: never read .env, use ensure/run instead of a paste.
|
|
6
|
+
* Doctor and init both use this so "instructions exist" is not just a filename.
|
|
7
|
+
*/
|
|
8
|
+
export function hasEnvsealImperative(text) {
|
|
9
|
+
const neverEnv = /never/i.test(text) && /\.env/i.test(text);
|
|
10
|
+
const useEnsure = /envseal\s+ensure/i.test(text);
|
|
11
|
+
const useRun = /envseal\s+run/i.test(text);
|
|
12
|
+
return neverEnv && useEnsure && useRun;
|
|
13
|
+
}
|
|
14
|
+
export function inspectAgentsMd(root) {
|
|
15
|
+
const path = join(root, 'AGENTS.md');
|
|
16
|
+
if (!existsSync(path)) {
|
|
17
|
+
return { path, exists: false, instructions: 'missing' };
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const text = readFileSync(path, 'utf8');
|
|
21
|
+
return {
|
|
22
|
+
path,
|
|
23
|
+
exists: true,
|
|
24
|
+
instructions: hasEnvsealImperative(text) ? 'ok' : 'missing',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { path, exists: true, instructions: 'missing' };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Merge plugins/generic/AGENTS.md into project-root AGENTS.md.
|
|
33
|
+
* Creates the file, or appends an envseal section; never clobbers unrelated content.
|
|
34
|
+
*/
|
|
35
|
+
export function mergeAgentsMd(root) {
|
|
36
|
+
const path = join(root, 'AGENTS.md');
|
|
37
|
+
if (!existsSync(path)) {
|
|
38
|
+
writeFileSync(path, AGENTS_MD_CONTENT, 'utf8');
|
|
39
|
+
return { action: 'created', path };
|
|
40
|
+
}
|
|
41
|
+
const existing = readFileSync(path, 'utf8');
|
|
42
|
+
if (hasEnvsealImperative(existing)) {
|
|
43
|
+
return { action: 'unchanged', path };
|
|
44
|
+
}
|
|
45
|
+
const trimmed = existing.replace(/\s+$/u, '');
|
|
46
|
+
const next = `${trimmed}\n\n${AGENTS_MD_CONTENT}`;
|
|
47
|
+
writeFileSync(path, next, 'utf8');
|
|
48
|
+
return { action: 'merged', path };
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=agents-md.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aider config shipped by `envseal init --host aider`.
|
|
3
|
+
*
|
|
4
|
+
* Keep this string identical to `plugins/aider/.aider.conf.yml`. The host-wiring
|
|
5
|
+
* test fails if they drift.
|
|
6
|
+
*/
|
|
7
|
+
export declare const AIDER_CONF_YML = "# .aider.conf.yml \u2014 envseal for Aider (Tier C host, Tier-4 CLI binding)\n#\n# Aider renders every file it reads into the chat context. NEVER add `.env`\n# or `.env.*` to the `read` list \u2014 that is exactly the leak path envseal exists\n# to prevent. `env.schema.jsonc` and `.env.example` contain declarations and\n# placeholders only and are safe to read.\n\nmodel: gpt-4o\nedit-format: editor-diff\n\nread:\n - env.schema.jsonc\n - .env.example\n\n# Optional: run a command after every edit with secrets injected.\n# auto-test:\n# command: \"../../.../envseal run -- pnpm test\"\n\n# --- Tier-4 shell recipe (run from Aider's REPL) --------------------------\n#\n# /run envseal status # which declared keys are present\n# /run envseal ensure # prompt the user for every missing key\n# /run envseal run -- pytest # run tests with secrets injected\n# /run envseal verify # probe the keys end-to-end\n# /run envseal doctor # report host + tier + config health\n#\n# `envseal ensure` and `envseal run --` are the only ways to obtain or use\n# secret values inside Aider. Never ask the user to paste a key into the chat;\n# never read `.env`; never `echo $KEY`.\n";
|
|
8
|
+
//# sourceMappingURL=aider-conf.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aider config shipped by `envseal init --host aider`.
|
|
3
|
+
*
|
|
4
|
+
* Keep this string identical to `plugins/aider/.aider.conf.yml`. The host-wiring
|
|
5
|
+
* test fails if they drift.
|
|
6
|
+
*/
|
|
7
|
+
export const AIDER_CONF_YML = `# .aider.conf.yml — envseal for Aider (Tier C host, Tier-4 CLI binding)
|
|
8
|
+
#
|
|
9
|
+
# Aider renders every file it reads into the chat context. NEVER add \`.env\`
|
|
10
|
+
# or \`.env.*\` to the \`read\` list — that is exactly the leak path envseal exists
|
|
11
|
+
# to prevent. \`env.schema.jsonc\` and \`.env.example\` contain declarations and
|
|
12
|
+
# placeholders only and are safe to read.
|
|
13
|
+
|
|
14
|
+
model: gpt-4o
|
|
15
|
+
edit-format: editor-diff
|
|
16
|
+
|
|
17
|
+
read:
|
|
18
|
+
- env.schema.jsonc
|
|
19
|
+
- .env.example
|
|
20
|
+
|
|
21
|
+
# Optional: run a command after every edit with secrets injected.
|
|
22
|
+
# auto-test:
|
|
23
|
+
# command: "../../.../envseal run -- pnpm test"
|
|
24
|
+
|
|
25
|
+
# --- Tier-4 shell recipe (run from Aider's REPL) --------------------------
|
|
26
|
+
#
|
|
27
|
+
# /run envseal status # which declared keys are present
|
|
28
|
+
# /run envseal ensure # prompt the user for every missing key
|
|
29
|
+
# /run envseal run -- pytest # run tests with secrets injected
|
|
30
|
+
# /run envseal verify # probe the keys end-to-end
|
|
31
|
+
# /run envseal doctor # report host + tier + config health
|
|
32
|
+
#
|
|
33
|
+
# \`envseal ensure\` and \`envseal run --\` are the only ways to obtain or use
|
|
34
|
+
# secret values inside Aider. Never ask the user to paste a key into the chat;
|
|
35
|
+
# never read \`.env\`; never \`echo $KEY\`.
|
|
36
|
+
`;
|
|
37
|
+
//# sourceMappingURL=aider-conf.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { McpWriteAction } from './mcp.js';
|
|
2
|
+
export declare function aiderConfPath(root: string): string | undefined;
|
|
3
|
+
export declare function aiderReadListIncludesEnv(text: string): boolean;
|
|
4
|
+
export declare function inspectAiderConf(root: string): {
|
|
5
|
+
path: string | undefined;
|
|
6
|
+
envOnRead: boolean;
|
|
7
|
+
wired: boolean;
|
|
8
|
+
message: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function mergeAiderConf(root: string): {
|
|
11
|
+
action: McpWriteAction;
|
|
12
|
+
path: string;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=aider.d.ts.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { AIDER_CONF_YML } from './aider-conf.js';
|
|
4
|
+
const AIDER_FILENAMES = [
|
|
5
|
+
'.aider.conf.yml',
|
|
6
|
+
'.aider.conf.yaml',
|
|
7
|
+
'aider.conf.yml',
|
|
8
|
+
'aider.conf.yaml',
|
|
9
|
+
'.aider.conf.json',
|
|
10
|
+
'aider.conf.json',
|
|
11
|
+
];
|
|
12
|
+
/** A `read:` list item that would dump `.env` into Aider's chat context. */
|
|
13
|
+
const ENV_READ_ITEM = /^\s*-\s+['"]?\.env(?:\..*?)?['"]?\s*$/;
|
|
14
|
+
export function aiderConfPath(root) {
|
|
15
|
+
for (const name of AIDER_FILENAMES) {
|
|
16
|
+
const path = join(root, name);
|
|
17
|
+
if (existsSync(path))
|
|
18
|
+
return path;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
export function aiderReadListIncludesEnv(text) {
|
|
23
|
+
return text.split(/\r?\n/).some((line) => ENV_READ_ITEM.test(line) && !/example/i.test(line));
|
|
24
|
+
}
|
|
25
|
+
function stripEnvFromReadList(text) {
|
|
26
|
+
return text
|
|
27
|
+
.split(/\r?\n/)
|
|
28
|
+
.filter((line) => !(ENV_READ_ITEM.test(line) && !/example/i.test(line)))
|
|
29
|
+
.join('\n');
|
|
30
|
+
}
|
|
31
|
+
function ensureSafeReadEntries(text) {
|
|
32
|
+
let next = stripEnvFromReadList(text);
|
|
33
|
+
if (!/^read:/m.test(next)) {
|
|
34
|
+
const trimmed = next.replace(/\s+$/u, '');
|
|
35
|
+
return `${trimmed}\n\nread:\n - env.schema.jsonc\n - .env.example\n`;
|
|
36
|
+
}
|
|
37
|
+
if (!/env\.schema\.jsonc/.test(next)) {
|
|
38
|
+
next = next.replace(/^read:\s*$/m, 'read:\n - env.schema.jsonc');
|
|
39
|
+
if (!/env\.schema\.jsonc/.test(next)) {
|
|
40
|
+
next = next.replace(/^read:/m, 'read:\n - env.schema.jsonc');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!/\.env\.example/.test(next)) {
|
|
44
|
+
next = next.replace(/(env\.schema\.jsonc[^\n]*)/, '$1\n - .env.example');
|
|
45
|
+
}
|
|
46
|
+
return next;
|
|
47
|
+
}
|
|
48
|
+
export function inspectAiderConf(root) {
|
|
49
|
+
const path = aiderConfPath(root);
|
|
50
|
+
if (path === undefined) {
|
|
51
|
+
return {
|
|
52
|
+
path,
|
|
53
|
+
envOnRead: false,
|
|
54
|
+
wired: false,
|
|
55
|
+
message: 'No Aider config found. Run `envseal init --host aider` to write .aider.conf.yml.',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const text = readFileSync(path, 'utf8');
|
|
59
|
+
const envOnRead = aiderReadListIncludesEnv(text);
|
|
60
|
+
if (envOnRead) {
|
|
61
|
+
return {
|
|
62
|
+
path,
|
|
63
|
+
envOnRead: true,
|
|
64
|
+
wired: false,
|
|
65
|
+
message: `${path} lists .env under read: — Aider would paste secrets into chat. Re-run envseal init --host aider.`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
path,
|
|
70
|
+
envOnRead: false,
|
|
71
|
+
wired: true,
|
|
72
|
+
message: 'Aider config does not put .env on the read list.',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function mergeAiderConf(root) {
|
|
76
|
+
const existing = aiderConfPath(root);
|
|
77
|
+
const path = existing ?? join(root, '.aider.conf.yml');
|
|
78
|
+
if (!existsSync(path)) {
|
|
79
|
+
writeFileSync(path, AIDER_CONF_YML, 'utf8');
|
|
80
|
+
return { action: 'created', path };
|
|
81
|
+
}
|
|
82
|
+
const text = readFileSync(path, 'utf8');
|
|
83
|
+
const next = ensureSafeReadEntries(text);
|
|
84
|
+
if (next === text) {
|
|
85
|
+
return { action: 'unchanged', path };
|
|
86
|
+
}
|
|
87
|
+
writeFileSync(path, next.endsWith('\n') ? next : `${next}\n`, 'utf8');
|
|
88
|
+
return { action: 'merged', path };
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=aider.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type AgentsMdAction } from './agents-md.js';
|
|
2
|
+
import { type CursorWiringResult } from './cursor.js';
|
|
3
|
+
import { type McpWriteAction } from './mcp.js';
|
|
4
|
+
export type HostWiringEntry = {
|
|
5
|
+
id: string;
|
|
6
|
+
action: McpWriteAction | 'printed';
|
|
7
|
+
path?: string;
|
|
8
|
+
hint: string;
|
|
9
|
+
extra?: Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
export type ApplyWiringResult = {
|
|
12
|
+
agentsMd: {
|
|
13
|
+
action: AgentsMdAction;
|
|
14
|
+
path: string;
|
|
15
|
+
};
|
|
16
|
+
hosts: HostWiringEntry[];
|
|
17
|
+
cursor?: CursorWiringResult;
|
|
18
|
+
/** True when init wrote nothing host-specific besides AGENTS.md. */
|
|
19
|
+
bareTerminal: boolean;
|
|
20
|
+
};
|
|
21
|
+
export declare function applyHostWiring(root: string, hostIds: string[], platform?: NodeJS.Platform): ApplyWiringResult;
|
|
22
|
+
//# sourceMappingURL=apply.d.ts.map
|