@vibesdotdev/secrets 0.0.1
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/README.md +59 -0
- package/SPEC.md +47 -0
- package/dist/cli/check/schemas/check-result.d.ts +9 -0
- package/dist/cli/check/schemas/check-result.d.ts.map +1 -0
- package/dist/cli/check/schemas/check-result.js +2 -0
- package/dist/cli/check/schemas/check-result.js.map +1 -0
- package/dist/cli/check/secrets.check.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/check/secrets.check.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/check/secrets.check.cli-command.descriptor.js +19 -0
- package/dist/cli/check/secrets.check.cli-command.descriptor.js.map +1 -0
- package/dist/cli/check/secrets.check.cli-command.impl.d.ts +5 -0
- package/dist/cli/check/secrets.check.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/check/secrets.check.cli-command.impl.js +135 -0
- package/dist/cli/check/secrets.check.cli-command.impl.js.map +1 -0
- package/dist/cli/export/secrets.export.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/export/secrets.export.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/export/secrets.export.cli-command.descriptor.js +20 -0
- package/dist/cli/export/secrets.export.cli-command.descriptor.js.map +1 -0
- package/dist/cli/export/secrets.export.cli-command.impl.d.ts +5 -0
- package/dist/cli/export/secrets.export.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/export/secrets.export.cli-command.impl.js +104 -0
- package/dist/cli/export/secrets.export.cli-command.impl.js.map +1 -0
- package/dist/cli/hooks/pre-commit-secrets.d.ts +2 -0
- package/dist/cli/hooks/pre-commit-secrets.d.ts.map +1 -0
- package/dist/cli/hooks/pre-commit-secrets.js +68 -0
- package/dist/cli/hooks/pre-commit-secrets.js.map +1 -0
- package/dist/cli/import/secrets.import.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/import/secrets.import.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/import/secrets.import.cli-command.descriptor.js +19 -0
- package/dist/cli/import/secrets.import.cli-command.descriptor.js.map +1 -0
- package/dist/cli/import/secrets.import.cli-command.impl.d.ts +5 -0
- package/dist/cli/import/secrets.import.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/import/secrets.import.cli-command.impl.js +155 -0
- package/dist/cli/import/secrets.import.cli-command.impl.js.map +1 -0
- package/dist/cli/list/secrets.list.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/list/secrets.list.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/list/secrets.list.cli-command.descriptor.js +18 -0
- package/dist/cli/list/secrets.list.cli-command.descriptor.js.map +1 -0
- package/dist/cli/list/secrets.list.cli-command.impl.d.ts +5 -0
- package/dist/cli/list/secrets.list.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/list/secrets.list.cli-command.impl.js +61 -0
- package/dist/cli/list/secrets.list.cli-command.impl.js.map +1 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.js +16 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.js.map +1 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.d.ts +5 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.js +10 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.js.map +1 -0
- package/dist/cli/pull/secrets.pull.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/pull/secrets.pull.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/pull/secrets.pull.cli-command.descriptor.js +20 -0
- package/dist/cli/pull/secrets.pull.cli-command.descriptor.js.map +1 -0
- package/dist/cli/pull/secrets.pull.cli-command.impl.d.ts +5 -0
- package/dist/cli/pull/secrets.pull.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/pull/secrets.pull.cli-command.impl.js +76 -0
- package/dist/cli/pull/secrets.pull.cli-command.impl.js.map +1 -0
- package/dist/cli/push/secrets.push.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/push/secrets.push.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/push/secrets.push.cli-command.descriptor.js +22 -0
- package/dist/cli/push/secrets.push.cli-command.descriptor.js.map +1 -0
- package/dist/cli/push/secrets.push.cli-command.impl.d.ts +5 -0
- package/dist/cli/push/secrets.push.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/push/secrets.push.cli-command.impl.js +109 -0
- package/dist/cli/push/secrets.push.cli-command.impl.js.map +1 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.descriptor.js +19 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.descriptor.js.map +1 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.impl.d.ts +5 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.impl.js +85 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.impl.js.map +1 -0
- package/dist/cli/secrets.cli-group.descriptor.d.ts +4 -0
- package/dist/cli/secrets.cli-group.descriptor.d.ts.map +1 -0
- package/dist/cli/secrets.cli-group.descriptor.js +11 -0
- package/dist/cli/secrets.cli-group.descriptor.js.map +1 -0
- package/dist/cli/set/secrets.set.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/set/secrets.set.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/set/secrets.set.cli-command.descriptor.js +21 -0
- package/dist/cli/set/secrets.set.cli-command.descriptor.js.map +1 -0
- package/dist/cli/set/secrets.set.cli-command.impl.d.ts +5 -0
- package/dist/cli/set/secrets.set.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/set/secrets.set.cli-command.impl.js +59 -0
- package/dist/cli/set/secrets.set.cli-command.impl.js.map +1 -0
- package/dist/cli/shared/resolve-environment.d.ts +14 -0
- package/dist/cli/shared/resolve-environment.d.ts.map +1 -0
- package/dist/cli/shared/resolve-environment.js +45 -0
- package/dist/cli/shared/resolve-environment.js.map +1 -0
- package/dist/cli/unset/secrets.unset.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/unset/secrets.unset.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/unset/secrets.unset.cli-command.descriptor.js +20 -0
- package/dist/cli/unset/secrets.unset.cli-command.descriptor.js.map +1 -0
- package/dist/cli/unset/secrets.unset.cli-command.impl.d.ts +5 -0
- package/dist/cli/unset/secrets.unset.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/unset/secrets.unset.cli-command.impl.js +31 -0
- package/dist/cli/unset/secrets.unset.cli-command.impl.js.map +1 -0
- package/dist/docs/backends.docs.descriptor.d.ts +4 -0
- package/dist/docs/backends.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/backends.docs.descriptor.js +149 -0
- package/dist/docs/backends.docs.descriptor.js.map +1 -0
- package/dist/docs/encryption.docs.descriptor.d.ts +4 -0
- package/dist/docs/encryption.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/encryption.docs.descriptor.js +163 -0
- package/dist/docs/encryption.docs.descriptor.js.map +1 -0
- package/dist/docs/env-file.docs.descriptor.d.ts +4 -0
- package/dist/docs/env-file.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/env-file.docs.descriptor.js +207 -0
- package/dist/docs/env-file.docs.descriptor.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/kinds/index.d.ts +4 -0
- package/dist/kinds/index.d.ts.map +1 -0
- package/dist/kinds/index.js +3 -0
- package/dist/kinds/index.js.map +1 -0
- package/dist/kinds/schemas/store.schema.d.ts +49 -0
- package/dist/kinds/schemas/store.schema.d.ts.map +1 -0
- package/dist/kinds/schemas/store.schema.js +34 -0
- package/dist/kinds/schemas/store.schema.js.map +1 -0
- package/dist/kinds/schemas/store.types.d.ts +28 -0
- package/dist/kinds/schemas/store.types.d.ts.map +1 -0
- package/dist/kinds/schemas/store.types.js +2 -0
- package/dist/kinds/schemas/store.types.js.map +1 -0
- package/dist/kinds/store.interface.d.ts +2 -0
- package/dist/kinds/store.interface.d.ts.map +1 -0
- package/dist/kinds/store.interface.js +2 -0
- package/dist/kinds/store.interface.js.map +1 -0
- package/dist/kinds/store.kind.d.ts +10 -0
- package/dist/kinds/store.kind.d.ts.map +1 -0
- package/dist/kinds/store.kind.js +36 -0
- package/dist/kinds/store.kind.js.map +1 -0
- package/dist/kinds/store.schema.d.ts +2 -0
- package/dist/kinds/store.schema.d.ts.map +1 -0
- package/dist/kinds/store.schema.js +2 -0
- package/dist/kinds/store.schema.js.map +1 -0
- package/dist/manifest/canonical.d.ts +30 -0
- package/dist/manifest/canonical.d.ts.map +1 -0
- package/dist/manifest/canonical.js +313 -0
- package/dist/manifest/canonical.js.map +1 -0
- package/dist/manifest/import-manifest.schema.d.ts +77 -0
- package/dist/manifest/import-manifest.schema.d.ts.map +1 -0
- package/dist/manifest/import-manifest.schema.js +55 -0
- package/dist/manifest/import-manifest.schema.js.map +1 -0
- package/dist/manifest/index.d.ts +3 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +3 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/requirements/index.d.ts +2 -0
- package/dist/requirements/index.d.ts.map +1 -0
- package/dist/requirements/index.js +2 -0
- package/dist/requirements/index.js.map +1 -0
- package/dist/requirements/resolver.d.ts +52 -0
- package/dist/requirements/resolver.d.ts.map +1 -0
- package/dist/requirements/resolver.js +196 -0
- package/dist/requirements/resolver.js.map +1 -0
- package/dist/requirements/schemas/requirements.d.ts +27 -0
- package/dist/requirements/schemas/requirements.d.ts.map +1 -0
- package/dist/requirements/schemas/requirements.js +2 -0
- package/dist/requirements/schemas/requirements.js.map +1 -0
- package/dist/secrets.plugin.d.ts +8 -0
- package/dist/secrets.plugin.d.ts.map +1 -0
- package/dist/secrets.plugin.js +59 -0
- package/dist/secrets.plugin.js.map +1 -0
- package/package.json +108 -0
- package/src/cli/check/schemas/check-result.ts +8 -0
- package/src/cli/check/secrets.check.cli-command.descriptor.ts +21 -0
- package/src/cli/check/secrets.check.cli-command.impl.ts +163 -0
- package/src/cli/export/secrets.export.cli-command.descriptor.ts +22 -0
- package/src/cli/export/secrets.export.cli-command.impl.ts +139 -0
- package/src/cli/hooks/pre-commit-secrets.ts +73 -0
- package/src/cli/import/secrets.import.cli-command.descriptor.ts +21 -0
- package/src/cli/import/secrets.import.cli-command.impl.ts +178 -0
- package/src/cli/list/secrets.list.cli-command.descriptor.ts +21 -0
- package/src/cli/list/secrets.list.cli-command.impl.ts +79 -0
- package/src/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.ts +18 -0
- package/src/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.ts +11 -0
- package/src/cli/pull/secrets.pull.cli-command.descriptor.ts +22 -0
- package/src/cli/pull/secrets.pull.cli-command.impl.ts +103 -0
- package/src/cli/push/secrets.push.cli-command.descriptor.ts +24 -0
- package/src/cli/push/secrets.push.cli-command.impl.ts +149 -0
- package/src/cli/reveal/secrets.reveal.cli-command.descriptor.ts +21 -0
- package/src/cli/reveal/secrets.reveal.cli-command.impl.ts +108 -0
- package/src/cli/secrets.cli-group.descriptor.ts +13 -0
- package/src/cli/set/secrets.set.cli-command.descriptor.ts +23 -0
- package/src/cli/set/secrets.set.cli-command.impl.ts +77 -0
- package/src/cli/shared/resolve-environment.ts +57 -0
- package/src/cli/unset/secrets.unset.cli-command.descriptor.ts +22 -0
- package/src/cli/unset/secrets.unset.cli-command.impl.ts +41 -0
- package/src/docs/backends.docs.descriptor.ts +151 -0
- package/src/docs/encryption.docs.descriptor.ts +165 -0
- package/src/docs/env-file.docs.descriptor.ts +209 -0
- package/src/index.ts +35 -0
- package/src/kinds/index.ts +12 -0
- package/src/kinds/schemas/store.schema.ts +47 -0
- package/src/kinds/schemas/store.types.ts +35 -0
- package/src/kinds/store.interface.ts +1 -0
- package/src/kinds/store.kind.ts +52 -0
- package/src/kinds/store.schema.ts +8 -0
- package/src/manifest/canonical.ts +324 -0
- package/src/manifest/import-manifest.schema.ts +63 -0
- package/src/manifest/index.ts +12 -0
- package/src/requirements/index.ts +6 -0
- package/src/requirements/resolver.ts +216 -0
- package/src/requirements/schemas/requirements.ts +29 -0
- package/src/secrets.plugin.ts +65 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { getVibesRuntime } from '@vibesdotdev/runtime';
|
|
2
|
+
import type { UIContext } from '@vibesdotdev/cli/providers';
|
|
3
|
+
import type { SecretEntry, SecretsStoreImplementation } from '../../kinds/store.interface';
|
|
4
|
+
import type { SecretsStoreDescriptor } from '../../kinds/store.schema';
|
|
5
|
+
import { resolveSecretRequirements } from '../../requirements/resolver';
|
|
6
|
+
import { resolveSecretsEnvironment } from '../shared/resolve-environment';
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
async execute(_args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
|
|
10
|
+
const runtime = getVibesRuntime();
|
|
11
|
+
const ui = (await runtime.context('cli/ui')) as UIContext;
|
|
12
|
+
const options = opts as {
|
|
13
|
+
environment?: string;
|
|
14
|
+
backend?: string;
|
|
15
|
+
json?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const { name: envName, tier: envTier } = await resolveSecretsEnvironment(
|
|
19
|
+
ui,
|
|
20
|
+
options.environment
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const descriptors = runtime.assets('secrets/store').descriptors() as SecretsStoreDescriptor[];
|
|
24
|
+
const matching = descriptors.filter((d) => {
|
|
25
|
+
if (options.backend && d.id !== options.backend) return false;
|
|
26
|
+
return d.tiers.includes(envTier);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const aggregated = new Map<string, SecretEntry>();
|
|
30
|
+
const storesByPriority = [...matching].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
|
31
|
+
|
|
32
|
+
for (const desc of storesByPriority) {
|
|
33
|
+
const impl = (await runtime
|
|
34
|
+
.query('secrets/store')
|
|
35
|
+
.withId(desc.id)
|
|
36
|
+
.resolve()) as SecretsStoreImplementation;
|
|
37
|
+
const entries = await impl.list(envName);
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
aggregated.set(entry.key, entry);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const entries = [...aggregated.values()].sort((a, b) => a.key.localeCompare(b.key));
|
|
44
|
+
|
|
45
|
+
if (options.json) {
|
|
46
|
+
const secrets = entries.map((e) => ({ key: e.key, hasValue: e.hasValue, source: e.source }));
|
|
47
|
+
ui.render({ environment: envName, secrets }, { format: 'json' });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (entries.length === 0) {
|
|
52
|
+
ui.info(`No secrets found for environment: ${envName}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
ui.log(`\nSecrets for environment: ${envName}\n`);
|
|
57
|
+
ui.log(' KEY'.padEnd(45) + 'SOURCE'.padEnd(20) + 'STATUS');
|
|
58
|
+
ui.log(' ' + '─'.repeat(70));
|
|
59
|
+
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
const status = entry.hasValue ? 'set' : 'empty';
|
|
62
|
+
ui.log(` ${entry.key.padEnd(43)} ${entry.source.padEnd(18)} ${status}`);
|
|
63
|
+
}
|
|
64
|
+
ui.log('');
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const requirements = await resolveSecretRequirements(runtime);
|
|
68
|
+
const required = requirements.filter((r) => r.required);
|
|
69
|
+
const setKeys = new Set(entries.filter((e) => e.hasValue).map((e) => e.key));
|
|
70
|
+
const missing = required.filter((r) => !setKeys.has(r.key));
|
|
71
|
+
if (missing.length > 0) {
|
|
72
|
+
ui.log(` ${missing.length} required secret(s) missing. Run 'vibes secrets check' for details.`);
|
|
73
|
+
ui.log('');
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
// infra kinds may not be loaded in all contexts
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CLICommandAssetDescriptor } from '@vibesdotdev/cli/schemas/types';
|
|
2
|
+
|
|
3
|
+
const descriptor: CLICommandAssetDescriptor = {
|
|
4
|
+
kind: 'cli/command',
|
|
5
|
+
id: 'dev.secrets.pre-commit-check',
|
|
6
|
+
name: 'pre-commit-check',
|
|
7
|
+
description: 'Run secrets pattern detection on staged files',
|
|
8
|
+
group: 'secrets',
|
|
9
|
+
options: [
|
|
10
|
+
{ flags: '--workspace <path>', description: 'Workspace root (default: cwd)' }
|
|
11
|
+
],
|
|
12
|
+
surfaces: ['cli'],
|
|
13
|
+
hardware: ['consumer'],
|
|
14
|
+
enabled: true,
|
|
15
|
+
order: 1
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default descriptor;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { runSecretsPreCommitCheck } from '../hooks/pre-commit-secrets';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
async execute(_args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
|
|
5
|
+
const options = opts as { workspace?: string };
|
|
6
|
+
const workspacePath = options.workspace ?? process.cwd();
|
|
7
|
+
|
|
8
|
+
const exitCode = await runSecretsPreCommitCheck(workspacePath);
|
|
9
|
+
process.exit(exitCode);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CLICommandAssetDescriptor } from '@vibesdotdev/cli/schemas/types';
|
|
2
|
+
|
|
3
|
+
const descriptor: CLICommandAssetDescriptor = {
|
|
4
|
+
kind: 'cli/command',
|
|
5
|
+
id: 'secrets.pull',
|
|
6
|
+
name: 'pull',
|
|
7
|
+
description: 'Pull secrets from a remote backend to local store',
|
|
8
|
+
group: 'secrets',
|
|
9
|
+
options: [
|
|
10
|
+
{ flags: '--environment <name>', description: 'Target environment (default: current)' },
|
|
11
|
+
{ flags: '--from <backend>', description: 'Source remote backend (default: vault)' },
|
|
12
|
+
{ flags: '--to <backend>', description: 'Target local backend (default: encrypted-local)' },
|
|
13
|
+
{ flags: '--key <key>', description: 'Pull only one secret key (repeat or comma-separate for multiple keys)' },
|
|
14
|
+
{ flags: '--dry-run', description: 'Show what would be pulled without pulling' }
|
|
15
|
+
],
|
|
16
|
+
surfaces: ['cli'],
|
|
17
|
+
hardware: ['consumer'],
|
|
18
|
+
enabled: true,
|
|
19
|
+
order: 70
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default descriptor;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { getVibesRuntime } from '@vibesdotdev/runtime';
|
|
2
|
+
import type { UIContext } from '@vibesdotdev/cli/providers';
|
|
3
|
+
import type { SecretsStoreImplementation } from '../../kinds/store.interface';
|
|
4
|
+
import type { SecretsStoreDescriptor } from '../../kinds/store.schema';
|
|
5
|
+
import { resolveSecretsEnvironment } from '../shared/resolve-environment';
|
|
6
|
+
|
|
7
|
+
const LOCAL_BACKENDS = new Set(['env-file', 'encrypted-local']);
|
|
8
|
+
const REMOTE_BACKENDS = new Set(['cloudflare-api', 'vault']);
|
|
9
|
+
|
|
10
|
+
function parseKeyFilter(value: unknown): Set<string> | undefined {
|
|
11
|
+
const rawValues = Array.isArray(value)
|
|
12
|
+
? value.filter((entry): entry is string => typeof entry === 'string')
|
|
13
|
+
: typeof value === 'string'
|
|
14
|
+
? [value]
|
|
15
|
+
: [];
|
|
16
|
+
const keys = rawValues
|
|
17
|
+
.flatMap((entry) => entry.split(','))
|
|
18
|
+
.map((entry) => entry.trim())
|
|
19
|
+
.filter((entry) => entry.length > 0);
|
|
20
|
+
return keys.length > 0 ? new Set(keys) : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
async execute(_args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
|
|
25
|
+
const runtime = getVibesRuntime();
|
|
26
|
+
const ui = (await runtime.context('cli/ui')) as UIContext;
|
|
27
|
+
const options = opts as {
|
|
28
|
+
environment?: string;
|
|
29
|
+
from?: string;
|
|
30
|
+
to?: string;
|
|
31
|
+
key?: string | string[];
|
|
32
|
+
dryRun?: boolean;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const { name: envName, tier: envTier } = await resolveSecretsEnvironment(
|
|
36
|
+
ui,
|
|
37
|
+
options.environment
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const descriptors = runtime.assets('secrets/store').descriptors() as SecretsStoreDescriptor[];
|
|
41
|
+
|
|
42
|
+
const source = options.from
|
|
43
|
+
? descriptors.find((d) => d.id === options.from)
|
|
44
|
+
: descriptors
|
|
45
|
+
.filter((d) => d.tiers.includes(envTier) && REMOTE_BACKENDS.has(d.backend))
|
|
46
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))[0];
|
|
47
|
+
|
|
48
|
+
const target = options.to
|
|
49
|
+
? descriptors.find((d) => d.id === options.to)
|
|
50
|
+
: descriptors
|
|
51
|
+
.filter((d) => d.tiers.includes(envTier) && LOCAL_BACKENDS.has(d.backend))
|
|
52
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))[0];
|
|
53
|
+
|
|
54
|
+
if (!source) {
|
|
55
|
+
ui.error('No remote secrets store available. Cloudflare does not support pulling secret values.');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
if (!target) {
|
|
59
|
+
ui.error('No local secrets store available.');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (source.backend === 'cloudflare-api' || source.backend === 'cloudflare-secrets-store') {
|
|
64
|
+
ui.warn('Cloudflare secrets backends do not expose secret values. Pull is only supported from Vault.');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const sourceImpl = (await runtime
|
|
69
|
+
.query('secrets/store')
|
|
70
|
+
.withId(source.id)
|
|
71
|
+
.resolve()) as SecretsStoreImplementation;
|
|
72
|
+
let secrets = await sourceImpl.getAll(envName);
|
|
73
|
+
const keyFilter = parseKeyFilter(options.key);
|
|
74
|
+
if (keyFilter) {
|
|
75
|
+
secrets = Object.fromEntries(
|
|
76
|
+
Object.entries(secrets).filter(([key]) => keyFilter.has(key))
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const keys = Object.keys(secrets);
|
|
80
|
+
|
|
81
|
+
if (keys.length === 0) {
|
|
82
|
+
ui.info(`No secrets found in [${source.id}] for environment: ${envName}`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (options.dryRun) {
|
|
87
|
+
ui.log(`\nDry run: would pull ${keys.length} secret(s) from [${source.id}] to [${target.id}]\n`);
|
|
88
|
+
for (const key of keys.sort()) {
|
|
89
|
+
ui.log(` ${key}`);
|
|
90
|
+
}
|
|
91
|
+
ui.log('');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const targetImpl = (await runtime
|
|
96
|
+
.query('secrets/store')
|
|
97
|
+
.withId(target.id)
|
|
98
|
+
.resolve()) as SecretsStoreImplementation;
|
|
99
|
+
await targetImpl.setAll(envName, secrets);
|
|
100
|
+
|
|
101
|
+
ui.success(`Pulled ${keys.length} secret(s) from [${source.id}] to [${target.id}] for ${envName}`);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CLICommandAssetDescriptor } from '@vibesdotdev/cli/schemas/types';
|
|
2
|
+
|
|
3
|
+
const descriptor: CLICommandAssetDescriptor = {
|
|
4
|
+
kind: 'cli/command',
|
|
5
|
+
id: 'secrets.push',
|
|
6
|
+
name: 'push',
|
|
7
|
+
description: 'Deploy secrets from local store to a remote backend',
|
|
8
|
+
group: 'secrets',
|
|
9
|
+
options: [
|
|
10
|
+
{ flags: '--environment <name>', description: 'Target environment (default: current)' },
|
|
11
|
+
{ flags: '--source-env <name>', description: 'Source environment in the source backend (default: source backend\'s primary tier, e.g. "local" for encrypted-local)' },
|
|
12
|
+
{ flags: '--from <backend>', description: 'Source backend (default: highest-priority local)' },
|
|
13
|
+
{ flags: '--to <backend>', description: 'Target backend (default: highest-priority remote)' },
|
|
14
|
+
{ flags: '--key <key>', description: 'Push only one secret key (repeat or comma-separate for multiple keys)' },
|
|
15
|
+
{ flags: '--app <id>', description: 'Push only secrets required by this app' },
|
|
16
|
+
{ flags: '--dry-run', description: 'Show what would be pushed without pushing' }
|
|
17
|
+
],
|
|
18
|
+
surfaces: ['cli'],
|
|
19
|
+
hardware: ['consumer'],
|
|
20
|
+
enabled: true,
|
|
21
|
+
order: 60
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default descriptor;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { getVibesRuntime } from '@vibesdotdev/runtime';
|
|
2
|
+
import type { UIContext } from '@vibesdotdev/cli/providers';
|
|
3
|
+
import type { SecretsStoreImplementation } from '../../kinds/store.interface';
|
|
4
|
+
import type { SecretsStoreDescriptor, EnvironmentTier } from '../../kinds/store.schema';
|
|
5
|
+
import { resolveSecretRequirements, uniqueSecretKeys } from '../../requirements/resolver';
|
|
6
|
+
import { resolveSecretsEnvironment } from '../shared/resolve-environment';
|
|
7
|
+
|
|
8
|
+
const LOCAL_BACKENDS = new Set(['env-file', 'encrypted-local']);
|
|
9
|
+
const REMOTE_BACKENDS = new Set(['cloudflare-api', 'cloudflare-secrets-store', 'vault']);
|
|
10
|
+
|
|
11
|
+
// Preferred ordering when picking a backend's primary tier as the default
|
|
12
|
+
// source-env. We pick the lowest tier the backend serves (local > dev >
|
|
13
|
+
// staging > production), since that's where a developer's local `secrets set`
|
|
14
|
+
// writes land. Keeps `push --environment production` reading from the user's
|
|
15
|
+
// machine-local store rather than stale data left in vault.environments.production
|
|
16
|
+
// from a long-ago `vibes secrets import`.
|
|
17
|
+
const TIER_PREFERENCE: EnvironmentTier[] = ['local', 'dev', 'staging', 'production'];
|
|
18
|
+
|
|
19
|
+
function pickPrimaryTier(descriptor: SecretsStoreDescriptor): EnvironmentTier {
|
|
20
|
+
for (const tier of TIER_PREFERENCE) {
|
|
21
|
+
if (descriptor.tiers.includes(tier)) return tier;
|
|
22
|
+
}
|
|
23
|
+
return descriptor.tiers[0] ?? 'local';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function findBackend(
|
|
27
|
+
descriptors: SecretsStoreDescriptor[],
|
|
28
|
+
tier: EnvironmentTier,
|
|
29
|
+
backendId: string | undefined,
|
|
30
|
+
filter: Set<string>
|
|
31
|
+
): SecretsStoreDescriptor | undefined {
|
|
32
|
+
if (backendId) return descriptors.find((d) => d.id === backendId);
|
|
33
|
+
return descriptors
|
|
34
|
+
.filter((d) => d.tiers.includes(tier) && filter.has(d.backend))
|
|
35
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))[0];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseKeyFilter(value: unknown): Set<string> | undefined {
|
|
39
|
+
const rawValues = Array.isArray(value)
|
|
40
|
+
? value.filter((entry): entry is string => typeof entry === 'string')
|
|
41
|
+
: typeof value === 'string'
|
|
42
|
+
? [value]
|
|
43
|
+
: [];
|
|
44
|
+
const keys = rawValues
|
|
45
|
+
.flatMap((entry) => entry.split(','))
|
|
46
|
+
.map((entry) => entry.trim())
|
|
47
|
+
.filter((entry) => entry.length > 0);
|
|
48
|
+
return keys.length > 0 ? new Set(keys) : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default {
|
|
52
|
+
async execute(_args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
|
|
53
|
+
const runtime = getVibesRuntime();
|
|
54
|
+
const ui = (await runtime.context('cli/ui')) as UIContext;
|
|
55
|
+
const options = opts as {
|
|
56
|
+
environment?: string;
|
|
57
|
+
sourceEnv?: string;
|
|
58
|
+
from?: string;
|
|
59
|
+
to?: string;
|
|
60
|
+
key?: string | string[];
|
|
61
|
+
app?: string;
|
|
62
|
+
dryRun?: boolean;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const { name: envName, tier: envTier } = await resolveSecretsEnvironment(
|
|
66
|
+
ui,
|
|
67
|
+
options.environment
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const descriptors = runtime.assets('secrets/store').descriptors() as SecretsStoreDescriptor[];
|
|
71
|
+
// Pick the source backend independently of the target env tier: typical
|
|
72
|
+
// flow is "push from machine-local store to production CF store" and
|
|
73
|
+
// the local store doesn't serve the production tier.
|
|
74
|
+
const source = options.from
|
|
75
|
+
? descriptors.find((d) => d.id === options.from)
|
|
76
|
+
: descriptors
|
|
77
|
+
.filter((d) => LOCAL_BACKENDS.has(d.backend))
|
|
78
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))[0];
|
|
79
|
+
const target = findBackend(descriptors, envTier, options.to, REMOTE_BACKENDS);
|
|
80
|
+
|
|
81
|
+
if (!source) {
|
|
82
|
+
ui.error('No local secrets store found. Set secrets with `vibes secrets set` first.');
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
if (!target) {
|
|
86
|
+
ui.error('No remote secrets store available for this environment tier.');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Source env defaults to the source backend's primary tier (e.g. 'local'
|
|
91
|
+
// for encrypted-local), NOT the target env. Reading from `getAll(envName)`
|
|
92
|
+
// blindly used to pull whatever stale values a prior `vibes secrets import`
|
|
93
|
+
// left in `vault.environments[envName]`, even though developers' day-to-day
|
|
94
|
+
// `vibes secrets set` writes always land in the local-tier namespace.
|
|
95
|
+
// That mismatch would overwrite live prod keys with stale test keys.
|
|
96
|
+
const sourceEnv = options.sourceEnv ?? pickPrimaryTier(source);
|
|
97
|
+
|
|
98
|
+
if (sourceEnv !== envName) {
|
|
99
|
+
ui.log(
|
|
100
|
+
`Reading from [${source.id}] env "${sourceEnv}" (source backend's primary tier); writing to [${target.id}] env "${envName}". Override with --source-env if needed.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const sourceImpl = (await runtime
|
|
105
|
+
.query('secrets/store')
|
|
106
|
+
.withId(source.id)
|
|
107
|
+
.resolve()) as SecretsStoreImplementation;
|
|
108
|
+
let secrets = await sourceImpl.getAll(sourceEnv);
|
|
109
|
+
|
|
110
|
+
if (options.app) {
|
|
111
|
+
const requirements = await resolveSecretRequirements(runtime);
|
|
112
|
+
const appReqs = requirements.filter((r) => r.appId === options.app);
|
|
113
|
+
const appKeys = new Set(uniqueSecretKeys(appReqs));
|
|
114
|
+
secrets = Object.fromEntries(
|
|
115
|
+
Object.entries(secrets).filter(([k]) => appKeys.has(k))
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const keyFilter = parseKeyFilter(options.key);
|
|
120
|
+
if (keyFilter) {
|
|
121
|
+
secrets = Object.fromEntries(
|
|
122
|
+
Object.entries(secrets).filter(([key]) => keyFilter.has(key))
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const keys = Object.keys(secrets);
|
|
127
|
+
if (keys.length === 0) {
|
|
128
|
+
ui.info('No secrets to push.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (options.dryRun) {
|
|
133
|
+
ui.log(`\nDry run: would push ${keys.length} secret(s) from [${source.id}] to [${target.id}]\n`);
|
|
134
|
+
for (const key of keys.sort()) {
|
|
135
|
+
ui.log(` ${key}`);
|
|
136
|
+
}
|
|
137
|
+
ui.log('');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const targetImpl = (await runtime
|
|
142
|
+
.query('secrets/store')
|
|
143
|
+
.withId(target.id)
|
|
144
|
+
.resolve()) as SecretsStoreImplementation;
|
|
145
|
+
await targetImpl.setAll(envName, secrets);
|
|
146
|
+
|
|
147
|
+
ui.success(`Pushed ${keys.length} secret(s) from [${source.id}] to [${target.id}] for ${envName}`);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CLICommandAssetDescriptor } from '@vibesdotdev/cli/schemas/types';
|
|
2
|
+
|
|
3
|
+
const descriptor: CLICommandAssetDescriptor = {
|
|
4
|
+
kind: 'cli/command',
|
|
5
|
+
id: 'secrets.reveal',
|
|
6
|
+
name: 'reveal',
|
|
7
|
+
description: 'Reveal a secret value (writes to stderr with audit log)',
|
|
8
|
+
group: 'secrets',
|
|
9
|
+
arguments: [{ name: 'key', description: 'Secret key to reveal', required: true }],
|
|
10
|
+
options: [
|
|
11
|
+
{ flags: '--environment <name>', description: 'Target environment (default: current)' },
|
|
12
|
+
{ flags: '--backend <id>', description: 'Filter to a specific backend' },
|
|
13
|
+
{ flags: '--force', description: 'Skip confirmation prompt' }
|
|
14
|
+
],
|
|
15
|
+
surfaces: ['cli'],
|
|
16
|
+
hardware: ['consumer'],
|
|
17
|
+
enabled: true,
|
|
18
|
+
order: 20
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default descriptor;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { getVibesRuntime } from '@vibesdotdev/runtime';
|
|
2
|
+
import type { UIContext } from '@vibesdotdev/cli/providers';
|
|
3
|
+
import type { SecretsStoreImplementation } from '../../kinds/store.interface';
|
|
4
|
+
import type { SecretsStoreDescriptor, EnvironmentTier } from '../../kinds/store.schema';
|
|
5
|
+
import type { AuditLogInput } from '@vibesdotdev/logging';
|
|
6
|
+
import { resolveSecretsEnvironment } from '../shared/resolve-environment';
|
|
7
|
+
|
|
8
|
+
function resolveBackend(
|
|
9
|
+
descriptors: SecretsStoreDescriptor[],
|
|
10
|
+
tier: EnvironmentTier,
|
|
11
|
+
backendId?: string
|
|
12
|
+
): SecretsStoreDescriptor | undefined {
|
|
13
|
+
if (backendId) return descriptors.find((d) => d.id === backendId);
|
|
14
|
+
const tierStores = descriptors
|
|
15
|
+
.filter((d) => d.tiers.includes(tier))
|
|
16
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
17
|
+
return tierStores[0];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function emitAudit(input: AuditLogInput): void {
|
|
21
|
+
process.stderr.write(`[AUDIT] ${JSON.stringify({
|
|
22
|
+
id: crypto.randomUUID(),
|
|
23
|
+
timestamp: new Date().toISOString(),
|
|
24
|
+
...input
|
|
25
|
+
})}\n`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default {
|
|
29
|
+
async execute(args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
|
|
30
|
+
const runtime = getVibesRuntime();
|
|
31
|
+
const ui = (await runtime.context('cli/ui')) as UIContext;
|
|
32
|
+
const key = args.key as string;
|
|
33
|
+
const options = opts as { environment?: string; backend?: string; force?: boolean };
|
|
34
|
+
|
|
35
|
+
if (!key) {
|
|
36
|
+
ui.error('Usage: vibes secrets reveal <KEY>');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { name: envName, tier: envTier } = await resolveSecretsEnvironment(
|
|
41
|
+
ui,
|
|
42
|
+
options.environment
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const descriptors = runtime.assets('secrets/store').descriptors() as SecretsStoreDescriptor[];
|
|
46
|
+
const target = resolveBackend(descriptors, envTier, options.backend);
|
|
47
|
+
|
|
48
|
+
if (!target) {
|
|
49
|
+
ui.error(`No secrets store available for tier: ${envTier}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const impl = (await runtime
|
|
54
|
+
.query('secrets/store')
|
|
55
|
+
.withId(target.id)
|
|
56
|
+
.resolve()) as SecretsStoreImplementation;
|
|
57
|
+
|
|
58
|
+
const value = await impl.get(envName, key);
|
|
59
|
+
if (value === undefined || value === '') {
|
|
60
|
+
ui.error(`Secret not found: ${key}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Skip the interactive confirmation when:
|
|
65
|
+
// - explicit --force flag
|
|
66
|
+
// - VIBES_SECRETS_REVEAL_NO_PROMPT=1 set (CI / scripts opt-in)
|
|
67
|
+
// - stdin or stderr is not a TTY (automation context — there's no
|
|
68
|
+
// human to answer the prompt, and blocking on stdin would hang)
|
|
69
|
+
const noPromptEnv = process.env.VIBES_SECRETS_REVEAL_NO_PROMPT === '1';
|
|
70
|
+
const stdinIsTty = Boolean((process.stdin as { isTTY?: boolean }).isTTY);
|
|
71
|
+
const stderrIsTty = Boolean((process.stderr as { isTTY?: boolean }).isTTY);
|
|
72
|
+
const skipPrompt = options.force || noPromptEnv || !stdinIsTty || !stderrIsTty;
|
|
73
|
+
if (!skipPrompt) {
|
|
74
|
+
process.stderr.write(`\n[REVEAL] About to reveal secret "${key}" to terminal.\n`);
|
|
75
|
+
process.stderr.write(`This value will be printed to stderr and logged to the audit trail.\n`);
|
|
76
|
+
process.stderr.write(`Continue? [y/N] `);
|
|
77
|
+
|
|
78
|
+
let confirmation = '';
|
|
79
|
+
for await (const chunk of process.stdin) {
|
|
80
|
+
confirmation = (chunk as Buffer).toString('utf-8').trim().toLowerCase();
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (confirmation !== 'y' && confirmation !== 'yes') {
|
|
85
|
+
ui.info('Reveal cancelled.');
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
emitAudit({
|
|
91
|
+
category: 'data_access',
|
|
92
|
+
severity: 'high',
|
|
93
|
+
action: 'secrets.reveal',
|
|
94
|
+
actor: { id: 'cli', type: 'user', name: 'CLI user' },
|
|
95
|
+
target: { id: key, type: 'secret', name: key },
|
|
96
|
+
outcome: 'success',
|
|
97
|
+
context: {
|
|
98
|
+
environment: envName,
|
|
99
|
+
backend: target.id
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Value goes to STDOUT so scripts can capture it via `$(vibes secrets reveal X)`.
|
|
104
|
+
// Audit/notice messaging stays on STDERR so a piped consumer gets a clean value.
|
|
105
|
+
process.stdout.write(`${value}\n`);
|
|
106
|
+
process.stderr.write(`\n[REVEAL] ${key} revealed (logged to audit trail).\n`);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CLIGroupAssetDescriptor } from '@vibesdotdev/cli/schemas/types';
|
|
2
|
+
|
|
3
|
+
const descriptor: CLIGroupAssetDescriptor = {
|
|
4
|
+
kind: 'cli/group',
|
|
5
|
+
id: 'secrets',
|
|
6
|
+
name: 'secrets',
|
|
7
|
+
description: 'Manage environment secrets across backends',
|
|
8
|
+
surfaces: ['cli'],
|
|
9
|
+
hardware: ['consumer'],
|
|
10
|
+
enabled: true
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default descriptor;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CLICommandAssetDescriptor } from '@vibesdotdev/cli/schemas/types';
|
|
2
|
+
|
|
3
|
+
const descriptor: CLICommandAssetDescriptor = {
|
|
4
|
+
kind: 'cli/command',
|
|
5
|
+
id: 'secrets.set',
|
|
6
|
+
name: 'set',
|
|
7
|
+
description: 'Set a secret value for the current environment',
|
|
8
|
+
group: 'secrets',
|
|
9
|
+
arguments: [
|
|
10
|
+
{ name: 'key', description: 'Secret key name (e.g., VIBES_AUTH_SECRET)', required: true },
|
|
11
|
+
{ name: 'value', description: 'Secret value (omit to prompt securely)', required: false }
|
|
12
|
+
],
|
|
13
|
+
options: [
|
|
14
|
+
{ flags: '--environment <name>', description: 'Target environment (default: current)' },
|
|
15
|
+
{ flags: '--backend <id>', description: 'Target backend (default: primary for tier)' }
|
|
16
|
+
],
|
|
17
|
+
surfaces: ['cli'],
|
|
18
|
+
hardware: ['consumer'],
|
|
19
|
+
enabled: true,
|
|
20
|
+
order: 20
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default descriptor;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { getVibesRuntime } from '@vibesdotdev/runtime';
|
|
2
|
+
import type { UIContext } from '@vibesdotdev/cli/providers';
|
|
3
|
+
import type { SecretsStoreImplementation } from '../../kinds/store.interface';
|
|
4
|
+
import type { SecretsStoreDescriptor, EnvironmentTier } from '../../kinds/store.schema';
|
|
5
|
+
import { resolveSecretsEnvironment } from '../shared/resolve-environment';
|
|
6
|
+
|
|
7
|
+
// `set` is the write side of the local-development workflow: the user types
|
|
8
|
+
// a value and expects it to land in their machine-local store. Remote
|
|
9
|
+
// backends (vault, cloudflare-secrets-store, ...) are the *deploy* side —
|
|
10
|
+
// reached via `secrets push` after the user explicitly opts in. Default
|
|
11
|
+
// routing therefore restricts to local backend kinds; an explicit
|
|
12
|
+
// `--backend <id>` still wins.
|
|
13
|
+
const LOCAL_BACKEND_KINDS = new Set(['env-file', 'encrypted-local']);
|
|
14
|
+
|
|
15
|
+
function resolveBackend(
|
|
16
|
+
descriptors: SecretsStoreDescriptor[],
|
|
17
|
+
tier: EnvironmentTier,
|
|
18
|
+
backendId?: string
|
|
19
|
+
): SecretsStoreDescriptor | undefined {
|
|
20
|
+
if (backendId) return descriptors.find((d) => d.id === backendId);
|
|
21
|
+
const tierStores = descriptors
|
|
22
|
+
.filter((d) => d.tiers.includes(tier))
|
|
23
|
+
.filter((d) => LOCAL_BACKEND_KINDS.has(d.backend))
|
|
24
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
25
|
+
return tierStores[0];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default {
|
|
29
|
+
async execute(args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
|
|
30
|
+
const runtime = getVibesRuntime();
|
|
31
|
+
const ui = (await runtime.context('cli/ui')) as UIContext;
|
|
32
|
+
const key = args.key as string;
|
|
33
|
+
let value = args.value as string | undefined;
|
|
34
|
+
const options = opts as { environment?: string; backend?: string };
|
|
35
|
+
|
|
36
|
+
const { name: envName, tier: envTier } = await resolveSecretsEnvironment(
|
|
37
|
+
ui,
|
|
38
|
+
options.environment
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if (!key || !/^[_A-Z0-9]+$/.test(key)) {
|
|
42
|
+
ui.error('Secret key must match pattern: ^[_A-Z0-9]+$');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!value) {
|
|
47
|
+
process.stdout.write(`Enter value for ${key}: `);
|
|
48
|
+
const chunks: Buffer[] = [];
|
|
49
|
+
for await (const chunk of process.stdin) {
|
|
50
|
+
chunks.push(chunk as Buffer);
|
|
51
|
+
if ((chunk as Buffer).includes(10)) break;
|
|
52
|
+
}
|
|
53
|
+
value = Buffer.concat(chunks).toString('utf-8').trim();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!value) {
|
|
57
|
+
ui.error('No value provided');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const descriptors = runtime.assets('secrets/store').descriptors() as SecretsStoreDescriptor[];
|
|
62
|
+
const target = resolveBackend(descriptors, envTier, options.backend);
|
|
63
|
+
|
|
64
|
+
if (!target) {
|
|
65
|
+
ui.error(`No secrets store available for tier: ${envTier}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const impl = (await runtime
|
|
70
|
+
.query('secrets/store')
|
|
71
|
+
.withId(target.id)
|
|
72
|
+
.resolve()) as SecretsStoreImplementation;
|
|
73
|
+
await impl.set(envName, key, value);
|
|
74
|
+
|
|
75
|
+
ui.success(`Set ${key} in [${target.id}] for environment: ${envName}`);
|
|
76
|
+
}
|
|
77
|
+
};
|