@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,163 @@
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 type { CheckResult } from './schemas/check-result';
6
+ import { resolveSecretRequirements, groupRequirementsByApp } from '../../requirements/resolver';
7
+ import { canonicalImportManifest } from '../../manifest/canonical';
8
+ import { resolveSecretsEnvironment } from '../shared/resolve-environment';
9
+
10
+ interface ManifestCheck {
11
+ key: string;
12
+ required: boolean;
13
+ source: string | undefined;
14
+ status: 'ok' | 'missing' | 'warn';
15
+ storedIn: string | undefined;
16
+ }
17
+
18
+ export default {
19
+ async execute(_args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
20
+ const runtime = getVibesRuntime();
21
+ const ui = (await runtime.context('cli/ui')) as UIContext;
22
+ const options = opts as {
23
+ environment?: string;
24
+ app?: string;
25
+ manifest?: boolean;
26
+ json?: boolean;
27
+ };
28
+
29
+ const { name: envName, tier: envTier } = await resolveSecretsEnvironment(
30
+ ui,
31
+ options.environment
32
+ );
33
+
34
+ let requirements = await resolveSecretRequirements(runtime);
35
+ if (options.app) {
36
+ requirements = requirements.filter((r) => r.appId === options.app);
37
+ }
38
+
39
+ if (requirements.length === 0 && !options.manifest) {
40
+ ui.info(options.app
41
+ ? `No secret requirements found for app: ${options.app}`
42
+ : 'No secret requirements found in infra descriptors');
43
+ return;
44
+ }
45
+
46
+ const descriptors = runtime.assets('secrets/store').descriptors() as SecretsStoreDescriptor[];
47
+ const tierStores = descriptors.filter((d) => d.tiers.includes(envTier));
48
+
49
+ const availableKeys = new Map<string, string>();
50
+ for (const desc of tierStores) {
51
+ const impl = (await runtime
52
+ .query('secrets/store')
53
+ .withId(desc.id)
54
+ .resolve()) as SecretsStoreImplementation;
55
+ const entries = await impl.list(envName);
56
+ for (const entry of entries) {
57
+ if (entry.hasValue && !availableKeys.has(entry.key)) {
58
+ availableKeys.set(entry.key, entry.source);
59
+ }
60
+ }
61
+ }
62
+
63
+ const results: CheckResult[] = requirements.map((req) => {
64
+ const source = availableKeys.get(req.key);
65
+ let status: 'ok' | 'missing' | 'warn';
66
+ if (source) {
67
+ status = 'ok';
68
+ } else if (req.required) {
69
+ status = 'missing';
70
+ } else {
71
+ status = 'warn';
72
+ }
73
+ return { ...req, status, source };
74
+ });
75
+
76
+ const manifestChecks: ManifestCheck[] = options.manifest
77
+ ? canonicalImportManifest.secrets.map((entry) => {
78
+ const storedIn = availableKeys.get(entry.key);
79
+ let status: 'ok' | 'missing' | 'warn';
80
+ if (storedIn) status = 'ok';
81
+ else if (entry.required) status = 'missing';
82
+ else status = 'warn';
83
+ return { key: entry.key, required: entry.required, source: entry.source, status, storedIn };
84
+ })
85
+ : [];
86
+
87
+ if (options.json) {
88
+ const summary: Record<string, unknown> = {
89
+ environment: envName,
90
+ tier: envTier,
91
+ total: results.length,
92
+ ok: results.filter((r) => r.status === 'ok').length,
93
+ missing: results.filter((r) => r.status === 'missing').length,
94
+ warn: results.filter((r) => r.status === 'warn').length,
95
+ results
96
+ };
97
+ if (options.manifest) {
98
+ summary.manifest = {
99
+ total: manifestChecks.length,
100
+ ok: manifestChecks.filter((m) => m.status === 'ok').length,
101
+ missing: manifestChecks.filter((m) => m.status === 'missing').length,
102
+ warn: manifestChecks.filter((m) => m.status === 'warn').length,
103
+ results: manifestChecks
104
+ };
105
+ }
106
+ ui.render(summary, { format: 'json' });
107
+ const manifestMissing = manifestChecks.filter((m) => m.status === 'missing').length;
108
+ if (results.filter((r) => r.status === 'missing').length > 0 || manifestMissing > 0) {
109
+ process.exit(1);
110
+ }
111
+ return;
112
+ }
113
+
114
+ const grouped = groupRequirementsByApp(requirements);
115
+ const okCount = results.filter((r) => r.status === 'ok').length;
116
+ const missingCount = results.filter((r) => r.status === 'missing').length;
117
+ const warnCount = results.filter((r) => r.status === 'warn').length;
118
+
119
+ ui.log(`\nSecrets check for environment: ${envName} (${envTier})\n`);
120
+
121
+ for (const [appId, reqs] of grouped) {
122
+ ui.log(` ${appId}`);
123
+ for (const req of reqs) {
124
+ const result = results.find((r) => r.key === req.key && r.appId === req.appId);
125
+ if (!result) continue;
126
+ const icon = result.status === 'ok' ? '+' : result.status === 'missing' ? 'x' : '?';
127
+ const label = result.required ? 'required' : 'optional';
128
+ const src = result.source ? ` (${result.source})` : '';
129
+ ui.log(` [${icon}] ${result.key.padEnd(40)} ${label.padEnd(10)} ${result.status}${src}`);
130
+ }
131
+ ui.log('');
132
+ }
133
+
134
+ ui.log(` Summary: ${okCount} ok, ${missingCount} missing, ${warnCount} optional not set`);
135
+ if (missingCount > 0) {
136
+ ui.log(` Run 'vibes secrets set <KEY>' to add missing secrets`);
137
+ }
138
+ ui.log('');
139
+
140
+ let manifestMissing = 0;
141
+ if (options.manifest && manifestChecks.length > 0) {
142
+ ui.log(` Canonical manifest cross-check (${manifestChecks.length} entries)`);
143
+ for (const m of manifestChecks) {
144
+ const icon = m.status === 'ok' ? '+' : m.status === 'missing' ? 'x' : '?';
145
+ const label = m.required ? 'required' : 'optional';
146
+ const where = m.storedIn ? ` (${m.storedIn})` : ` <- ${m.source}`;
147
+ ui.log(` [${icon}] ${m.key.padEnd(40)} ${label.padEnd(10)} ${m.status}${where}`);
148
+ }
149
+ const mOk = manifestChecks.filter((m) => m.status === 'ok').length;
150
+ manifestMissing = manifestChecks.filter((m) => m.status === 'missing').length;
151
+ const mWarn = manifestChecks.filter((m) => m.status === 'warn').length;
152
+ ui.log(` Manifest summary: ${mOk} ok, ${manifestMissing} missing, ${mWarn} optional not set`);
153
+ if (manifestMissing > 0) {
154
+ ui.log(` Run 'vibes secrets import' to populate from local sources`);
155
+ }
156
+ ui.log('');
157
+ }
158
+
159
+ if (missingCount > 0 || manifestMissing > 0) {
160
+ process.exit(1);
161
+ }
162
+ }
163
+ };
@@ -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.export',
6
+ name: 'export',
7
+ description: 'Export secrets from a local backend to an env-formatted file (audit-logged)',
8
+ group: 'secrets',
9
+ options: [
10
+ { flags: '--environment <name>', description: 'Source environment (default: current)' },
11
+ { flags: '--from <backend>', description: 'Source backend (default: highest-priority local)' },
12
+ { flags: '--out <path>', description: 'Output file path (required; created mode 0600)' },
13
+ { flags: '--manifest-only', description: 'Limit to keys declared in the canonical manifest' },
14
+ { flags: '--force', description: 'Skip the interactive bulk-reveal confirmation' }
15
+ ],
16
+ surfaces: ['cli'],
17
+ hardware: ['consumer'],
18
+ enabled: true,
19
+ order: 35
20
+ };
21
+
22
+ export default descriptor;
@@ -0,0 +1,139 @@
1
+ import { resolve, dirname } from 'node:path';
2
+ import { mkdir, writeFile, chmod, rename } from 'node:fs/promises';
3
+ import { getVibesRuntime } from '@vibesdotdev/runtime';
4
+ import type { UIContext } from '@vibesdotdev/cli/providers';
5
+ import type { AuditLogInput } from '@vibesdotdev/logging';
6
+ import type { SecretsStoreImplementation } from '../../kinds/store.interface';
7
+ import type { SecretsStoreDescriptor, EnvironmentTier } from '../../kinds/store.schema';
8
+ import { canonicalImportManifest } from '../../manifest/canonical';
9
+ import { resolveSecretsEnvironment } from '../shared/resolve-environment';
10
+
11
+ const LOCAL_BACKENDS = new Set(['env-file', 'encrypted-local']);
12
+
13
+ function findSourceBackend(
14
+ descriptors: SecretsStoreDescriptor[],
15
+ tier: EnvironmentTier,
16
+ backendId?: string
17
+ ): SecretsStoreDescriptor | undefined {
18
+ if (backendId) return descriptors.find((d) => d.id === backendId);
19
+ return descriptors
20
+ .filter((d) => d.tiers.includes(tier) && LOCAL_BACKENDS.has(d.backend))
21
+ .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))[0];
22
+ }
23
+
24
+ function serializeEnv(secrets: Record<string, string>): string {
25
+ const lines = Object.keys(secrets)
26
+ .sort()
27
+ .map((key) => {
28
+ const value = secrets[key] ?? '';
29
+ const needsQuote = value.includes(' ') || value.includes('#') || value.includes('"');
30
+ const escaped = needsQuote ? `"${value.replace(/"/g, '\\"')}"` : value;
31
+ return `${key}=${escaped}`;
32
+ });
33
+ return `# Generated by 'vibes secrets export' — DO NOT COMMIT\n${lines.join('\n')}\n`;
34
+ }
35
+
36
+ async function writeEnvFile(path: string, content: string): Promise<void> {
37
+ const absolute = resolve(process.cwd(), path);
38
+ await mkdir(dirname(absolute), { recursive: true });
39
+ const tmp = `${absolute}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
40
+ await writeFile(tmp, content, { encoding: 'utf-8', mode: 0o600 });
41
+ await rename(tmp, absolute);
42
+ await chmod(absolute, 0o600);
43
+ }
44
+
45
+ async function promptConfirm(prompt: string): Promise<boolean> {
46
+ process.stderr.write(prompt);
47
+ for await (const chunk of process.stdin) {
48
+ const answer = (chunk as Buffer).toString('utf-8').trim().toLowerCase();
49
+ return answer === 'y' || answer === 'yes';
50
+ }
51
+ return false;
52
+ }
53
+
54
+ function emitAudit(input: AuditLogInput): void {
55
+ process.stderr.write(`[AUDIT] ${JSON.stringify({
56
+ id: crypto.randomUUID(),
57
+ timestamp: new Date().toISOString(),
58
+ ...input
59
+ })}\n`);
60
+ }
61
+
62
+ export default {
63
+ async execute(_args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
64
+ const runtime = getVibesRuntime();
65
+ const ui = (await runtime.context('cli/ui')) as UIContext;
66
+ const options = opts as {
67
+ environment?: string;
68
+ from?: string;
69
+ out?: string;
70
+ manifestOnly?: boolean;
71
+ force?: boolean;
72
+ };
73
+
74
+ if (!options.out) {
75
+ ui.error('--out <path> is required. Export refuses to write secrets to stdout.');
76
+ process.exit(1);
77
+ }
78
+
79
+ const { name: envName, tier: envTier } = await resolveSecretsEnvironment(
80
+ ui,
81
+ options.environment
82
+ );
83
+
84
+ const descriptors = runtime.assets('secrets/store').descriptors() as SecretsStoreDescriptor[];
85
+ const source = findSourceBackend(descriptors, envTier, options.from);
86
+ if (!source) {
87
+ ui.error('No local secrets store found to export from.');
88
+ process.exit(1);
89
+ }
90
+
91
+ const impl = (await runtime
92
+ .query('secrets/store')
93
+ .withId(source.id)
94
+ .resolve()) as SecretsStoreImplementation;
95
+ let secrets = await impl.getAll(envName);
96
+
97
+ if (options.manifestOnly) {
98
+ const manifestKeys = new Set(canonicalImportManifest.secrets.map((s) => s.key));
99
+ secrets = Object.fromEntries(
100
+ Object.entries(secrets).filter(([k]) => manifestKeys.has(k))
101
+ );
102
+ }
103
+
104
+ const count = Object.keys(secrets).length;
105
+ if (count === 0) {
106
+ ui.info(`No secrets to export from [${source.id}] for ${envName}.`);
107
+ return;
108
+ }
109
+
110
+ if (!options.force) {
111
+ const confirmed = await promptConfirm(
112
+ `\n[EXPORT] About to write ${count} secret value(s) from [${source.id}] (${envName}) to ${options.out}.\nThis bulk reveal is logged to the audit trail.\nContinue? [y/N] `
113
+ );
114
+ if (!confirmed) {
115
+ ui.info('Export cancelled.');
116
+ return;
117
+ }
118
+ }
119
+
120
+ emitAudit({
121
+ category: 'data_access',
122
+ severity: 'high',
123
+ action: 'secrets.export',
124
+ actor: { id: 'cli', type: 'user', name: 'CLI user' },
125
+ target: { id: source.id, type: 'secrets-store', name: source.id },
126
+ outcome: 'success',
127
+ context: {
128
+ environment: envName,
129
+ backend: source.id,
130
+ count,
131
+ manifestOnly: options.manifestOnly === true,
132
+ outPath: options.out
133
+ }
134
+ });
135
+
136
+ await writeEnvFile(options.out, serializeEnv(secrets));
137
+ ui.success(`Exported ${count} secret(s) from [${source.id}] to ${options.out} (mode 0600)`);
138
+ }
139
+ };
@@ -0,0 +1,73 @@
1
+ const SECRET_PATTERNS: Array<{ pattern: RegExp; name: string }> = [
2
+ { pattern: /sk_live_[a-zA-Z0-9]{24,}/, name: 'Stripe live secret key (sk_live_)' },
3
+ { pattern: /pk_live_[a-zA-Z0-9]{24,}/, name: 'Stripe live public key (pk_live_)' },
4
+ { pattern: /do_[a-zA-Z0-9]{32,}/, name: 'DigitalOcean API key (do_)' },
5
+ { pattern: /AKIA[a-zA-Z0-9]{16,}/, name: 'AWS access key ID (AKIA)' },
6
+ { pattern: /ghp_[a-zA-Z0-9]{36}/, name: 'GitHub personal access token (ghp_)' },
7
+ { pattern: /gho_[a-zA-Z0-9]{36}/, name: 'GitHub OAuth token (gho_)' }
8
+ ];
9
+
10
+ export async function runSecretsPreCommitCheck(workspacePath: string): Promise<number> {
11
+ const { execSync } = await import('node:child_process');
12
+
13
+ let stagedFiles: string[];
14
+ try {
15
+ const output = execSync('git diff --cached --name-only --diff-filter=ACM', {
16
+ cwd: workspacePath,
17
+ encoding: 'utf-8'
18
+ });
19
+ stagedFiles = output.trim().split('\n').filter(Boolean);
20
+ } catch {
21
+ return 0;
22
+ }
23
+
24
+ if (!stagedFiles.length) return 0;
25
+
26
+ const violations: Array<{ file: string; name: string; line?: string }> = [];
27
+ const { readFileSync } = await import('node:fs');
28
+
29
+ for (const file of stagedFiles) {
30
+ const fullPath = `${workspacePath}/${file}`;
31
+
32
+ let content: string;
33
+ try {
34
+ content = readFileSync(fullPath, 'utf-8');
35
+ } catch {
36
+ continue;
37
+ }
38
+
39
+ const lines = content.split('\n');
40
+ for (let i = 0; i < lines.length; i++) {
41
+ const line = lines[i];
42
+ for (const { pattern, name } of SECRET_PATTERNS) {
43
+ if (pattern.test(line)) {
44
+ violations.push({
45
+ file,
46
+ name,
47
+ line: line.trim().substring(0, 80)
48
+ });
49
+ break;
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ if (violations.length > 0) {
56
+ process.stderr.write(`\n[SECRETS-001] ${violations.length} secret pattern(s) detected in staged files.\n\n`);
57
+ for (const v of violations) {
58
+ process.stderr.write(` BLOCKED ${v.file}\n`);
59
+ process.stderr.write(` Pattern: ${v.name}\n`);
60
+ if (v.line) {
61
+ process.stderr.write(` Line: ${v.line}\n`);
62
+ }
63
+ process.stderr.write('\n');
64
+ }
65
+ process.stderr.write('Commit blocked: production secrets must not be committed.\n');
66
+ process.stderr.write(' - Rotate any exposed keys immediately.\n');
67
+ process.stderr.write(' - Use `vibes secrets set` to store in a secret manager.\n');
68
+ process.stderr.write(' - Add to .gitignore if genuinely not a secret.\n\n');
69
+ return 1;
70
+ }
71
+
72
+ return 0;
73
+ }
@@ -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.import',
6
+ name: 'import',
7
+ description: 'Import secrets from local root env files into a backend, driven by the canonical manifest',
8
+ group: 'secrets',
9
+ options: [
10
+ { flags: '--environment <name>', description: 'Target environment (default: current)' },
11
+ { flags: '--to <backend>', description: 'Target backend (default: encrypted-local)' },
12
+ { flags: '--source <id>', description: 'Restrict to one source from the manifest' },
13
+ { flags: '--dry-run', description: 'Show what would be imported without writing' }
14
+ ],
15
+ surfaces: ['cli'],
16
+ hardware: ['consumer'],
17
+ enabled: true,
18
+ order: 30
19
+ };
20
+
21
+ export default descriptor;
@@ -0,0 +1,178 @@
1
+ import { resolve } from 'node:path';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { getVibesRuntime } from '@vibesdotdev/runtime';
4
+ import type { UIContext } from '@vibesdotdev/cli/providers';
5
+ import type { SecretsStoreImplementation } from '../../kinds/store.interface';
6
+ import type { SecretsStoreDescriptor } from '../../kinds/store.schema';
7
+ import { canonicalImportManifest } from '../../manifest/canonical';
8
+ import { resolveSecretsEnvironment } from '../shared/resolve-environment';
9
+
10
+ /**
11
+ * Tiny line-oriented .env parser. Returns a flat KEY → value map.
12
+ * Quoted values are unwrapped. Comments and blank lines are ignored.
13
+ * Missing files yield an empty map (not an error) — sources are bootstrap inputs.
14
+ */
15
+ async function readEnvFile(path: string): Promise<Record<string, string>> {
16
+ let content: string;
17
+ try {
18
+ content = await readFile(path, 'utf-8');
19
+ } catch {
20
+ return {};
21
+ }
22
+
23
+ const result: Record<string, string> = {};
24
+ for (const raw of content.split('\n')) {
25
+ const line = raw.trim();
26
+ if (line === '' || line.startsWith('#')) continue;
27
+ const eqIdx = raw.indexOf('=');
28
+ if (eqIdx === -1) continue;
29
+ const key = raw.slice(0, eqIdx).trim();
30
+ let value = raw.slice(eqIdx + 1).trim();
31
+ if (
32
+ (value.startsWith('"') && value.endsWith('"')) ||
33
+ (value.startsWith("'") && value.endsWith("'"))
34
+ ) {
35
+ value = value.slice(1, -1);
36
+ }
37
+ result[key] = value;
38
+ }
39
+ return result;
40
+ }
41
+
42
+ function pickValue(
43
+ envMap: Record<string, string>,
44
+ key: string,
45
+ aliases: string[]
46
+ ): string | undefined {
47
+ if (envMap[key] !== undefined && envMap[key] !== '') return envMap[key];
48
+ for (const alias of aliases) {
49
+ if (envMap[alias] !== undefined && envMap[alias] !== '') return envMap[alias];
50
+ }
51
+ return undefined;
52
+ }
53
+
54
+ export default {
55
+ async execute(_args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
56
+ const runtime = getVibesRuntime();
57
+ const ui = (await runtime.context('cli/ui')) as UIContext;
58
+ const options = opts as {
59
+ environment?: string;
60
+ to?: string;
61
+ source?: string;
62
+ dryRun?: boolean;
63
+ };
64
+
65
+ const { name: envName, tier: envTier } = await resolveSecretsEnvironment(
66
+ ui,
67
+ options.environment
68
+ );
69
+
70
+ const sourceFilter = options.source;
71
+ if (sourceFilter && !canonicalImportManifest.sources[sourceFilter]) {
72
+ ui.error(`Unknown source: ${sourceFilter}. Known: ${Object.keys(canonicalImportManifest.sources).join(', ')}`);
73
+ process.exit(1);
74
+ }
75
+
76
+ const repoRoot = process.cwd();
77
+ const sourceContents = new Map<string, Record<string, string>>();
78
+ const manualSources = new Set<string>();
79
+ for (const [id, src] of Object.entries(canonicalImportManifest.sources)) {
80
+ if (sourceFilter && id !== sourceFilter) continue;
81
+ if (!src.path) {
82
+ manualSources.add(id);
83
+ continue;
84
+ }
85
+ sourceContents.set(id, await readEnvFile(resolve(repoRoot, src.path)));
86
+ }
87
+
88
+ const found: Record<string, string> = {};
89
+ const missingRequired: string[] = [];
90
+ const missingOptional: string[] = [];
91
+ const manualEntries: { key: string; required: boolean }[] = [];
92
+
93
+ for (const entry of canonicalImportManifest.secrets) {
94
+ if (sourceFilter && entry.source !== sourceFilter) continue;
95
+ if (manualSources.has(entry.source)) {
96
+ manualEntries.push({ key: entry.key, required: entry.required });
97
+ continue;
98
+ }
99
+ const sourceMap = sourceContents.get(entry.source);
100
+ if (!sourceMap) {
101
+ if (entry.required) missingRequired.push(entry.key);
102
+ else missingOptional.push(entry.key);
103
+ continue;
104
+ }
105
+ const value = pickValue(sourceMap, entry.key, entry.aliases);
106
+ if (value === undefined) {
107
+ if (entry.required) missingRequired.push(entry.key);
108
+ else missingOptional.push(entry.key);
109
+ continue;
110
+ }
111
+ found[entry.key] = value;
112
+ }
113
+
114
+ const foundCount = Object.keys(found).length;
115
+
116
+ if (options.dryRun) {
117
+ ui.log(`\nDry run: would import ${foundCount} secret(s) for ${envName}\n`);
118
+ for (const key of Object.keys(found).sort()) ui.log(` + ${key}`);
119
+ if (missingRequired.length) {
120
+ ui.log(`\n ${missingRequired.length} required secret(s) not found:`);
121
+ for (const key of missingRequired) ui.log(` ! ${key}`);
122
+ }
123
+ if (missingOptional.length) {
124
+ ui.log(`\n ${missingOptional.length} optional secret(s) not found:`);
125
+ for (const key of missingOptional) ui.log(` ? ${key}`);
126
+ }
127
+ if (manualEntries.length) {
128
+ ui.log(`\n ${manualEntries.length} manual entry/entries (set via 'vibes secrets set'):`);
129
+ for (const { key, required } of manualEntries) {
130
+ ui.log(` m ${key} (${required ? 'required' : 'optional'})`);
131
+ }
132
+ }
133
+ ui.log('');
134
+ return;
135
+ }
136
+
137
+ if (foundCount === 0) {
138
+ ui.warn('No secrets found in any source file. Nothing to import.');
139
+ if (missingRequired.length > 0) process.exit(1);
140
+ return;
141
+ }
142
+
143
+ const descriptors = runtime.assets('secrets/store').descriptors() as SecretsStoreDescriptor[];
144
+ const targetId = options.to ?? 'encrypted-local';
145
+ const target = descriptors.find((d) => d.id === targetId)
146
+ ?? descriptors.filter((d) => d.tiers.includes(envTier) && d.backend === 'encrypted-local')[0];
147
+
148
+ if (!target) {
149
+ ui.error(`No backend available with id [${targetId}] for tier ${envTier}`);
150
+ process.exit(1);
151
+ }
152
+
153
+ const impl = (await runtime
154
+ .query('secrets/store')
155
+ .withId(target.id)
156
+ .resolve()) as SecretsStoreImplementation;
157
+ await impl.setAll(envName, found);
158
+
159
+ ui.success(`Imported ${foundCount} secret(s) into [${target.id}] for ${envName}`);
160
+
161
+ const missingManualRequired = manualEntries.filter((e) => e.required).map((e) => e.key);
162
+ if (manualEntries.length > 0) {
163
+ ui.info(
164
+ `${manualEntries.length} manual entry/entries — set via 'vibes secrets set <KEY>' (${
165
+ missingManualRequired.length
166
+ } required)`
167
+ );
168
+ }
169
+
170
+ if (missingRequired.length > 0) {
171
+ ui.warn(`${missingRequired.length} required secret(s) missing from sources: ${missingRequired.join(', ')}`);
172
+ process.exit(1);
173
+ }
174
+ if (missingOptional.length > 0) {
175
+ ui.info(`${missingOptional.length} optional secret(s) skipped: ${missingOptional.join(', ')}`);
176
+ }
177
+ }
178
+ };
@@ -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.list',
6
+ name: 'list',
7
+ description: 'List secrets for the current environment',
8
+ group: 'secrets',
9
+ options: [
10
+ { flags: '--environment <name>', description: 'Target environment (default: current)' },
11
+ { flags: '--backend <id>', description: 'Filter to a specific backend' },
12
+
13
+ { flags: '--json', description: 'Output as JSON' }
14
+ ],
15
+ surfaces: ['cli'],
16
+ hardware: ['consumer'],
17
+ enabled: true,
18
+ order: 10
19
+ };
20
+
21
+ export default descriptor;