@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.
Files changed (208) hide show
  1. package/README.md +59 -0
  2. package/SPEC.md +47 -0
  3. package/dist/cli/check/schemas/check-result.d.ts +9 -0
  4. package/dist/cli/check/schemas/check-result.d.ts.map +1 -0
  5. package/dist/cli/check/schemas/check-result.js +2 -0
  6. package/dist/cli/check/schemas/check-result.js.map +1 -0
  7. package/dist/cli/check/secrets.check.cli-command.descriptor.d.ts +4 -0
  8. package/dist/cli/check/secrets.check.cli-command.descriptor.d.ts.map +1 -0
  9. package/dist/cli/check/secrets.check.cli-command.descriptor.js +19 -0
  10. package/dist/cli/check/secrets.check.cli-command.descriptor.js.map +1 -0
  11. package/dist/cli/check/secrets.check.cli-command.impl.d.ts +5 -0
  12. package/dist/cli/check/secrets.check.cli-command.impl.d.ts.map +1 -0
  13. package/dist/cli/check/secrets.check.cli-command.impl.js +135 -0
  14. package/dist/cli/check/secrets.check.cli-command.impl.js.map +1 -0
  15. package/dist/cli/export/secrets.export.cli-command.descriptor.d.ts +4 -0
  16. package/dist/cli/export/secrets.export.cli-command.descriptor.d.ts.map +1 -0
  17. package/dist/cli/export/secrets.export.cli-command.descriptor.js +20 -0
  18. package/dist/cli/export/secrets.export.cli-command.descriptor.js.map +1 -0
  19. package/dist/cli/export/secrets.export.cli-command.impl.d.ts +5 -0
  20. package/dist/cli/export/secrets.export.cli-command.impl.d.ts.map +1 -0
  21. package/dist/cli/export/secrets.export.cli-command.impl.js +104 -0
  22. package/dist/cli/export/secrets.export.cli-command.impl.js.map +1 -0
  23. package/dist/cli/hooks/pre-commit-secrets.d.ts +2 -0
  24. package/dist/cli/hooks/pre-commit-secrets.d.ts.map +1 -0
  25. package/dist/cli/hooks/pre-commit-secrets.js +68 -0
  26. package/dist/cli/hooks/pre-commit-secrets.js.map +1 -0
  27. package/dist/cli/import/secrets.import.cli-command.descriptor.d.ts +4 -0
  28. package/dist/cli/import/secrets.import.cli-command.descriptor.d.ts.map +1 -0
  29. package/dist/cli/import/secrets.import.cli-command.descriptor.js +19 -0
  30. package/dist/cli/import/secrets.import.cli-command.descriptor.js.map +1 -0
  31. package/dist/cli/import/secrets.import.cli-command.impl.d.ts +5 -0
  32. package/dist/cli/import/secrets.import.cli-command.impl.d.ts.map +1 -0
  33. package/dist/cli/import/secrets.import.cli-command.impl.js +155 -0
  34. package/dist/cli/import/secrets.import.cli-command.impl.js.map +1 -0
  35. package/dist/cli/list/secrets.list.cli-command.descriptor.d.ts +4 -0
  36. package/dist/cli/list/secrets.list.cli-command.descriptor.d.ts.map +1 -0
  37. package/dist/cli/list/secrets.list.cli-command.descriptor.js +18 -0
  38. package/dist/cli/list/secrets.list.cli-command.descriptor.js.map +1 -0
  39. package/dist/cli/list/secrets.list.cli-command.impl.d.ts +5 -0
  40. package/dist/cli/list/secrets.list.cli-command.impl.d.ts.map +1 -0
  41. package/dist/cli/list/secrets.list.cli-command.impl.js +61 -0
  42. package/dist/cli/list/secrets.list.cli-command.impl.js.map +1 -0
  43. package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.d.ts +4 -0
  44. package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.d.ts.map +1 -0
  45. package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.js +16 -0
  46. package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.js.map +1 -0
  47. package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.d.ts +5 -0
  48. package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.d.ts.map +1 -0
  49. package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.js +10 -0
  50. package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.js.map +1 -0
  51. package/dist/cli/pull/secrets.pull.cli-command.descriptor.d.ts +4 -0
  52. package/dist/cli/pull/secrets.pull.cli-command.descriptor.d.ts.map +1 -0
  53. package/dist/cli/pull/secrets.pull.cli-command.descriptor.js +20 -0
  54. package/dist/cli/pull/secrets.pull.cli-command.descriptor.js.map +1 -0
  55. package/dist/cli/pull/secrets.pull.cli-command.impl.d.ts +5 -0
  56. package/dist/cli/pull/secrets.pull.cli-command.impl.d.ts.map +1 -0
  57. package/dist/cli/pull/secrets.pull.cli-command.impl.js +76 -0
  58. package/dist/cli/pull/secrets.pull.cli-command.impl.js.map +1 -0
  59. package/dist/cli/push/secrets.push.cli-command.descriptor.d.ts +4 -0
  60. package/dist/cli/push/secrets.push.cli-command.descriptor.d.ts.map +1 -0
  61. package/dist/cli/push/secrets.push.cli-command.descriptor.js +22 -0
  62. package/dist/cli/push/secrets.push.cli-command.descriptor.js.map +1 -0
  63. package/dist/cli/push/secrets.push.cli-command.impl.d.ts +5 -0
  64. package/dist/cli/push/secrets.push.cli-command.impl.d.ts.map +1 -0
  65. package/dist/cli/push/secrets.push.cli-command.impl.js +109 -0
  66. package/dist/cli/push/secrets.push.cli-command.impl.js.map +1 -0
  67. package/dist/cli/reveal/secrets.reveal.cli-command.descriptor.d.ts +4 -0
  68. package/dist/cli/reveal/secrets.reveal.cli-command.descriptor.d.ts.map +1 -0
  69. package/dist/cli/reveal/secrets.reveal.cli-command.descriptor.js +19 -0
  70. package/dist/cli/reveal/secrets.reveal.cli-command.descriptor.js.map +1 -0
  71. package/dist/cli/reveal/secrets.reveal.cli-command.impl.d.ts +5 -0
  72. package/dist/cli/reveal/secrets.reveal.cli-command.impl.d.ts.map +1 -0
  73. package/dist/cli/reveal/secrets.reveal.cli-command.impl.js +85 -0
  74. package/dist/cli/reveal/secrets.reveal.cli-command.impl.js.map +1 -0
  75. package/dist/cli/secrets.cli-group.descriptor.d.ts +4 -0
  76. package/dist/cli/secrets.cli-group.descriptor.d.ts.map +1 -0
  77. package/dist/cli/secrets.cli-group.descriptor.js +11 -0
  78. package/dist/cli/secrets.cli-group.descriptor.js.map +1 -0
  79. package/dist/cli/set/secrets.set.cli-command.descriptor.d.ts +4 -0
  80. package/dist/cli/set/secrets.set.cli-command.descriptor.d.ts.map +1 -0
  81. package/dist/cli/set/secrets.set.cli-command.descriptor.js +21 -0
  82. package/dist/cli/set/secrets.set.cli-command.descriptor.js.map +1 -0
  83. package/dist/cli/set/secrets.set.cli-command.impl.d.ts +5 -0
  84. package/dist/cli/set/secrets.set.cli-command.impl.d.ts.map +1 -0
  85. package/dist/cli/set/secrets.set.cli-command.impl.js +59 -0
  86. package/dist/cli/set/secrets.set.cli-command.impl.js.map +1 -0
  87. package/dist/cli/shared/resolve-environment.d.ts +14 -0
  88. package/dist/cli/shared/resolve-environment.d.ts.map +1 -0
  89. package/dist/cli/shared/resolve-environment.js +45 -0
  90. package/dist/cli/shared/resolve-environment.js.map +1 -0
  91. package/dist/cli/unset/secrets.unset.cli-command.descriptor.d.ts +4 -0
  92. package/dist/cli/unset/secrets.unset.cli-command.descriptor.d.ts.map +1 -0
  93. package/dist/cli/unset/secrets.unset.cli-command.descriptor.js +20 -0
  94. package/dist/cli/unset/secrets.unset.cli-command.descriptor.js.map +1 -0
  95. package/dist/cli/unset/secrets.unset.cli-command.impl.d.ts +5 -0
  96. package/dist/cli/unset/secrets.unset.cli-command.impl.d.ts.map +1 -0
  97. package/dist/cli/unset/secrets.unset.cli-command.impl.js +31 -0
  98. package/dist/cli/unset/secrets.unset.cli-command.impl.js.map +1 -0
  99. package/dist/docs/backends.docs.descriptor.d.ts +4 -0
  100. package/dist/docs/backends.docs.descriptor.d.ts.map +1 -0
  101. package/dist/docs/backends.docs.descriptor.js +149 -0
  102. package/dist/docs/backends.docs.descriptor.js.map +1 -0
  103. package/dist/docs/encryption.docs.descriptor.d.ts +4 -0
  104. package/dist/docs/encryption.docs.descriptor.d.ts.map +1 -0
  105. package/dist/docs/encryption.docs.descriptor.js +163 -0
  106. package/dist/docs/encryption.docs.descriptor.js.map +1 -0
  107. package/dist/docs/env-file.docs.descriptor.d.ts +4 -0
  108. package/dist/docs/env-file.docs.descriptor.d.ts.map +1 -0
  109. package/dist/docs/env-file.docs.descriptor.js +207 -0
  110. package/dist/docs/env-file.docs.descriptor.js.map +1 -0
  111. package/dist/index.d.ts +13 -0
  112. package/dist/index.d.ts.map +1 -0
  113. package/dist/index.js +13 -0
  114. package/dist/index.js.map +1 -0
  115. package/dist/kinds/index.d.ts +4 -0
  116. package/dist/kinds/index.d.ts.map +1 -0
  117. package/dist/kinds/index.js +3 -0
  118. package/dist/kinds/index.js.map +1 -0
  119. package/dist/kinds/schemas/store.schema.d.ts +49 -0
  120. package/dist/kinds/schemas/store.schema.d.ts.map +1 -0
  121. package/dist/kinds/schemas/store.schema.js +34 -0
  122. package/dist/kinds/schemas/store.schema.js.map +1 -0
  123. package/dist/kinds/schemas/store.types.d.ts +28 -0
  124. package/dist/kinds/schemas/store.types.d.ts.map +1 -0
  125. package/dist/kinds/schemas/store.types.js +2 -0
  126. package/dist/kinds/schemas/store.types.js.map +1 -0
  127. package/dist/kinds/store.interface.d.ts +2 -0
  128. package/dist/kinds/store.interface.d.ts.map +1 -0
  129. package/dist/kinds/store.interface.js +2 -0
  130. package/dist/kinds/store.interface.js.map +1 -0
  131. package/dist/kinds/store.kind.d.ts +10 -0
  132. package/dist/kinds/store.kind.d.ts.map +1 -0
  133. package/dist/kinds/store.kind.js +36 -0
  134. package/dist/kinds/store.kind.js.map +1 -0
  135. package/dist/kinds/store.schema.d.ts +2 -0
  136. package/dist/kinds/store.schema.d.ts.map +1 -0
  137. package/dist/kinds/store.schema.js +2 -0
  138. package/dist/kinds/store.schema.js.map +1 -0
  139. package/dist/manifest/canonical.d.ts +30 -0
  140. package/dist/manifest/canonical.d.ts.map +1 -0
  141. package/dist/manifest/canonical.js +313 -0
  142. package/dist/manifest/canonical.js.map +1 -0
  143. package/dist/manifest/import-manifest.schema.d.ts +77 -0
  144. package/dist/manifest/import-manifest.schema.d.ts.map +1 -0
  145. package/dist/manifest/import-manifest.schema.js +55 -0
  146. package/dist/manifest/import-manifest.schema.js.map +1 -0
  147. package/dist/manifest/index.d.ts +3 -0
  148. package/dist/manifest/index.d.ts.map +1 -0
  149. package/dist/manifest/index.js +3 -0
  150. package/dist/manifest/index.js.map +1 -0
  151. package/dist/requirements/index.d.ts +2 -0
  152. package/dist/requirements/index.d.ts.map +1 -0
  153. package/dist/requirements/index.js +2 -0
  154. package/dist/requirements/index.js.map +1 -0
  155. package/dist/requirements/resolver.d.ts +52 -0
  156. package/dist/requirements/resolver.d.ts.map +1 -0
  157. package/dist/requirements/resolver.js +196 -0
  158. package/dist/requirements/resolver.js.map +1 -0
  159. package/dist/requirements/schemas/requirements.d.ts +27 -0
  160. package/dist/requirements/schemas/requirements.d.ts.map +1 -0
  161. package/dist/requirements/schemas/requirements.js +2 -0
  162. package/dist/requirements/schemas/requirements.js.map +1 -0
  163. package/dist/secrets.plugin.d.ts +8 -0
  164. package/dist/secrets.plugin.d.ts.map +1 -0
  165. package/dist/secrets.plugin.js +59 -0
  166. package/dist/secrets.plugin.js.map +1 -0
  167. package/package.json +108 -0
  168. package/src/cli/check/schemas/check-result.ts +8 -0
  169. package/src/cli/check/secrets.check.cli-command.descriptor.ts +21 -0
  170. package/src/cli/check/secrets.check.cli-command.impl.ts +163 -0
  171. package/src/cli/export/secrets.export.cli-command.descriptor.ts +22 -0
  172. package/src/cli/export/secrets.export.cli-command.impl.ts +139 -0
  173. package/src/cli/hooks/pre-commit-secrets.ts +73 -0
  174. package/src/cli/import/secrets.import.cli-command.descriptor.ts +21 -0
  175. package/src/cli/import/secrets.import.cli-command.impl.ts +178 -0
  176. package/src/cli/list/secrets.list.cli-command.descriptor.ts +21 -0
  177. package/src/cli/list/secrets.list.cli-command.impl.ts +79 -0
  178. package/src/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.ts +18 -0
  179. package/src/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.ts +11 -0
  180. package/src/cli/pull/secrets.pull.cli-command.descriptor.ts +22 -0
  181. package/src/cli/pull/secrets.pull.cli-command.impl.ts +103 -0
  182. package/src/cli/push/secrets.push.cli-command.descriptor.ts +24 -0
  183. package/src/cli/push/secrets.push.cli-command.impl.ts +149 -0
  184. package/src/cli/reveal/secrets.reveal.cli-command.descriptor.ts +21 -0
  185. package/src/cli/reveal/secrets.reveal.cli-command.impl.ts +108 -0
  186. package/src/cli/secrets.cli-group.descriptor.ts +13 -0
  187. package/src/cli/set/secrets.set.cli-command.descriptor.ts +23 -0
  188. package/src/cli/set/secrets.set.cli-command.impl.ts +77 -0
  189. package/src/cli/shared/resolve-environment.ts +57 -0
  190. package/src/cli/unset/secrets.unset.cli-command.descriptor.ts +22 -0
  191. package/src/cli/unset/secrets.unset.cli-command.impl.ts +41 -0
  192. package/src/docs/backends.docs.descriptor.ts +151 -0
  193. package/src/docs/encryption.docs.descriptor.ts +165 -0
  194. package/src/docs/env-file.docs.descriptor.ts +209 -0
  195. package/src/index.ts +35 -0
  196. package/src/kinds/index.ts +12 -0
  197. package/src/kinds/schemas/store.schema.ts +47 -0
  198. package/src/kinds/schemas/store.types.ts +35 -0
  199. package/src/kinds/store.interface.ts +1 -0
  200. package/src/kinds/store.kind.ts +52 -0
  201. package/src/kinds/store.schema.ts +8 -0
  202. package/src/manifest/canonical.ts +324 -0
  203. package/src/manifest/import-manifest.schema.ts +63 -0
  204. package/src/manifest/index.ts +12 -0
  205. package/src/requirements/index.ts +6 -0
  206. package/src/requirements/resolver.ts +216 -0
  207. package/src/requirements/schemas/requirements.ts +29 -0
  208. 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
+ };