@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.
- package/README.md +478 -0
- package/bin/goki-dev.js +452 -0
- package/bin/mcp-server.js +16 -0
- package/bin/secrets-cli.js +302 -0
- package/cli/ComposeOverrideGenerator.js +226 -0
- package/cli/ComposeParser.js +73 -0
- package/cli/ConfigGenerator.js +304 -0
- package/cli/ConfigManager.js +46 -0
- package/cli/DatabaseManager.js +94 -0
- package/cli/DevToolsChecker.js +21 -0
- package/cli/DevToolsDir.js +66 -0
- package/cli/DevToolsManager.js +451 -0
- package/cli/DockerManager.js +138 -0
- package/cli/FunctionManager.js +95 -0
- package/cli/HttpProxyRewriter.js +91 -0
- package/cli/Logger.js +10 -0
- package/cli/McpConfigManager.js +123 -0
- package/cli/NgrokManager.js +431 -0
- package/cli/ProjectCLI.js +2322 -0
- package/cli/PubSubManager.js +129 -0
- package/cli/SnapshotManager.js +88 -0
- package/cli/UiFormatter.js +292 -0
- package/cli/WebhookUrlRewriter.js +32 -0
- package/cli/secrets/BiometricAuth.js +125 -0
- package/cli/secrets/SecretInjector.js +47 -0
- package/cli/secrets/SecretsConfig.js +141 -0
- package/cli/secrets/SecretsDoctor.js +384 -0
- package/cli/secrets/SecretsManager.js +255 -0
- package/client/dist/client.d.ts +332 -0
- package/client/dist/client.js +507 -0
- package/client/dist/helpers.d.ts +62 -0
- package/client/dist/helpers.js +122 -0
- package/client/dist/index.d.ts +59 -0
- package/client/dist/index.js +78 -0
- package/client/dist/package.json +1 -0
- package/client/dist/types.d.ts +280 -0
- package/client/dist/types.js +7 -0
- package/config.development +46 -0
- package/config.test +18 -0
- package/guidelines/CodingStyleGuideline.md +148 -0
- package/guidelines/CommentingGuideline.md +10 -0
- package/guidelines/HttpApiImplementationGuideline.md +137 -0
- package/guidelines/NamingGuideline.md +182 -0
- package/package.json +138 -0
- package/patterns/api/[collectionName]/Controllers.md +62 -0
- package/patterns/api/[collectionName]/Logic.md +154 -0
- package/patterns/api/[collectionName]/Permissions.md +81 -0
- package/patterns/api/[collectionName]/Router.md +83 -0
- package/patterns/api/[collectionName]/Schemas.md +197 -0
- package/patterns/configs/Patterns.md +7 -0
- package/patterns/enums/Patterns.md +24 -0
- package/patterns/errorHandling/Patterns.md +185 -0
- package/patterns/testing/Patterns.md +232 -0
- package/src/Server.js +238 -0
- package/src/api/dashboard/Controllers.js +9 -0
- package/src/api/dashboard/Logic.js +76 -0
- package/src/api/dashboard/Router.js +11 -0
- package/src/api/dashboard/Schemas.js +47 -0
- package/src/api/data/Controllers.js +26 -0
- package/src/api/data/Logic.js +188 -0
- package/src/api/data/Router.js +16 -0
- package/src/api/docker/Controllers.js +33 -0
- package/src/api/docker/Logic.js +268 -0
- package/src/api/docker/Router.js +15 -0
- package/src/api/docker/Schemas.js +80 -0
- package/src/api/docs/Controllers.js +15 -0
- package/src/api/docs/Logic.js +85 -0
- package/src/api/docs/Router.js +12 -0
- package/src/api/export/Controllers.js +30 -0
- package/src/api/export/Logic.js +143 -0
- package/src/api/export/Router.js +18 -0
- package/src/api/export/Schemas.js +104 -0
- package/src/api/firestore/Controllers.js +152 -0
- package/src/api/firestore/Logic.js +474 -0
- package/src/api/firestore/Router.js +23 -0
- package/src/api/functions/Controllers.js +261 -0
- package/src/api/functions/Logic.js +710 -0
- package/src/api/functions/Router.js +50 -0
- package/src/api/functions/Schemas.js +193 -0
- package/src/api/gateway/Controllers.js +72 -0
- package/src/api/gateway/Logic.js +74 -0
- package/src/api/gateway/Router.js +10 -0
- package/src/api/gateway/Schemas.js +19 -0
- package/src/api/health/Controllers.js +14 -0
- package/src/api/health/Logic.js +24 -0
- package/src/api/health/Router.js +12 -0
- package/src/api/httpTraffic/Controllers.js +29 -0
- package/src/api/httpTraffic/Logic.js +33 -0
- package/src/api/httpTraffic/Router.js +9 -0
- package/src/api/httpTraffic/Schemas.js +23 -0
- package/src/api/logging/Controllers.js +80 -0
- package/src/api/logging/Logic.js +461 -0
- package/src/api/logging/Router.js +24 -0
- package/src/api/logging/Schemas.js +43 -0
- package/src/api/mqtt/Controllers.js +17 -0
- package/src/api/mqtt/Logic.js +66 -0
- package/src/api/mqtt/Router.js +12 -0
- package/src/api/postgres/Controllers.js +97 -0
- package/src/api/postgres/Logic.js +221 -0
- package/src/api/postgres/Router.js +21 -0
- package/src/api/pubsub/Controllers.js +236 -0
- package/src/api/pubsub/Logic.js +732 -0
- package/src/api/pubsub/Router.js +41 -0
- package/src/api/pubsub/Schemas.js +355 -0
- package/src/api/redis/Controllers.js +63 -0
- package/src/api/redis/Logic.js +239 -0
- package/src/api/redis/Router.js +21 -0
- package/src/api/scheduler/Controllers.js +27 -0
- package/src/api/scheduler/Logic.js +49 -0
- package/src/api/scheduler/Router.js +16 -0
- package/src/api/services/Controllers.js +26 -0
- package/src/api/services/Logic.js +205 -0
- package/src/api/services/Router.js +14 -0
- package/src/api/services/Schemas.js +66 -0
- package/src/api/snapshots/Controllers.js +37 -0
- package/src/api/snapshots/Logic.js +797 -0
- package/src/api/snapshots/Router.js +15 -0
- package/src/api/snapshots/Schemas.js +23 -0
- package/src/api/webhooks/Controllers.js +49 -0
- package/src/api/webhooks/Logic.js +137 -0
- package/src/api/webhooks/Router.js +12 -0
- package/src/api/webhooks/Schemas.js +31 -0
- package/src/configs/Application.js +147 -0
- package/src/configs/Default.js +13 -0
- package/src/consumers/BlackboxLogsConsumer.js +235 -0
- package/src/consumers/DockerLogsConsumer.js +687 -0
- package/src/db/Tables.js +66 -0
- package/src/db/schemas/firestore.js +18 -0
- package/src/db/schemas/functions.js +65 -0
- package/src/db/schemas/httpTraffic.js +43 -0
- package/src/db/schemas/logging.js +74 -0
- package/src/db/schemas/migrations.js +64 -0
- package/src/db/schemas/mqtt.js +56 -0
- package/src/db/schemas/pubsub.js +90 -0
- package/src/db/schemas/pubsubRegistry.js +22 -0
- package/src/db/schemas/webhooks.js +28 -0
- package/src/emulation/awsiot/Controllers.js +91 -0
- package/src/emulation/awsiot/Logic.js +70 -0
- package/src/emulation/awsiot/Router.js +19 -0
- package/src/emulation/awsiot/Server.js +100 -0
- package/src/emulation/firestore/Server.js +136 -0
- package/src/emulation/logging/Controllers.js +212 -0
- package/src/emulation/logging/Logic.js +416 -0
- package/src/emulation/logging/Router.js +36 -0
- package/src/emulation/logging/Schemas.js +82 -0
- package/src/emulation/logging/Server.js +108 -0
- package/src/emulation/pubsub/Controllers.js +279 -0
- package/src/emulation/pubsub/DefaultTopics.js +162 -0
- package/src/emulation/pubsub/Logic.js +427 -0
- package/src/emulation/pubsub/README.md +309 -0
- package/src/emulation/pubsub/Router.js +33 -0
- package/src/emulation/pubsub/Server.js +104 -0
- package/src/emulation/pubsub/ShadowPoller.js +276 -0
- package/src/emulation/pubsub/ShadowSubscriptionManager.js +199 -0
- package/src/enums/ContainerNames.js +106 -0
- package/src/enums/ErrorReason.js +28 -0
- package/src/enums/FunctionStatuses.js +15 -0
- package/src/enums/FunctionTriggerTypes.js +15 -0
- package/src/enums/GatewayState.js +7 -0
- package/src/enums/ServiceNames.js +68 -0
- package/src/jobs/DatabaseMaintenance.js +184 -0
- package/src/jobs/MessageHistoryCleanup.js +152 -0
- package/src/mcp/ApiClient.js +25 -0
- package/src/mcp/Server.js +52 -0
- package/src/mcp/prompts/debugging.js +104 -0
- package/src/mcp/resources/platform.js +118 -0
- package/src/mcp/tools/data.js +84 -0
- package/src/mcp/tools/docker.js +166 -0
- package/src/mcp/tools/firestore.js +162 -0
- package/src/mcp/tools/functions.js +380 -0
- package/src/mcp/tools/httpTraffic.js +69 -0
- package/src/mcp/tools/logging.js +174 -0
- package/src/mcp/tools/mqtt.js +37 -0
- package/src/mcp/tools/postgres.js +130 -0
- package/src/mcp/tools/pubsub.js +316 -0
- package/src/mcp/tools/redis.js +146 -0
- package/src/mcp/tools/services.js +169 -0
- package/src/mcp/tools/snapshots.js +88 -0
- package/src/mcp/tools/webhooks.js +115 -0
- package/src/middleware/DevProxy.js +67 -0
- package/src/middleware/ErrorCatcher.js +35 -0
- package/src/middleware/HttpProxy.js +215 -0
- package/src/middleware/Reply.js +24 -0
- package/src/middleware/TraceId.js +9 -0
- package/src/middleware/WebhookProxy.js +234 -0
- package/src/protocols/mqtt/Broker.js +92 -0
- package/src/protocols/mqtt/Handlers.js +175 -0
- package/src/protocols/mqtt/PubSubBridge.js +162 -0
- package/src/protocols/mqtt/Server.js +116 -0
- package/src/runtime/FunctionRunner.js +179 -0
- package/src/services/AppGatewayService.js +582 -0
- package/src/singletons/FirestoreBroadcaster.js +367 -0
- package/src/singletons/FunctionTriggerDispatcher.js +456 -0
- package/src/singletons/FunctionsService.js +418 -0
- package/src/singletons/HttpProxy.js +224 -0
- package/src/singletons/LogBroadcaster.js +159 -0
- package/src/singletons/Logger.js +49 -0
- package/src/singletons/MemoryJsonStore.js +175 -0
- package/src/singletons/MessageBroadcaster.js +190 -0
- package/src/singletons/PostgresBroadcaster.js +367 -0
- package/src/singletons/PostgresClient.js +180 -0
- package/src/singletons/RedisClient.js +184 -0
- package/src/singletons/SqliteStore.js +480 -0
- package/src/singletons/TickService.js +151 -0
- package/src/singletons/WebhookProxy.js +223 -0
package/bin/goki-dev.js
ADDED
|
@@ -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
|
+
})
|