@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
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.3';
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') {
@@ -112,7 +112,7 @@ async function main() {
112
112
  finish(EXIT.USAGE);
113
113
  break;
114
114
  }
115
- await revoke(root, key, json);
115
+ await revoke(root, key, json, parsed.flags.yes === true);
116
116
  break;
117
117
  }
118
118
  case 'mcp': {
@@ -137,7 +137,7 @@ function showHelp() {
137
137
  Usage: envseal <command> [options]
138
138
 
139
139
  Commands:
140
- init [--host <name>] Initialize env.schema.jsonc
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
@@ -12,6 +12,7 @@ type BrokerOpts = ConstructorParameters<typeof Broker>[0];
12
12
  export declare function createBroker(root: string, opts?: {
13
13
  onConfirm?: BrokerOpts['onConfirm'];
14
14
  onApprovalNeeded?: BrokerOpts['onApprovalNeeded'];
15
+ onRevokeConfirm?: BrokerOpts['onRevokeConfirm'];
15
16
  }): Promise<Broker>;
16
17
  /**
17
18
  * Whether there is a human we could put a question to.
package/dist/cli-utils.js CHANGED
@@ -25,6 +25,7 @@ export async function createBroker(root, opts) {
25
25
  prompter,
26
26
  onConfirm: opts?.onConfirm,
27
27
  onApprovalNeeded: opts?.onApprovalNeeded,
28
+ onRevokeConfirm: opts?.onRevokeConfirm,
28
29
  });
29
30
  registerDisposable(() => broker.dispose());
30
31
  return broker;
@@ -160,9 +161,9 @@ export function parseArgs(argv) {
160
161
  const COMMAND_USAGE = {
161
162
  init: `Usage: envseal init [--host <name>] [--json] [--project <path>]
162
163
 
163
- Initialize env.schema.jsonc, declaring every environment-variable reference found by scanning the project.
164
+ Initialize env.schema.jsonc, merge AGENTS.md (Layer 1), and write project host MCP/config for every matching marker.
164
165
 
165
- --host <name> Override host detection. Valid values: claude-code, cursor, continue, aider, windsurf, cline, zed, codex, jetbrains, goose, copilot, generic, unknown.
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.
166
167
  --json Output as JSON.
167
168
  --project <path> Project root (default: auto-detect).`,
168
169
  ensure: `Usage: envseal ensure [--check] [--json] [--project <path>]
@@ -201,15 +202,17 @@ Asks for confirmation first; --yes (or ENVSEAL_ASSUME_YES=1) pre-approves it.
201
202
  --project <path> Project root (default: auto-detect).`,
202
203
  doctor: `Usage: envseal doctor [--json] [--project <path>]
203
204
 
204
- Audit the project configuration: detected host and tier, gitignore coverage,
205
- 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.
206
207
 
207
208
  --json Output as JSON.
208
209
  --project <path> Project root (default: auto-detect).`,
209
- revoke: `Usage: envseal revoke <KEY> [--json] [--project <path>]
210
+ revoke: `Usage: envseal revoke <KEY> [--yes] [--json] [--project <path>]
210
211
 
211
212
  Remove a key from its sink and report the provider's rotation URL.
213
+ Asks for confirmation first; --yes (or ENVSEAL_ASSUME_YES=1) pre-approves it.
212
214
 
215
+ --yes Skip the confirmation prompt.
213
216
  --json Output as JSON.
214
217
  --project <path> Project root (default: auto-detect).`,
215
218
  mcp: `Usage: envseal mcp
@@ -1,12 +1,13 @@
1
1
  import { existsSync, statSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { readFileSync } from 'node:fs';
4
3
  import { SepError } from '@envseal/protocol';
4
+ import { inspectDotenvGitSafety, projectPaths } from '@envseal/core';
5
5
  import { emit, fail } from '../output.js';
6
6
  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,
@@ -25,12 +26,9 @@ export async function doctor(root, json) {
25
26
  const status = await broker.describe();
26
27
  const gitignorePath = join(root, '.gitignore');
27
28
  const envPath = join(root, '.env');
28
- // Check gitignore
29
- let gitignoreCovers = false;
30
- if (existsSync(gitignorePath)) {
31
- const gitignoreContent = readFileSync(gitignorePath, 'utf-8');
32
- gitignoreCovers = gitignoreContent.includes('.env');
33
- }
29
+ const gitSafety = inspectDotenvGitSafety(projectPaths(root));
30
+ const gitignoreCovers = gitSafety.ignored;
31
+ const hookFailClosed = process.env.ENVSEAL_HOOK_FAIL_CLOSED === '1';
34
32
  // Check .env permissions
35
33
  let envFileOk = false;
36
34
  if (existsSync(envPath)) {
@@ -38,6 +36,7 @@ export async function doctor(root, json) {
38
36
  envFileOk = (stats.mode & 0o077) === 0;
39
37
  }
40
38
  const host = detectHost(root);
39
+ const inspection = inspectPrimaryHostWiring(root, host.id, { probe: true });
41
40
  const output = {
42
41
  projectRoot: root,
43
42
  manifestPath,
@@ -48,24 +47,42 @@ export async function doctor(root, json) {
48
47
  reason: host.reason,
49
48
  recommendation: host.recommendation,
50
49
  },
50
+ agentWiring: inspection.wiring,
51
51
  gitignore: {
52
52
  exists: existsSync(gitignorePath),
53
53
  covers: gitignoreCovers,
54
54
  },
55
55
  envFile: {
56
56
  exists: existsSync(envPath),
57
- isTracked: false,
57
+ isTracked: gitSafety.tracked,
58
58
  permissionsOk: envFileOk,
59
59
  },
60
+ hookFailClosed,
60
61
  missingRequiredCount: status.missingRequired.length,
61
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
+ }),
62
73
  };
63
74
  if (!json) {
64
75
  console.log(`Project root: ${root}`);
65
76
  console.log(`Host: ${host.name} (Tier ${host.tier})`);
66
77
  console.log(` ${host.reason}`);
67
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}`);
68
84
  console.log(`Gitignore covers .env: ${gitignoreCovers ? 'yes' : 'no'}`);
85
+ console.log(`Hook on internal error: ${hookFailClosed ? 'fail-closed' : 'fail-open (default)'}`);
69
86
  console.log(`Missing required keys: ${status.missingRequired.length}`);
70
87
  if (status.missingRequired.length > 0) {
71
88
  for (const key of status.missingRequired) {
@@ -76,8 +93,7 @@ export async function doctor(root, json) {
76
93
  else {
77
94
  emit(json, '', output);
78
95
  }
79
- // Exit with UNSATISFIED if required keys are missing
80
- if (status.missingRequired.length > 0) {
96
+ if (status.missingRequired.length > 0 || wiringFailsDoctor(inspection)) {
81
97
  finish(EXIT.UNSATISFIED);
82
98
  return;
83
99
  }
@@ -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
- // The ids detectHost can ever return. --host used to accept any string
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
- if (hostOverride !== undefined && !KNOWN_HOST_IDS.includes(hostOverride)) {
29
- console.error(`Error: unknown --host '${hostOverride}'. Valid values: ${KNOWN_HOST_IDS.join(', ')}.`);
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 host = hostOverride
64
- ? { id: hostOverride, name: hostOverride, tier: 'C', reason: 'specified with --host', recommendation: '' }
65
- : detectHost(root);
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: host.id,
69
- protectionTier: host.tier,
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(` Host: ${host.name} (protection tier ${host.tier})`);
100
- if (host.recommendation)
101
- console.log(` ${host.recommendation}`);
102
- if (hostOverride) {
103
- // The override line above is what was ASKED for, not what is here. An
104
- // auto-detected init on the same project can print a different tier, and
105
- // doctor is the one that reports evidence.
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 (host.id === 'claude-code') {
109
- // Without this the first run ends at a manifest and no connection: init
110
- // writes env.schema.jsonc but nothing tells the user the agent still has
111
- // to be pointed at the broker.
112
- console.log('');
113
- console.log('Connect your agent: create .mcp.json in the project root containing');
114
- console.log(' {"mcpServers":{"envseal-mcp":{"command":"envseal-mcp","args":[]}}}');
115
- console.log('then restart Claude Code — or install plugins/claude-code for Tier A hooks.');
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) {
@@ -1,2 +1,2 @@
1
- export declare function revoke(root: string, key: string, json: boolean): Promise<void>;
1
+ export declare function revoke(root: string, key: string, json: boolean, assumeYes?: boolean): Promise<void>;
2
2
  //# sourceMappingURL=revoke.d.ts.map
@@ -1,10 +1,48 @@
1
+ import { createInterface } from 'node:readline';
2
+ import { SepError } from '@envseal/protocol';
3
+ import { revokeConfirmationBody } from '@envseal/core';
1
4
  import { emit, fail } from '../output.js';
2
5
  import { EXIT } from '../exit-codes.js';
3
6
  import { createBroker } from '../cli-utils.js';
4
7
  import { finish } from '../exit.js';
5
- export async function revoke(root, key, json) {
8
+ /**
9
+ * Ask the user to approve removing stored credentials.
10
+ *
11
+ * Without this the broker's revoke confirmation callback is absent and
12
+ * `revoke` always throws SEP_CONFIRMATION_DENIED. Mirrors run.ts: same
13
+ * TTY/--yes/ENVSEAL_ASSUME_YES gate, same fail-closed default for headless.
14
+ */
15
+ async function confirmRevokeInteractive(keys) {
16
+ if (process.env.ENVSEAL_ASSUME_YES === '1')
17
+ return true;
18
+ if (!process.stdin.isTTY) {
19
+ throw new SepError({
20
+ code: 'SEP_NO_INTERACTIVE_SURFACE',
21
+ userMessage: 'envseal revoke needs confirmation before removing stored credentials, but there is no terminal to ask on. ' +
22
+ 'Nothing was removed. Re-run it yourself in an interactive shell to review and ' +
23
+ 'approve the revocation; see docs/ci.md for the supported headless pipeline setup.',
24
+ });
25
+ }
26
+ const body = revokeConfirmationBody(keys, process.cwd());
27
+ process.stderr.write(`\n${body}\n\n`);
28
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
29
+ try {
30
+ const answer = await new Promise((resolve) => {
31
+ rl.question('Continue? [y/N] ', resolve);
32
+ });
33
+ return /^y(es)?$/i.test(answer.trim());
34
+ }
35
+ finally {
36
+ rl.close();
37
+ }
38
+ }
39
+ export async function revoke(root, key, json, assumeYes = false) {
6
40
  try {
7
- const broker = await createBroker(root);
41
+ const broker = await createBroker(root, {
42
+ onRevokeConfirm: assumeYes
43
+ ? async () => true
44
+ : async (keys) => confirmRevokeInteractive(keys),
45
+ });
8
46
  const results = await broker.revoke({
9
47
  keys: [key],
10
48
  });
@@ -22,6 +22,7 @@ export function exitCodeForError(e) {
22
22
  case 'SEP_GITIGNORE_UNSAFE':
23
23
  case 'SEP_PROBE_NOT_APPROVED':
24
24
  case 'SEP_VALUE_IN_REQUEST':
25
+ case 'SEP_PATTERN_UNSAFE':
25
26
  return EXIT.USAGE;
26
27
  case 'SEP_NO_INTERACTIVE_SURFACE':
27
28
  return EXIT.NO_SURFACE;
@@ -36,6 +37,7 @@ export function exitCodeForError(e) {
36
37
  case 'SEP_TICKET_UNKNOWN':
37
38
  case 'SEP_TARGET_CHANGED':
38
39
  case 'SEP_CONFIRMATION_DENIED':
40
+ case 'SEP_KEYS_MISSING':
39
41
  return EXIT.UNSATISFIED;
40
42
  default: {
41
43
  const _exhaustive = code;
@@ -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