@envseal/cli 0.1.0

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.
@@ -0,0 +1,89 @@
1
+ import { existsSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { readFileSync } from 'node:fs';
4
+ import { SepError } from '@envseal/protocol';
5
+ import { emit, fail } from '../output.js';
6
+ import { EXIT } from '../exit-codes.js';
7
+ import { detectHost } from '../host.js';
8
+ import { createBroker } from '../cli-utils.js';
9
+ import { finish } from '../exit.js';
10
+ export async function doctor(root, json) {
11
+ try {
12
+ // An audit of a project with no configuration would report an empty,
13
+ // healthy-looking bill of health: gitignore/no, missing keys/0, exit 0.
14
+ // Every other command treats that state as SEP_NOT_DECLARED; the audit
15
+ // command must not be the one place it reads as success.
16
+ const manifestPath = join(root, 'env.schema.jsonc');
17
+ if (!existsSync(manifestPath)) {
18
+ fail(json, new SepError({
19
+ code: 'SEP_NOT_DECLARED',
20
+ userMessage: 'No env.schema.jsonc in this project (or parents). Run `envseal init` to create one.',
21
+ }));
22
+ return;
23
+ }
24
+ const broker = await createBroker(root);
25
+ const status = await broker.describe();
26
+ const gitignorePath = join(root, '.gitignore');
27
+ 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
+ }
34
+ // Check .env permissions
35
+ let envFileOk = false;
36
+ if (existsSync(envPath)) {
37
+ const stats = statSync(envPath);
38
+ envFileOk = (stats.mode & 0o077) === 0;
39
+ }
40
+ const host = detectHost(root);
41
+ const output = {
42
+ projectRoot: root,
43
+ manifestPath,
44
+ host: {
45
+ id: host.id,
46
+ name: host.name,
47
+ tier: host.tier,
48
+ reason: host.reason,
49
+ recommendation: host.recommendation,
50
+ },
51
+ gitignore: {
52
+ exists: existsSync(gitignorePath),
53
+ covers: gitignoreCovers,
54
+ },
55
+ envFile: {
56
+ exists: existsSync(envPath),
57
+ isTracked: false,
58
+ permissionsOk: envFileOk,
59
+ },
60
+ missingRequiredCount: status.missingRequired.length,
61
+ missingRequired: status.missingRequired,
62
+ };
63
+ if (!json) {
64
+ console.log(`Project root: ${root}`);
65
+ console.log(`Host: ${host.name} (Tier ${host.tier})`);
66
+ console.log(` ${host.reason}`);
67
+ console.log(` ${host.recommendation}`);
68
+ console.log(`Gitignore covers .env: ${gitignoreCovers ? 'yes' : 'no'}`);
69
+ console.log(`Missing required keys: ${status.missingRequired.length}`);
70
+ if (status.missingRequired.length > 0) {
71
+ for (const key of status.missingRequired) {
72
+ console.log(` - ${key}`);
73
+ }
74
+ }
75
+ }
76
+ else {
77
+ emit(json, '', output);
78
+ }
79
+ // Exit with UNSATISFIED if required keys are missing
80
+ if (status.missingRequired.length > 0) {
81
+ finish(EXIT.UNSATISFIED);
82
+ return;
83
+ }
84
+ }
85
+ catch (error) {
86
+ fail(json, error);
87
+ }
88
+ }
89
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1,2 @@
1
+ export declare function ensure(root: string, json: boolean, check?: boolean): Promise<void>;
2
+ //# sourceMappingURL=ensure.d.ts.map
@@ -0,0 +1,126 @@
1
+ import { emit, fail } from '../output.js';
2
+ import { EXIT } from '../exit-codes.js';
3
+ import { loadManifest, projectPaths } from '@envseal/core';
4
+ import { SepError } from '@envseal/protocol';
5
+ import { createBroker, outcomeForKey } from '../cli-utils.js';
6
+ import { finish } from '../exit.js';
7
+ export async function ensure(root, json, check = false) {
8
+ try {
9
+ // A project without env.schema.jsonc declares NOTHING, but describe()
10
+ // reports that exactly like an empty manifest: zero missing keys, so this
11
+ // used to print "✓ All required keys are satisfied" and exit 0 — vacuous
12
+ // success from the command whose job is telling the truth, while doctor on
13
+ // the same project reported the missing declarations.
14
+ //
15
+ // Exit USAGE (2), not UNSATISFIED (1): 1 means "required keys are still
16
+ // missing after the operation", and there are no keys here to satisfy.
17
+ // This is the same class as SEP_NOT_DECLARED, which already maps to 2.
18
+ // (`status` keeps exiting 0 on an init-less project because it is a
19
+ // read-only report with genuinely nothing to show; `ensure` claims work
20
+ // done, so it may not.) The message below differs from the missing-keys
21
+ // failure ("✗ Only N/M keys set" / satisfied:false), so scripts can tell
22
+ // the two apart by text as well as code.
23
+ const manifest = loadManifest(projectPaths(root));
24
+ if (manifest === null) {
25
+ throw new SepError({
26
+ code: 'SEP_NOT_DECLARED',
27
+ userMessage: 'No env.schema.jsonc in this project (or parents). Run `envseal init` to create one.',
28
+ });
29
+ }
30
+ const broker = await createBroker(root);
31
+ const status = await broker.describe();
32
+ // Use missingRequired array from ManifestStatus
33
+ const missingRequired = status.missingRequired;
34
+ // --check is the headless gate: it reports and exits, never requests.
35
+ // Skipping broker.request() is what makes it prompt-free — not just under
36
+ // CI (where a request would exit 4) but in an interactive terminal too,
37
+ // where a request would open a dialog the caller of a *check* never asked
38
+ // for. Total counts required entries only: an optional key being absent
39
+ // is not a failure.
40
+ if (check) {
41
+ const total = manifest.entries.filter((e) => e.required).length;
42
+ const missing = missingRequired.length;
43
+ if (!json) {
44
+ if (missing === 0) {
45
+ console.log('✓ All required keys are satisfied');
46
+ }
47
+ else {
48
+ console.log(`✗ ${missing} of ${total} required key(s) missing:`);
49
+ for (const key of missingRequired) {
50
+ console.log(` ${key}`);
51
+ }
52
+ }
53
+ }
54
+ else {
55
+ emit(json, '', {
56
+ satisfied: missing === 0,
57
+ keysSet: total - missing,
58
+ total,
59
+ missing: missingRequired,
60
+ });
61
+ }
62
+ if (missing > 0) {
63
+ finish(EXIT.UNSATISFIED);
64
+ }
65
+ return;
66
+ }
67
+ if (missingRequired.length === 0) {
68
+ if (!json) {
69
+ console.log('✓ All required keys are satisfied');
70
+ }
71
+ else {
72
+ // `total` is part of the documented shape and was omitted here, so a
73
+ // caller reading `total` got undefined on exactly the path it is most
74
+ // likely to take. Nothing was missing, so nothing was requested: 0.
75
+ emit(json, '', {
76
+ satisfied: true,
77
+ keysSet: 0,
78
+ total: 0,
79
+ });
80
+ }
81
+ return;
82
+ }
83
+ // Request all missing keys at once
84
+ const ticket = await broker.request({
85
+ keys: missingRequired,
86
+ reason: 'Ensure all required keys are present',
87
+ });
88
+ // Await the results (timeoutMs defaults to 90000)
89
+ const results = await broker.await({
90
+ ticket: ticket.ticket,
91
+ timeoutMs: 90000,
92
+ });
93
+ const outcomes = missingRequired.map((key) => outcomeForKey(results, key));
94
+ const keysSet = outcomes.filter((o) => o === 'stored').length;
95
+ const allSet = keysSet === missingRequired.length;
96
+ if (!json) {
97
+ if (allSet) {
98
+ console.log(`✓ Set ${keysSet} key(s)`);
99
+ }
100
+ else {
101
+ console.log(`✗ Only ${keysSet}/${missingRequired.length} keys set`);
102
+ }
103
+ }
104
+ else {
105
+ emit(json, '', {
106
+ satisfied: allSet,
107
+ keysSet,
108
+ total: missingRequired.length,
109
+ });
110
+ }
111
+ if (!allSet) {
112
+ // Exit 1 says "still missing"; exit 3 says "a human declined or never
113
+ // answered". Both are retriable, but only 3 tells a caller that retrying
114
+ // unattended is pointless — so a stop of that kind wins over a plain
115
+ // shortfall. Everything else (skipped, invalid_format, verify_failed)
116
+ // stays 1.
117
+ const stopped = outcomes.some((o) => o === 'cancelled' || o === 'timeout');
118
+ finish(stopped ? EXIT.CANCELLED : EXIT.UNSATISFIED);
119
+ return;
120
+ }
121
+ }
122
+ catch (error) {
123
+ fail(json, error);
124
+ }
125
+ }
126
+ //# sourceMappingURL=ensure.js.map
@@ -0,0 +1,2 @@
1
+ export declare function init(root: string, json: boolean, hostOverride?: string): Promise<void>;
2
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1,101 @@
1
+ import { projectPaths, loadManifest, declareEntries } from '@envseal/core';
2
+ import { emit, fail } from '../output.js';
3
+ import { detectHost } from '../host.js';
4
+ import { scanForEnvKeys, entryForKey } from '../scan.js';
5
+ import { EXIT } from '../exit-codes.js';
6
+ import { finish } from '../exit.js';
7
+ // The ids detectHost can ever return. --host used to accept any string
8
+ // silently, recording a host detection would never report and printing a tier
9
+ // computed for a fiction.
10
+ const KNOWN_HOST_IDS = [
11
+ 'claude-code',
12
+ 'cursor',
13
+ 'continue',
14
+ 'aider',
15
+ 'windsurf',
16
+ 'cline',
17
+ 'zed',
18
+ 'codex',
19
+ 'jetbrains',
20
+ 'goose',
21
+ 'copilot',
22
+ 'generic',
23
+ 'unknown',
24
+ ];
25
+ export async function init(root, json, hostOverride) {
26
+ try {
27
+ if (hostOverride !== undefined && !KNOWN_HOST_IDS.includes(hostOverride)) {
28
+ console.error(`Error: unknown --host '${hostOverride}'. Valid values: ${KNOWN_HOST_IDS.join(', ')}.`);
29
+ finish(EXIT.USAGE);
30
+ return;
31
+ }
32
+ const paths = projectPaths(root);
33
+ const discovered = scanForEnvKeys(root);
34
+ const entries = discovered.map(entryForKey);
35
+ // declareEntries creates the manifest when absent and edits it surgically
36
+ // when present, so re-running init on an existing project is safe and
37
+ // preserves any descriptions the user has written by hand. It runs even
38
+ // when the scan finds nothing: the "created an empty manifest" message
39
+ // below must be true, not aspirational.
40
+ const result = declareEntries(paths, entries);
41
+ const manifest = loadManifest(paths);
42
+ const host = hostOverride
43
+ ? { id: hostOverride, name: hostOverride, tier: 'C', reason: 'specified with --host', recommendation: '' }
44
+ : detectHost(root);
45
+ const output = {
46
+ manifestPath: paths.manifest,
47
+ host: host.id,
48
+ protectionTier: host.tier,
49
+ scanned: discovered.length,
50
+ added: result.added,
51
+ updated: result.updated,
52
+ unchanged: result.unchanged,
53
+ secretKeys: discovered.filter((d) => d.secret).map((d) => d.key),
54
+ configKeys: discovered.filter((d) => !d.secret).map((d) => d.key),
55
+ entries: manifest?.entries.length ?? 0,
56
+ };
57
+ if (json) {
58
+ emit(json, '', output);
59
+ return;
60
+ }
61
+ if (discovered.length === 0) {
62
+ console.log('No environment variables found in this project.');
63
+ console.log(`Created an empty manifest at ${paths.manifest}.`);
64
+ console.log('Add entries with `envseal set <KEY>` or let your agent call env_declare.');
65
+ }
66
+ else {
67
+ console.log(`✓ Manifest written to ${paths.manifest}`);
68
+ console.log(` Found ${discovered.length} variable(s): ${result.added.length} added, ${result.unchanged.length} unchanged`);
69
+ const secrets = discovered.filter((d) => d.secret);
70
+ if (secrets.length > 0) {
71
+ console.log(` Secrets: ${secrets.map((s) => s.key).join(', ')}`);
72
+ }
73
+ const config = discovered.filter((d) => !d.secret);
74
+ if (config.length > 0) {
75
+ console.log(` Config (not prompted): ${config.map((s) => s.key).join(', ')}`);
76
+ }
77
+ }
78
+ console.log(` Host: ${host.name} (protection tier ${host.tier})`);
79
+ if (host.recommendation)
80
+ console.log(` ${host.recommendation}`);
81
+ if (hostOverride) {
82
+ // The override line above is what was ASKED for, not what is here. An
83
+ // auto-detected init on the same project can print a different tier, and
84
+ // doctor is the one that reports evidence.
85
+ console.log(' Override recorded; envseal doctor reports what is actually detected.');
86
+ }
87
+ if (host.id === 'claude-code') {
88
+ // Without this the first run ends at a manifest and no connection: init
89
+ // writes env.schema.jsonc but nothing tells the user the agent still has
90
+ // to be pointed at the broker.
91
+ console.log('');
92
+ console.log('Connect your agent: create .mcp.json in the project root containing');
93
+ console.log(' {"mcpServers":{"envseal-mcp":{"command":"envseal-mcp","args":[]}}}');
94
+ console.log('then restart Claude Code — or install plugins/claude-code for Tier A hooks.');
95
+ }
96
+ }
97
+ catch (error) {
98
+ fail(json, error);
99
+ }
100
+ }
101
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1,2 @@
1
+ export declare function mcp(root: string): Promise<void>;
2
+ //# sourceMappingURL=mcp.d.ts.map
@@ -0,0 +1,21 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { EXIT } from '../exit-codes.js';
3
+ import { finish } from '../exit.js';
4
+ export async function mcp(root) {
5
+ // Delegate to the mcp-server binary
6
+ const result = spawnSync('envseal-mcp', ['--project', root], {
7
+ stdio: 'inherit',
8
+ });
9
+ // A spawn failure leaves `status` null AND populates `error`. The old
10
+ // `result.status ?? 0` turned "the server binary does not exist / cannot be
11
+ // executed" into exit 0 — a host wiring this command saw silent success while
12
+ // nothing was serving. Report it honestly instead.
13
+ if (result.error || result.status === null) {
14
+ const reason = result.error?.message ?? 'the envseal-mcp binary could not be executed';
15
+ console.error(`Error: failed to start the envseal MCP server (is @envseal/mcp-server installed?): ${reason}`);
16
+ finish(EXIT.SINK_FAILURE);
17
+ return;
18
+ }
19
+ finish(result.status);
20
+ }
21
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1,2 @@
1
+ export declare function revoke(root: string, key: string, json: boolean): Promise<void>;
2
+ //# sourceMappingURL=revoke.d.ts.map
@@ -0,0 +1,48 @@
1
+ import { emit, fail } from '../output.js';
2
+ import { EXIT } from '../exit-codes.js';
3
+ import { createBroker } from '../cli-utils.js';
4
+ import { finish } from '../exit.js';
5
+ export async function revoke(root, key, json) {
6
+ try {
7
+ const broker = await createBroker(root);
8
+ const results = await broker.revoke({
9
+ keys: [key],
10
+ });
11
+ // `broker.revoke` skips keys that are not in the manifest, so an unknown
12
+ // key comes back as an empty array rather than `removed: false`.
13
+ const result = results[0];
14
+ const removed = result?.removed ?? false;
15
+ const rotateUrl = result?.rotateUrl ?? null;
16
+ if (!json) {
17
+ if (removed) {
18
+ console.log(`✓ ${key} revoked`);
19
+ if (rotateUrl) {
20
+ console.log(` Rotate the credential at: ${rotateUrl}`);
21
+ }
22
+ }
23
+ else if (result === undefined) {
24
+ console.log(`✗ ${key} is not declared in this project's manifest`);
25
+ }
26
+ else {
27
+ console.log(`✗ Failed to revoke ${key}: nothing was removed from the sink`);
28
+ }
29
+ }
30
+ else {
31
+ emit(json, '', {
32
+ key,
33
+ removed,
34
+ rotateUrl,
35
+ });
36
+ }
37
+ // Exit 0 meant "revoked" even when nothing was removed, so a caller could
38
+ // not distinguish a burned key from a no-op.
39
+ if (!removed) {
40
+ finish(EXIT.UNSATISFIED);
41
+ return;
42
+ }
43
+ }
44
+ catch (error) {
45
+ fail(json, error);
46
+ }
47
+ }
48
+ //# sourceMappingURL=revoke.js.map
@@ -0,0 +1,2 @@
1
+ export declare function run(root: string, command: string[], json: boolean, assumeYes?: boolean): Promise<void>;
2
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1,82 @@
1
+ import { createInterface } from 'node:readline';
2
+ import { SepError } from '@envseal/protocol';
3
+ import { emit, fail } from '../output.js';
4
+ import { createBroker } from '../cli-utils.js';
5
+ import { finish } from '../exit.js';
6
+ /**
7
+ * Ask the user to approve running a command with secrets injected.
8
+ *
9
+ * Without this the broker's confirmation callback is absent and `use` always
10
+ * throws SEP_CONFIRMATION_DENIED, making the command impossible to use. The
11
+ * prompt reads from the controlling terminal, so it still works when stdout is
12
+ * being piped.
13
+ *
14
+ * Fails closed: no TTY and no --yes means no approval. That is the right
15
+ * default, because the alternative is a CI job silently handing credentials to
16
+ * whatever it was told to run.
17
+ */
18
+ async function confirmInteractive(info) {
19
+ if (process.env.ENVSEAL_ASSUME_YES === '1')
20
+ return true;
21
+ if (!process.stdin.isTTY) {
22
+ // Distinct from a refusal. Reporting "the user denied the confirmation" when
23
+ // no human was ever asked is actively misleading, and it is the shell-only
24
+ // agents of Tier 4 that hit this path — the ones this binding exists for.
25
+ throw new SepError({
26
+ code: 'SEP_NO_INTERACTIVE_SURFACE',
27
+ userMessage: 'envseal run needs confirmation before injecting secrets, but there is no terminal to ask on. ' +
28
+ 'Re-run in an interactive shell, or pass --yes (or set ENVSEAL_ASSUME_YES=1) to approve non-interactively.',
29
+ });
30
+ }
31
+ const lines = [
32
+ '',
33
+ 'envseal is about to run a command with secrets in its environment:',
34
+ ` command: ${info.command.join(' ')}`,
35
+ ` keys: ${info.keys.join(', ')}`,
36
+ ];
37
+ if (info.networkEgress) {
38
+ lines.push(' WARNING: this command can make network requests, so it could send', ' these values somewhere. Only continue if you trust it.');
39
+ }
40
+ lines.push('');
41
+ process.stderr.write(lines.join('\n'));
42
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
43
+ try {
44
+ const answer = await new Promise((resolve) => {
45
+ rl.question('Continue? [y/N] ', resolve);
46
+ });
47
+ return /^y(es)?$/i.test(answer.trim());
48
+ }
49
+ finally {
50
+ rl.close();
51
+ }
52
+ }
53
+ export async function run(root, command, json, assumeYes = false) {
54
+ try {
55
+ const broker = await createBroker(root, {
56
+ onConfirm: assumeYes ? async () => true : confirmInteractive,
57
+ });
58
+ const status = await broker.describe();
59
+ const presentKeys = status.entries.filter((e) => e.present).map((e) => e.key);
60
+ const result = await broker.use({ keys: presentKeys, command });
61
+ if (!json) {
62
+ if (result.stdout)
63
+ process.stdout.write(result.stdout);
64
+ if (result.stderr)
65
+ process.stderr.write(result.stderr);
66
+ }
67
+ else {
68
+ emit(json, '', {
69
+ exitCode: result.exitCode,
70
+ stdout: result.stdout,
71
+ stderr: result.stderr,
72
+ redactedCount: result.redactedCount,
73
+ });
74
+ }
75
+ finish(result.exitCode ?? 0);
76
+ return;
77
+ }
78
+ catch (error) {
79
+ fail(json, error);
80
+ }
81
+ }
82
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1,2 @@
1
+ export declare function set(root: string, key: string, json: boolean): Promise<void>;
2
+ //# sourceMappingURL=set.d.ts.map
@@ -0,0 +1,122 @@
1
+ import { emit, fail } from '../output.js';
2
+ import { EXIT, exitCodeForOutcome } from '../exit-codes.js';
3
+ import { createBroker, outcomeForKey } from '../cli-utils.js';
4
+ import { finish } from '../exit.js';
5
+ import { loadManifest, projectPaths, saveManifest } from '@envseal/core';
6
+ /**
7
+ * Undo a declaration THIS invocation added, after nothing was stored under it.
8
+ *
9
+ * `set` declares before it requests (the declare-time schema guard has to run
10
+ * first), so every attempt that did not end `stored` — a CI run with no prompt
11
+ * surface, a cancelled or timed-out prompt, a typo the user abandons — used to
12
+ * leave the key behind as required+secret in env.schema.jsonc. Nothing
13
+ * mentioned the mutation: status then shows a phantom key forever, revoke
14
+ * touches only sinks, and init does not prune. An entry that already existed
15
+ * before this run is left alone — it was not ours to remove.
16
+ *
17
+ * stderr only: stdout is the machine-readable channel in --json mode.
18
+ */
19
+ function rollbackDeclaredEntry(root, key) {
20
+ const paths = projectPaths(root);
21
+ const manifest = loadManifest(paths);
22
+ if (manifest === null)
23
+ return;
24
+ const remaining = manifest.entries.filter((e) => e.key !== key);
25
+ if (remaining.length === manifest.entries.length)
26
+ return;
27
+ manifest.entries = remaining;
28
+ // saveManifest edits only the changed field through jsonc, so the header
29
+ // comments survive.
30
+ saveManifest(paths, manifest);
31
+ console.error(`declared ${key} but nothing was stored; declaration removed`);
32
+ }
33
+ export async function set(root, key, json) {
34
+ // Whether the declare below added the entry, as opposed to finding it already
35
+ // there. Lives outside the try so the catch can roll it back when `request`
36
+ // throws (no interactive surface in CI is the common case).
37
+ let declaredHere = false;
38
+ try {
39
+ const broker = await createBroker(root);
40
+ // Declare the key only if it is not already in the manifest.
41
+ //
42
+ // `declareEntries` replaces an entry wholesale rather than merging, so
43
+ // declaring unconditionally meant `envseal set OPENAI_API_KEY` on an
44
+ // initialised project overwrote the existing entry with this bare stub —
45
+ // silently dropping its format pattern, its provider links and its verify
46
+ // probe. Caught by the invalid_format case in contract-e2e.test.ts, which
47
+ // stored a value the declared pattern rejects.
48
+ const status = await broker.describe();
49
+ if (!status.entries.some((e) => e.key === key)) {
50
+ try {
51
+ const entry = {
52
+ key,
53
+ description: `Configuration for ${key}`,
54
+ required: true,
55
+ secret: true,
56
+ sink: 'dotenv',
57
+ };
58
+ const declareResult = await broker.declare({
59
+ entries: [entry],
60
+ });
61
+ declaredHere = declareResult.added.includes(key);
62
+ }
63
+ catch {
64
+ // A key name the manifest schema rejects. Fall through: `request` then
65
+ // raises SEP_NOT_DECLARED, which maps to exit 2 (usage) — the right
66
+ // answer for a bad argument. Nothing was written, so no rollback.
67
+ }
68
+ }
69
+ // Request the key
70
+ const ticket = await broker.request({
71
+ keys: [key],
72
+ reason: `Set ${key}`,
73
+ });
74
+ // Await the result (timeoutMs defaults to 90000)
75
+ const result = await broker.await({
76
+ ticket: ticket.ticket,
77
+ timeoutMs: 90000,
78
+ });
79
+ const outcome = outcomeForKey(result, key);
80
+ if (outcome === null) {
81
+ // The ticket resolved without saying anything about the key we asked
82
+ // for. That is an internal inconsistency, not a user decision, so report
83
+ // it as such rather than as a silent success. Nothing was stored, so the
84
+ // declaration we added has nothing to show for itself either.
85
+ if (declaredHere)
86
+ rollbackDeclaredEntry(root, key);
87
+ fail(json, `The prompt for ${key} finished without reporting an outcome.`);
88
+ return;
89
+ }
90
+ if (!json) {
91
+ if (outcome === 'stored') {
92
+ console.log(`✓ ${key} set successfully`);
93
+ }
94
+ else {
95
+ console.log(`✗ Failed to set ${key}: ${outcome}`);
96
+ }
97
+ }
98
+ else {
99
+ emit(json, '', {
100
+ key,
101
+ outcome,
102
+ });
103
+ }
104
+ if (outcome !== 'stored' && declaredHere) {
105
+ rollbackDeclaredEntry(root, key);
106
+ }
107
+ const code = exitCodeForOutcome(outcome);
108
+ if (code !== EXIT.OK) {
109
+ finish(code);
110
+ return;
111
+ }
112
+ }
113
+ catch (error) {
114
+ // `request` throws before any prompt happens (SEP_NO_INTERACTIVE_SURFACE
115
+ // under CI=1 is the common route here), so a declaration we just added
116
+ // would otherwise outlive a run that never even asked for the value.
117
+ if (declaredHere)
118
+ rollbackDeclaredEntry(root, key);
119
+ fail(json, error);
120
+ }
121
+ }
122
+ //# sourceMappingURL=set.js.map
@@ -0,0 +1,2 @@
1
+ export declare function status(root: string, keys: string[], json: boolean): Promise<void>;
2
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1,49 @@
1
+ import { emit, fail } from '../output.js';
2
+ import { EXIT } from '../exit-codes.js';
3
+ import { createBroker } from '../cli-utils.js';
4
+ import { finish } from '../exit.js';
5
+ export async function status(root, keys, json) {
6
+ try {
7
+ const broker = await createBroker(root);
8
+ const status = await broker.describe();
9
+ let entriesToShow = status.entries;
10
+ if (keys.length > 0) {
11
+ entriesToShow = entriesToShow.filter((e) => keys.includes(e.key));
12
+ }
13
+ const hasRequired = status.missingRequired.length > 0;
14
+ if (!json && keys.length === 0) {
15
+ if (entriesToShow.length === 0) {
16
+ console.log('No environment variables declared.');
17
+ }
18
+ else {
19
+ for (const entry of entriesToShow) {
20
+ const status_str = entry.present ? '✓' : '✗';
21
+ console.log(`${status_str} ${entry.key}`);
22
+ }
23
+ }
24
+ }
25
+ else {
26
+ emit(json, '', {
27
+ entries: entriesToShow.map((e) => ({
28
+ key: e.key,
29
+ present: e.present,
30
+ sink: e.sink,
31
+ formatValid: e.formatValid,
32
+ lengthBucket: e.lengthBucket,
33
+ fingerprint: e.fingerprint,
34
+ lastVerified: e.lastVerified,
35
+ verifyResult: e.verifyResult,
36
+ })),
37
+ });
38
+ }
39
+ // Exit with UNSATISFIED if required keys are missing
40
+ if (hasRequired) {
41
+ finish(EXIT.UNSATISFIED);
42
+ return;
43
+ }
44
+ }
45
+ catch (error) {
46
+ fail(json, error);
47
+ }
48
+ }
49
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1,2 @@
1
+ export declare function verify(root: string, keys: string[], json: boolean): Promise<void>;
2
+ //# sourceMappingURL=verify.d.ts.map