@gokiteam/goki-dev 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/README.md +478 -0
  2. package/bin/goki-dev.js +452 -0
  3. package/bin/mcp-server.js +16 -0
  4. package/bin/secrets-cli.js +302 -0
  5. package/cli/ComposeOverrideGenerator.js +226 -0
  6. package/cli/ComposeParser.js +73 -0
  7. package/cli/ConfigGenerator.js +304 -0
  8. package/cli/ConfigManager.js +46 -0
  9. package/cli/DatabaseManager.js +94 -0
  10. package/cli/DevToolsChecker.js +21 -0
  11. package/cli/DevToolsDir.js +66 -0
  12. package/cli/DevToolsManager.js +451 -0
  13. package/cli/DockerManager.js +138 -0
  14. package/cli/FunctionManager.js +95 -0
  15. package/cli/HttpProxyRewriter.js +91 -0
  16. package/cli/Logger.js +10 -0
  17. package/cli/McpConfigManager.js +123 -0
  18. package/cli/NgrokManager.js +431 -0
  19. package/cli/ProjectCLI.js +2322 -0
  20. package/cli/PubSubManager.js +129 -0
  21. package/cli/SnapshotManager.js +88 -0
  22. package/cli/UiFormatter.js +292 -0
  23. package/cli/WebhookUrlRewriter.js +32 -0
  24. package/cli/secrets/BiometricAuth.js +125 -0
  25. package/cli/secrets/SecretInjector.js +47 -0
  26. package/cli/secrets/SecretsConfig.js +141 -0
  27. package/cli/secrets/SecretsDoctor.js +384 -0
  28. package/cli/secrets/SecretsManager.js +255 -0
  29. package/client/dist/client.d.ts +332 -0
  30. package/client/dist/client.js +507 -0
  31. package/client/dist/helpers.d.ts +62 -0
  32. package/client/dist/helpers.js +122 -0
  33. package/client/dist/index.d.ts +59 -0
  34. package/client/dist/index.js +78 -0
  35. package/client/dist/package.json +1 -0
  36. package/client/dist/types.d.ts +280 -0
  37. package/client/dist/types.js +7 -0
  38. package/config.development +46 -0
  39. package/config.test +18 -0
  40. package/guidelines/CodingStyleGuideline.md +148 -0
  41. package/guidelines/CommentingGuideline.md +10 -0
  42. package/guidelines/HttpApiImplementationGuideline.md +137 -0
  43. package/guidelines/NamingGuideline.md +182 -0
  44. package/package.json +138 -0
  45. package/patterns/api/[collectionName]/Controllers.md +62 -0
  46. package/patterns/api/[collectionName]/Logic.md +154 -0
  47. package/patterns/api/[collectionName]/Permissions.md +81 -0
  48. package/patterns/api/[collectionName]/Router.md +83 -0
  49. package/patterns/api/[collectionName]/Schemas.md +197 -0
  50. package/patterns/configs/Patterns.md +7 -0
  51. package/patterns/enums/Patterns.md +24 -0
  52. package/patterns/errorHandling/Patterns.md +185 -0
  53. package/patterns/testing/Patterns.md +232 -0
  54. package/src/Server.js +238 -0
  55. package/src/api/dashboard/Controllers.js +9 -0
  56. package/src/api/dashboard/Logic.js +76 -0
  57. package/src/api/dashboard/Router.js +11 -0
  58. package/src/api/dashboard/Schemas.js +47 -0
  59. package/src/api/data/Controllers.js +26 -0
  60. package/src/api/data/Logic.js +188 -0
  61. package/src/api/data/Router.js +16 -0
  62. package/src/api/docker/Controllers.js +33 -0
  63. package/src/api/docker/Logic.js +268 -0
  64. package/src/api/docker/Router.js +15 -0
  65. package/src/api/docker/Schemas.js +80 -0
  66. package/src/api/docs/Controllers.js +15 -0
  67. package/src/api/docs/Logic.js +85 -0
  68. package/src/api/docs/Router.js +12 -0
  69. package/src/api/export/Controllers.js +30 -0
  70. package/src/api/export/Logic.js +143 -0
  71. package/src/api/export/Router.js +18 -0
  72. package/src/api/export/Schemas.js +104 -0
  73. package/src/api/firestore/Controllers.js +152 -0
  74. package/src/api/firestore/Logic.js +474 -0
  75. package/src/api/firestore/Router.js +23 -0
  76. package/src/api/functions/Controllers.js +261 -0
  77. package/src/api/functions/Logic.js +710 -0
  78. package/src/api/functions/Router.js +50 -0
  79. package/src/api/functions/Schemas.js +193 -0
  80. package/src/api/gateway/Controllers.js +72 -0
  81. package/src/api/gateway/Logic.js +74 -0
  82. package/src/api/gateway/Router.js +10 -0
  83. package/src/api/gateway/Schemas.js +19 -0
  84. package/src/api/health/Controllers.js +14 -0
  85. package/src/api/health/Logic.js +24 -0
  86. package/src/api/health/Router.js +12 -0
  87. package/src/api/httpTraffic/Controllers.js +29 -0
  88. package/src/api/httpTraffic/Logic.js +33 -0
  89. package/src/api/httpTraffic/Router.js +9 -0
  90. package/src/api/httpTraffic/Schemas.js +23 -0
  91. package/src/api/logging/Controllers.js +80 -0
  92. package/src/api/logging/Logic.js +461 -0
  93. package/src/api/logging/Router.js +24 -0
  94. package/src/api/logging/Schemas.js +43 -0
  95. package/src/api/mqtt/Controllers.js +17 -0
  96. package/src/api/mqtt/Logic.js +66 -0
  97. package/src/api/mqtt/Router.js +12 -0
  98. package/src/api/postgres/Controllers.js +97 -0
  99. package/src/api/postgres/Logic.js +221 -0
  100. package/src/api/postgres/Router.js +21 -0
  101. package/src/api/pubsub/Controllers.js +236 -0
  102. package/src/api/pubsub/Logic.js +732 -0
  103. package/src/api/pubsub/Router.js +41 -0
  104. package/src/api/pubsub/Schemas.js +355 -0
  105. package/src/api/redis/Controllers.js +63 -0
  106. package/src/api/redis/Logic.js +239 -0
  107. package/src/api/redis/Router.js +21 -0
  108. package/src/api/scheduler/Controllers.js +27 -0
  109. package/src/api/scheduler/Logic.js +49 -0
  110. package/src/api/scheduler/Router.js +16 -0
  111. package/src/api/services/Controllers.js +26 -0
  112. package/src/api/services/Logic.js +205 -0
  113. package/src/api/services/Router.js +14 -0
  114. package/src/api/services/Schemas.js +66 -0
  115. package/src/api/snapshots/Controllers.js +37 -0
  116. package/src/api/snapshots/Logic.js +797 -0
  117. package/src/api/snapshots/Router.js +15 -0
  118. package/src/api/snapshots/Schemas.js +23 -0
  119. package/src/api/webhooks/Controllers.js +49 -0
  120. package/src/api/webhooks/Logic.js +137 -0
  121. package/src/api/webhooks/Router.js +12 -0
  122. package/src/api/webhooks/Schemas.js +31 -0
  123. package/src/configs/Application.js +147 -0
  124. package/src/configs/Default.js +13 -0
  125. package/src/consumers/BlackboxLogsConsumer.js +235 -0
  126. package/src/consumers/DockerLogsConsumer.js +687 -0
  127. package/src/db/Tables.js +66 -0
  128. package/src/db/schemas/firestore.js +18 -0
  129. package/src/db/schemas/functions.js +65 -0
  130. package/src/db/schemas/httpTraffic.js +43 -0
  131. package/src/db/schemas/logging.js +74 -0
  132. package/src/db/schemas/migrations.js +64 -0
  133. package/src/db/schemas/mqtt.js +56 -0
  134. package/src/db/schemas/pubsub.js +90 -0
  135. package/src/db/schemas/pubsubRegistry.js +22 -0
  136. package/src/db/schemas/webhooks.js +28 -0
  137. package/src/emulation/awsiot/Controllers.js +91 -0
  138. package/src/emulation/awsiot/Logic.js +70 -0
  139. package/src/emulation/awsiot/Router.js +19 -0
  140. package/src/emulation/awsiot/Server.js +100 -0
  141. package/src/emulation/firestore/Server.js +136 -0
  142. package/src/emulation/logging/Controllers.js +212 -0
  143. package/src/emulation/logging/Logic.js +416 -0
  144. package/src/emulation/logging/Router.js +36 -0
  145. package/src/emulation/logging/Schemas.js +82 -0
  146. package/src/emulation/logging/Server.js +108 -0
  147. package/src/emulation/pubsub/Controllers.js +279 -0
  148. package/src/emulation/pubsub/DefaultTopics.js +162 -0
  149. package/src/emulation/pubsub/Logic.js +427 -0
  150. package/src/emulation/pubsub/README.md +309 -0
  151. package/src/emulation/pubsub/Router.js +33 -0
  152. package/src/emulation/pubsub/Server.js +104 -0
  153. package/src/emulation/pubsub/ShadowPoller.js +276 -0
  154. package/src/emulation/pubsub/ShadowSubscriptionManager.js +199 -0
  155. package/src/enums/ContainerNames.js +106 -0
  156. package/src/enums/ErrorReason.js +28 -0
  157. package/src/enums/FunctionStatuses.js +15 -0
  158. package/src/enums/FunctionTriggerTypes.js +15 -0
  159. package/src/enums/GatewayState.js +7 -0
  160. package/src/enums/ServiceNames.js +68 -0
  161. package/src/jobs/DatabaseMaintenance.js +184 -0
  162. package/src/jobs/MessageHistoryCleanup.js +152 -0
  163. package/src/mcp/ApiClient.js +25 -0
  164. package/src/mcp/Server.js +52 -0
  165. package/src/mcp/prompts/debugging.js +104 -0
  166. package/src/mcp/resources/platform.js +118 -0
  167. package/src/mcp/tools/data.js +84 -0
  168. package/src/mcp/tools/docker.js +166 -0
  169. package/src/mcp/tools/firestore.js +162 -0
  170. package/src/mcp/tools/functions.js +380 -0
  171. package/src/mcp/tools/httpTraffic.js +69 -0
  172. package/src/mcp/tools/logging.js +174 -0
  173. package/src/mcp/tools/mqtt.js +37 -0
  174. package/src/mcp/tools/postgres.js +130 -0
  175. package/src/mcp/tools/pubsub.js +316 -0
  176. package/src/mcp/tools/redis.js +146 -0
  177. package/src/mcp/tools/services.js +169 -0
  178. package/src/mcp/tools/snapshots.js +88 -0
  179. package/src/mcp/tools/webhooks.js +115 -0
  180. package/src/middleware/DevProxy.js +67 -0
  181. package/src/middleware/ErrorCatcher.js +35 -0
  182. package/src/middleware/HttpProxy.js +215 -0
  183. package/src/middleware/Reply.js +24 -0
  184. package/src/middleware/TraceId.js +9 -0
  185. package/src/middleware/WebhookProxy.js +234 -0
  186. package/src/protocols/mqtt/Broker.js +92 -0
  187. package/src/protocols/mqtt/Handlers.js +175 -0
  188. package/src/protocols/mqtt/PubSubBridge.js +162 -0
  189. package/src/protocols/mqtt/Server.js +116 -0
  190. package/src/runtime/FunctionRunner.js +179 -0
  191. package/src/services/AppGatewayService.js +582 -0
  192. package/src/singletons/FirestoreBroadcaster.js +367 -0
  193. package/src/singletons/FunctionTriggerDispatcher.js +456 -0
  194. package/src/singletons/FunctionsService.js +418 -0
  195. package/src/singletons/HttpProxy.js +224 -0
  196. package/src/singletons/LogBroadcaster.js +159 -0
  197. package/src/singletons/Logger.js +49 -0
  198. package/src/singletons/MemoryJsonStore.js +175 -0
  199. package/src/singletons/MessageBroadcaster.js +190 -0
  200. package/src/singletons/PostgresBroadcaster.js +367 -0
  201. package/src/singletons/PostgresClient.js +180 -0
  202. package/src/singletons/RedisClient.js +184 -0
  203. package/src/singletons/SqliteStore.js +480 -0
  204. package/src/singletons/TickService.js +151 -0
  205. package/src/singletons/WebhookProxy.js +223 -0
@@ -0,0 +1,452 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander'
3
+ import { ProjectCLI } from '../cli/ProjectCLI.js'
4
+ import { NgrokManager } from '../cli/NgrokManager.js'
5
+ import { Logger } from '../cli/Logger.js'
6
+ import { offerConfigGeneration } from '../cli/ConfigGenerator.js'
7
+ import { configExists, loadConfig as loadDevToolsConfig } from '../cli/DevToolsDir.js'
8
+
9
+ async function loadConfig () {
10
+ const projectDir = process.cwd()
11
+ if (configExists(projectDir)) {
12
+ const config = await loadDevToolsConfig(projectDir)
13
+ if (config) return config
14
+ }
15
+ // Config not found — offer to generate
16
+ const generated = await offerConfigGeneration()
17
+ if (generated) {
18
+ const config = await loadDevToolsConfig(projectDir)
19
+ if (config) return config
20
+ }
21
+ Logger.error('No .dev-tools/config.js found in current directory')
22
+ console.log('\nExpected structure:')
23
+ console.log(' .dev-tools/config.js - Project configuration')
24
+ console.log('\nRun "goki-dev start" to auto-generate configuration')
25
+ process.exit(1)
26
+ }
27
+
28
+ const program = new Command()
29
+
30
+ program
31
+ .name('goki-dev')
32
+ .description('Goki microservices development CLI')
33
+ .version('1.0.0')
34
+
35
+ // Default action when no command provided - show interactive menu
36
+ program
37
+ .action(async () => {
38
+ const config = await loadConfig()
39
+ const cli = new ProjectCLI(config)
40
+ await cli.menu()
41
+ })
42
+
43
+ program
44
+ .command('menu')
45
+ .description('Show interactive menu')
46
+ .action(async () => {
47
+ const config = await loadConfig()
48
+ const cli = new ProjectCLI(config)
49
+ await cli.menu()
50
+ })
51
+
52
+ program
53
+ .command('start')
54
+ .description('Start application')
55
+ .action(async () => {
56
+ const config = await loadConfig()
57
+ const cli = new ProjectCLI(config)
58
+ await cli.start()
59
+ })
60
+
61
+ program
62
+ .command('stop')
63
+ .description('Stop application')
64
+ .action(async () => {
65
+ const config = await loadConfig()
66
+ const cli = new ProjectCLI(config)
67
+ await cli.stop()
68
+ })
69
+
70
+ program
71
+ .command('status')
72
+ .description('Show service status')
73
+ .action(async () => {
74
+ const config = await loadConfig()
75
+ const cli = new ProjectCLI(config)
76
+ await cli.status()
77
+ })
78
+
79
+ program
80
+ .command('logs')
81
+ .option('-f, --follow', 'Follow log output')
82
+ .description('View application logs')
83
+ .action(async (options) => {
84
+ const config = await loadConfig()
85
+ const cli = new ProjectCLI(config)
86
+ await cli.logs(options)
87
+ })
88
+
89
+ program
90
+ .command('setup')
91
+ .description('Complete local setup (Docker, dev-tools, DB, Pub/Sub, app)')
92
+ .action(async () => {
93
+ const config = await loadConfig()
94
+ const cli = new ProjectCLI(config)
95
+ await cli.setup()
96
+ })
97
+
98
+ program
99
+ .command('shutdown')
100
+ .description('Stop services with options')
101
+ .option('--app', 'Stop app only')
102
+ .option('--all', 'Stop app + dev-tools')
103
+ .option('--reset', 'Clear database data')
104
+ .option('--destroy', 'Full cleanup (stop all + drop database)')
105
+ .action(async (options) => {
106
+ const config = await loadConfig()
107
+ const cli = new ProjectCLI(config)
108
+ await cli.shutdown(options)
109
+ })
110
+
111
+ const db = program.command('db').description('Database management')
112
+
113
+ db.command('init')
114
+ .description('Initialize database schema')
115
+ .action(async () => {
116
+ const config = await loadConfig()
117
+ const cli = new ProjectCLI(config)
118
+ await cli.dbInit()
119
+ })
120
+
121
+ db.command('reset')
122
+ .description('Drop and recreate database (DESTRUCTIVE)')
123
+ .action(async () => {
124
+ const config = await loadConfig()
125
+ const cli = new ProjectCLI(config)
126
+ await cli.dbReset()
127
+ })
128
+
129
+ db.command('clear')
130
+ .description('Clear all data, keep schema')
131
+ .action(async () => {
132
+ const config = await loadConfig()
133
+ const cli = new ProjectCLI(config)
134
+ await cli.dbClear()
135
+ })
136
+
137
+ const pubsub = program.command('pubsub').description('Pub/Sub management')
138
+
139
+ pubsub.command('init')
140
+ .description('Initialize Pub/Sub topics and subscriptions')
141
+ .action(async () => {
142
+ const config = await loadConfig()
143
+ const cli = new ProjectCLI(config)
144
+ await cli.pubsubInit()
145
+ })
146
+
147
+ const ngrok = program.command('ngrok').description('ngrok tunnel management')
148
+
149
+ ngrok.command('status')
150
+ .description('Show ngrok tunnel status')
151
+ .action(async () => {
152
+ const status = await NgrokManager.getStatus()
153
+ if (status.running) {
154
+ console.log('\n ngrok tunnel is running')
155
+ console.log(` URL: ${status.url}`)
156
+ if (status.domain) console.log(` Domain: ${status.domain}`)
157
+ if (status.pid) console.log(` PID: ${status.pid}`)
158
+ if (status.port) console.log(` Port: ${status.port}`)
159
+ if (status.uptime) console.log(` Uptime: ${status.uptime}`)
160
+ if (status.startedBy) console.log(` Started: by ${status.startedBy}`)
161
+ console.log('')
162
+ } else {
163
+ console.log('\n ngrok tunnel is not running\n')
164
+ }
165
+ })
166
+
167
+ ngrok.command('stop')
168
+ .description('Stop ngrok tunnel')
169
+ .action(async () => {
170
+ const result = await NgrokManager.stop()
171
+ if (result.stopped) {
172
+ const pidInfo = result.pid ? ` (PID: ${result.pid})` : ''
173
+ console.log(`\n ngrok tunnel stopped${pidInfo}\n`)
174
+ } else {
175
+ console.log('\n ngrok tunnel is not running\n')
176
+ }
177
+ })
178
+
179
+ ngrok.command('restart')
180
+ .description('Restart ngrok tunnel with saved domain')
181
+ .action(async () => {
182
+ const state = NgrokManager.readState()
183
+ const domain = state?.domain || await NgrokManager.loadDomain()
184
+ if (!domain) {
185
+ Logger.error('No saved ngrok domain found. Run "goki-dev start" first or set domain via prompt.')
186
+ process.exit(1)
187
+ }
188
+ const port = state?.port || 9000
189
+ console.log(`\n Restarting ngrok tunnel (${domain})...`)
190
+ await NgrokManager.stop()
191
+ try {
192
+ const { url, pid } = await NgrokManager.start(domain, port)
193
+ console.log(` ngrok tunnel started (PID: ${pid})`)
194
+ console.log(` URL: ${url}\n`)
195
+ } catch (error) {
196
+ Logger.error(`Failed to start ngrok: ${error.message}`)
197
+ process.exit(1)
198
+ }
199
+ })
200
+
201
+ const e2e = program.command('e2e').description('End-to-end testing with snapshot/restore')
202
+
203
+ e2e.command('run')
204
+ .description('Run e2e tests (auto snapshot/restore)')
205
+ .option('--keep-snapshot', 'Keep snapshot after tests (for debugging)')
206
+ .action(async (options) => {
207
+ const config = await loadConfig()
208
+ const cli = new ProjectCLI(config)
209
+ await cli.e2eRun(options)
210
+ })
211
+
212
+ e2e.command('snapshot')
213
+ .description('Create a snapshot of current development state')
214
+ .action(async () => {
215
+ const config = await loadConfig()
216
+ const cli = new ProjectCLI(config)
217
+ await cli.e2eSnapshot()
218
+ })
219
+
220
+ e2e.command('restore <snapshot-id>')
221
+ .description('Restore development environment from snapshot')
222
+ .action(async (snapshotId) => {
223
+ const config = await loadConfig()
224
+ const cli = new ProjectCLI(config)
225
+ await cli.e2eRestore(snapshotId)
226
+ })
227
+
228
+ e2e.command('list')
229
+ .description('List available snapshots')
230
+ .action(async () => {
231
+ const config = await loadConfig()
232
+ const cli = new ProjectCLI(config)
233
+ await cli.e2eList()
234
+ })
235
+
236
+ e2e.command('cleanup [snapshot-id]')
237
+ .description('Delete snapshot files (or all if no ID specified)')
238
+ .action(async (snapshotId) => {
239
+ const config = await loadConfig()
240
+ const cli = new ProjectCLI(config)
241
+ await cli.e2eCleanup(snapshotId)
242
+ })
243
+
244
+ // --- Secrets ---
245
+
246
+ const secrets = program.command('secrets').description('Credential management')
247
+
248
+ secrets.command('set <key> [value]')
249
+ .description('Set a secret (value prompted if omitted)')
250
+ .option('--global', 'Force global scope')
251
+ .option('--description <desc>', 'Optional metadata description')
252
+ .action(async (key, value, options) => {
253
+ const { SecretsManager } = await import('../cli/secrets/SecretsManager.js')
254
+ const chalk = (await import('chalk')).default
255
+ try {
256
+ if (!value) {
257
+ const inquirer = (await import('inquirer')).default
258
+ const answers = await inquirer.prompt([{
259
+ type: 'password',
260
+ name: 'value',
261
+ message: `Enter value for ${key}:`,
262
+ mask: '\u25CF'
263
+ }])
264
+ value = answers.value
265
+ }
266
+ const manager = await SecretsManager.create({ projectDir: process.cwd() })
267
+ if (manager.projectName) Logger.info(`project detected: ${manager.projectName}`)
268
+ await manager.unlock()
269
+ const scope = options.global ? 'global' : undefined
270
+ await manager.set(key, value, { scope, description: options.description })
271
+ const label = options.global ? 'global' : (manager.projectName ? `project: ${manager.projectName}` : 'global')
272
+ Logger.success(`set ${key} (${label})`)
273
+ } catch (error) {
274
+ Logger.error(error.message)
275
+ process.exit(1)
276
+ }
277
+ })
278
+
279
+ secrets.command('get <key>')
280
+ .description('Print secret value to stdout')
281
+ .option('--global', 'Force global scope')
282
+ .action(async (key, options) => {
283
+ const { SecretsManager } = await import('../cli/secrets/SecretsManager.js')
284
+ const chalk = (await import('chalk')).default
285
+ try {
286
+ const manager = await SecretsManager.create({ projectDir: process.cwd() })
287
+ await manager.unlock()
288
+ let value
289
+ if (options.global) {
290
+ value = manager.getGlobalSecrets()[key]
291
+ } else {
292
+ value = manager.get(key)
293
+ }
294
+ if (value === undefined) {
295
+ Logger.error(`secret not found: ${key}`)
296
+ process.exit(1)
297
+ }
298
+ process.stdout.write(value)
299
+ } catch (error) {
300
+ Logger.error(error.message)
301
+ process.exit(1)
302
+ }
303
+ })
304
+
305
+ secrets.command('delete <key>')
306
+ .description('Remove secret from keychain and index')
307
+ .option('--global', 'Force global scope')
308
+ .action(async (key, options) => {
309
+ const { SecretsManager } = await import('../cli/secrets/SecretsManager.js')
310
+ try {
311
+ const manager = await SecretsManager.create({ projectDir: process.cwd() })
312
+ if (manager.projectName) Logger.info(`project detected: ${manager.projectName}`)
313
+ await manager.unlock()
314
+ await manager.delete(key, { scope: options.global ? 'global' : undefined })
315
+ Logger.success(`deleted ${key}`)
316
+ } catch (error) {
317
+ Logger.error(error.message)
318
+ process.exit(1)
319
+ }
320
+ })
321
+
322
+ secrets.command('list')
323
+ .description('List keys and metadata (never values)')
324
+ .option('--format <format>', 'Output format: table or json', 'table')
325
+ .action(async (options) => {
326
+ const { SecretsManager } = await import('../cli/secrets/SecretsManager.js')
327
+ const chalk = (await import('chalk')).default
328
+ try {
329
+ const manager = await SecretsManager.create({ projectDir: process.cwd() })
330
+ if (manager.projectName) Logger.info(`project detected: ${manager.projectName}`)
331
+ const entries = manager.list()
332
+ if (options.format === 'json') {
333
+ console.log(JSON.stringify(entries, null, 2))
334
+ return
335
+ }
336
+ if (entries.length === 0) {
337
+ console.log(chalk.dim(' No secrets found'))
338
+ return
339
+ }
340
+ const globalEntries = entries.filter(e => e.scope === 'global')
341
+ const projectEntries = entries.filter(e => e.scope === 'project')
342
+ if (globalEntries.length > 0) {
343
+ console.log(chalk.bold('\n Global'))
344
+ for (const e of globalEntries) {
345
+ console.log(` ${e.key.padEnd(30)} ${(e.description || '-').padEnd(30)}`)
346
+ }
347
+ }
348
+ if (projectEntries.length > 0) {
349
+ console.log(chalk.bold(`\n Project: ${projectEntries[0].project || manager.projectName}`))
350
+ for (const e of projectEntries) {
351
+ console.log(` ${e.key.padEnd(30)} ${(e.description || '-').padEnd(30)}`)
352
+ }
353
+ }
354
+ console.log()
355
+ } catch (error) {
356
+ Logger.error(error.message)
357
+ process.exit(1)
358
+ }
359
+ })
360
+
361
+ secrets.command('setup')
362
+ .description('Interactive: prompt for missing secrets')
363
+ .action(async () => {
364
+ const { SecretsManager } = await import('../cli/secrets/SecretsManager.js')
365
+ const { SecretsConfig } = await import('../cli/secrets/SecretsConfig.js')
366
+ try {
367
+ const manager = await SecretsManager.create({ projectDir: process.cwd() })
368
+ await manager.unlock()
369
+ const config = new SecretsConfig(manager)
370
+ await config.setup()
371
+ } catch (error) {
372
+ Logger.error(error.message)
373
+ process.exit(1)
374
+ }
375
+ })
376
+
377
+ secrets.command('check')
378
+ .description('Non-interactive: verify required secrets exist')
379
+ .action(async () => {
380
+ const { SecretsManager } = await import('../cli/secrets/SecretsManager.js')
381
+ const { SecretsConfig } = await import('../cli/secrets/SecretsConfig.js')
382
+ try {
383
+ const manager = await SecretsManager.create({ projectDir: process.cwd() })
384
+ await manager.unlock()
385
+ const config = new SecretsConfig(manager)
386
+ const ok = await config.check()
387
+ if (!ok) process.exit(1)
388
+ } catch (error) {
389
+ Logger.error(error.message)
390
+ process.exit(1)
391
+ }
392
+ })
393
+
394
+ secrets.command('doctor')
395
+ .description('Scan for plaintext credentials')
396
+ .option('--fix', 'Interactively migrate plaintext credentials to keychain')
397
+ .action(async (options) => {
398
+ const { SecretsDoctor } = await import('../cli/secrets/SecretsDoctor.js')
399
+ try {
400
+ const doctor = new SecretsDoctor(process.cwd())
401
+ const findings = await doctor.scan()
402
+ if (!options.fix) {
403
+ doctor.report(findings)
404
+ if (findings.length > 0) process.exit(1)
405
+ return
406
+ }
407
+ doctor.report(findings)
408
+ if (findings.length === 0) return
409
+ const { SecretsManager } = await import('../cli/secrets/SecretsManager.js')
410
+ const manager = await SecretsManager.create({ projectDir: process.cwd() })
411
+ await manager.unlock()
412
+ await doctor.interactiveFix(findings, manager)
413
+ } catch (error) {
414
+ Logger.error(error.message)
415
+ process.exit(1)
416
+ }
417
+ })
418
+
419
+ secrets.command('config [setting] [value]')
420
+ .description('Show or update preferences')
421
+ .action(async (setting, value) => {
422
+ const { BiometricAuth } = await import('../cli/secrets/BiometricAuth.js')
423
+ const chalk = (await import('chalk')).default
424
+ try {
425
+ if (!setting) {
426
+ const prefs = BiometricAuth.loadPreferences()
427
+ console.log(chalk.bold('\n Preferences'))
428
+ console.log(` biometric: ${prefs.biometric !== false ? chalk.green('on') : chalk.red('off')}`)
429
+ console.log(` passphrase: ${prefs.passphraseHash ? chalk.green('set') : chalk.dim('not set')}`)
430
+ console.log()
431
+ return
432
+ }
433
+ if (setting === 'biometric') {
434
+ if (value !== 'on' && value !== 'off') {
435
+ Logger.error('usage: goki-dev secrets config biometric on|off')
436
+ process.exit(1)
437
+ }
438
+ const prefs = BiometricAuth.loadPreferences()
439
+ prefs.biometric = value === 'on'
440
+ BiometricAuth.savePreferences(prefs)
441
+ Logger.success(`biometric ${value}`)
442
+ return
443
+ }
444
+ Logger.error(`unknown setting: ${setting}`)
445
+ process.exit(1)
446
+ } catch (error) {
447
+ Logger.error(error.message)
448
+ process.exit(1)
449
+ }
450
+ })
451
+
452
+ program.parse()
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+
3
+ // MCP server entry point for Claude Code / Claude Desktop integration.
4
+ // Spawned as a subprocess using stdio transport.
5
+ //
6
+ // IMPORTANT: Never use console.log() in this process — stdout is reserved
7
+ // for JSON-RPC protocol messages. Use console.error() for diagnostics.
8
+
9
+ import { startMcpServer } from '../src/mcp/Server.js'
10
+
11
+ const DEV_TOOLS_URL = process.env.DEV_TOOLS_URL || 'http://localhost:9000'
12
+
13
+ startMcpServer({ devToolsUrl: DEV_TOOLS_URL }).catch(error => {
14
+ console.error('Fatal: MCP server failed to start', error)
15
+ process.exit(1)
16
+ })