@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.
- package/dist/bin.js +3 -3
- package/dist/cli-utils.d.ts +1 -0
- package/dist/cli-utils.js +8 -5
- package/dist/commands/doctor.js +26 -10
- package/dist/commands/init.js +70 -42
- package/dist/commands/revoke.d.ts +1 -1
- package/dist/commands/revoke.js +40 -2
- package/dist/exit-codes.js +2 -0
- 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') {
|
|
@@ -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
|
|
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.d.ts
CHANGED
|
@@ -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,
|
|
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>
|
|
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,
|
|
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
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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:
|
|
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
|
-
|
|
80
|
-
if (status.missingRequired.length > 0) {
|
|
96
|
+
if (status.missingRequired.length > 0 || wiringFailsDoctor(inspection)) {
|
|
81
97
|
finish(EXIT.UNSATISFIED);
|
|
82
98
|
return;
|
|
83
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) {
|
|
@@ -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
|
package/dist/commands/revoke.js
CHANGED
|
@@ -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
|
-
|
|
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
|
});
|
package/dist/exit-codes.js
CHANGED
|
@@ -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
|