@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,152 @@
1
+ // LRU cleanup job for message history
2
+ // Runs periodically to maintain size limits while keeping frequently viewed messages
3
+
4
+ import { SqliteStore } from '../singletons/SqliteStore.js'
5
+ import { Application } from '../configs/Application.js'
6
+ import { Logger } from '../singletons/Logger.js'
7
+ import { PUBSUB_MESSAGE_HISTORY } from '../db/Tables.js'
8
+
9
+ /**
10
+ * Run the LRU cleanup process
11
+ * Deletes least viewed messages when approaching size limit
12
+ * @returns {Object} - { cleaned: boolean, deletedCount?: number, reason?: string }
13
+ */
14
+ export const runMessageHistoryCleanup = () => {
15
+ const { maxMessages, minMessages, cleanupThreshold } = Application.messageHistory
16
+
17
+ // Get current count
18
+ const countResult = SqliteStore.db.prepare(`SELECT COUNT(*) as count FROM ${PUBSUB_MESSAGE_HISTORY}`).get()
19
+ const count = countResult.count
20
+
21
+ // Only cleanup if above threshold
22
+ if (count < maxMessages * cleanupThreshold) {
23
+ Logger.log({
24
+ level: 'debug',
25
+ message: 'Message history cleanup skipped - below threshold',
26
+ data: { currentCount: count, threshold: maxMessages * cleanupThreshold }
27
+ })
28
+ return { cleaned: false, reason: 'below threshold' }
29
+ }
30
+
31
+ // Calculate how many to delete (target 80% capacity)
32
+ const targetCount = Math.floor(maxMessages * 0.8)
33
+ const deleteCount = Math.max(0, count - targetCount)
34
+
35
+ // Safety check: ensure we don't delete below minimum
36
+ if (deleteCount === 0 || count - deleteCount < minMessages) {
37
+ Logger.log({
38
+ level: 'debug',
39
+ message: 'Message history cleanup skipped - minimum threshold',
40
+ data: { currentCount: count, minMessages, targetCount }
41
+ })
42
+ return { cleaned: false, reason: 'minimum threshold' }
43
+ }
44
+
45
+ // Delete least viewed messages (LRU strategy)
46
+ // Order by: view_count ASC (least viewed first), created_at ASC (oldest first)
47
+ const deleteSql = `
48
+ DELETE FROM ${PUBSUB_MESSAGE_HISTORY}
49
+ WHERE id IN (
50
+ SELECT id FROM ${PUBSUB_MESSAGE_HISTORY}
51
+ ORDER BY view_count ASC, created_at ASC
52
+ LIMIT ?
53
+ )
54
+ `
55
+
56
+ try {
57
+ const deleted = SqliteStore.db.prepare(deleteSql).run(deleteCount)
58
+
59
+ Logger.log({
60
+ level: 'info',
61
+ message: 'Message history LRU cleanup completed',
62
+ data: {
63
+ totalCount: count,
64
+ deletedCount: deleted.changes,
65
+ remainingCount: count - deleted.changes,
66
+ targetCount,
67
+ maxMessages,
68
+ minMessages
69
+ }
70
+ })
71
+
72
+ return { cleaned: true, deletedCount: deleted.changes }
73
+ } catch (error) {
74
+ Logger.log({
75
+ level: 'error',
76
+ message: 'Message history cleanup failed',
77
+ data: { error: error.message, count, deleteCount }
78
+ })
79
+ throw error
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Check if cleanup is needed and run if necessary
85
+ * Used for on-demand cleanup (e.g., after publish)
86
+ * @returns {Object} - Cleanup result
87
+ */
88
+ export const checkAndCleanup = () => {
89
+ const { maxMessages, cleanupThreshold } = Application.messageHistory
90
+
91
+ const countResult = SqliteStore.db.prepare(`SELECT COUNT(*) as count FROM ${PUBSUB_MESSAGE_HISTORY}`).get()
92
+ const count = countResult.count
93
+
94
+ if (count >= maxMessages * cleanupThreshold) {
95
+ return runMessageHistoryCleanup()
96
+ }
97
+
98
+ return { cleaned: false, reason: 'below threshold' }
99
+ }
100
+
101
+ /**
102
+ * Start the scheduled cleanup job
103
+ * @returns {NodeJS.Timer} - Interval timer
104
+ */
105
+ export const startCleanupJob = () => {
106
+ const { cleanupIntervalHours } = Application.messageHistory
107
+ const intervalMs = cleanupIntervalHours * 60 * 60 * 1000
108
+
109
+ Logger.log({
110
+ level: 'info',
111
+ message: 'Starting message history cleanup job',
112
+ data: { intervalHours: cleanupIntervalHours }
113
+ })
114
+
115
+ // Run immediately on start
116
+ try {
117
+ runMessageHistoryCleanup()
118
+ } catch (error) {
119
+ Logger.log({
120
+ level: 'error',
121
+ message: 'Initial cleanup failed',
122
+ data: { error: error.message }
123
+ })
124
+ }
125
+
126
+ // Schedule periodic cleanup
127
+ return setInterval(() => {
128
+ try {
129
+ runMessageHistoryCleanup()
130
+ } catch (error) {
131
+ Logger.log({
132
+ level: 'error',
133
+ message: 'Scheduled cleanup failed',
134
+ data: { error: error.message }
135
+ })
136
+ }
137
+ }, intervalMs)
138
+ }
139
+
140
+ /**
141
+ * Stop the cleanup job
142
+ * @param {NodeJS.Timer} timer - The interval timer to clear
143
+ */
144
+ export const stopCleanupJob = (timer) => {
145
+ if (timer) {
146
+ clearInterval(timer)
147
+ Logger.log({
148
+ level: 'info',
149
+ message: 'Message history cleanup job stopped'
150
+ })
151
+ }
152
+ }
@@ -0,0 +1,25 @@
1
+ export class ApiClient {
2
+ constructor (baseUrl) {
3
+ this.baseUrl = baseUrl
4
+ }
5
+
6
+ async post (path, body = {}) {
7
+ const url = `${this.baseUrl}${path}`
8
+ const response = await fetch(url, {
9
+ method: 'POST',
10
+ headers: { 'Content-Type': 'application/json' },
11
+ body: JSON.stringify(body)
12
+ })
13
+ const result = await response.json()
14
+ if (!result.success) {
15
+ throw new Error(result.data?.message || result.message || 'API call failed')
16
+ }
17
+ return result.data
18
+ }
19
+
20
+ async get (path) {
21
+ const url = `${this.baseUrl}${path}`
22
+ const response = await fetch(url)
23
+ return response.json()
24
+ }
25
+ }
@@ -0,0 +1,52 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
3
+ import { ApiClient } from './ApiClient.js'
4
+ import { registerPubsubTools } from './tools/pubsub.js'
5
+ import { registerLoggingTools } from './tools/logging.js'
6
+ import { registerFirestoreTools } from './tools/firestore.js'
7
+ import { registerPostgresTools } from './tools/postgres.js'
8
+ import { registerRedisTools } from './tools/redis.js'
9
+ import { registerDockerTools } from './tools/docker.js'
10
+ import { registerMqttTools } from './tools/mqtt.js'
11
+ import { registerServicesTools } from './tools/services.js'
12
+ import { registerHttpTrafficTools } from './tools/httpTraffic.js'
13
+ import { registerDataTools } from './tools/data.js'
14
+ import { registerWebhooksTools } from './tools/webhooks.js'
15
+ import { registerSnapshotTools } from './tools/snapshots.js'
16
+ import { registerFunctionsTools } from './tools/functions.js'
17
+ import { registerPlatformResources } from './resources/platform.js'
18
+ import { registerDebuggingPrompts } from './prompts/debugging.js'
19
+
20
+ export async function startMcpServer ({ devToolsUrl }) {
21
+ const server = new McpServer({
22
+ name: 'goki-dev-tools',
23
+ version: '1.0.0'
24
+ })
25
+
26
+ const apiClient = new ApiClient(devToolsUrl)
27
+
28
+ // Register tool groups
29
+ registerPubsubTools(server, apiClient)
30
+ registerLoggingTools(server, apiClient)
31
+ registerFirestoreTools(server, apiClient)
32
+ registerPostgresTools(server, apiClient)
33
+ registerRedisTools(server, apiClient)
34
+ registerDockerTools(server, apiClient)
35
+ registerMqttTools(server, apiClient)
36
+ registerServicesTools(server, apiClient)
37
+ registerHttpTrafficTools(server, apiClient)
38
+ registerDataTools(server, apiClient)
39
+ registerWebhooksTools(server, apiClient)
40
+ registerSnapshotTools(server, apiClient)
41
+ registerFunctionsTools(server, apiClient)
42
+
43
+ // Register resources and prompts
44
+ registerPlatformResources(server, apiClient)
45
+ registerDebuggingPrompts(server)
46
+
47
+ // Connect via stdio transport
48
+ const transport = new StdioServerTransport()
49
+ await server.connect(transport)
50
+ console.error('Goki Dev Tools MCP server running on stdio')
51
+ console.error(`Backend URL: ${devToolsUrl}`)
52
+ }
@@ -0,0 +1,104 @@
1
+ export function registerDebuggingPrompts (server) {
2
+ server.prompt(
3
+ 'debug-pubsub',
4
+ 'Debug Pub/Sub message flow — checks topics, subscriptions, and recent messages',
5
+ { topicName: { type: 'string', description: 'Optional topic name to focus on' } },
6
+ async ({ topicName }) => ({
7
+ messages: [{
8
+ role: 'user',
9
+ content: {
10
+ type: 'text',
11
+ text: [
12
+ 'Debug Pub/Sub message flow in the Goki dev-tools platform.',
13
+ topicName ? `Focus on topic: ${topicName}` : '',
14
+ '',
15
+ 'Steps:',
16
+ '1. List all Pub/Sub topics and subscriptions',
17
+ '2. Check message history stats for overall volume',
18
+ topicName
19
+ ? `3. Search recent messages on topic "${topicName}"`
20
+ : '3. Show recent messages across all topics',
21
+ '4. Identify any issues (missing subscriptions, undelivered messages)',
22
+ '5. Suggest fixes if problems are found'
23
+ ].filter(Boolean).join('\n')
24
+ }
25
+ }]
26
+ })
27
+ )
28
+
29
+ server.prompt(
30
+ 'monitor-services',
31
+ 'Check health of all platform services and Docker containers',
32
+ {},
33
+ async () => ({
34
+ messages: [{
35
+ role: 'user',
36
+ content: {
37
+ type: 'text',
38
+ text: [
39
+ 'Check the health of all Goki dev-tools platform services:',
40
+ '',
41
+ '1. Get platform dashboard stats',
42
+ '2. List all Docker containers and their status',
43
+ '3. Check the gateway status',
44
+ '4. Test Redis connection',
45
+ '5. Test PostgreSQL connection',
46
+ '6. Check scheduler tick status',
47
+ '',
48
+ 'Report any services that are down or unhealthy, and suggest remediation steps.'
49
+ ].join('\n')
50
+ }
51
+ }]
52
+ })
53
+ )
54
+
55
+ server.prompt(
56
+ 'trace-request',
57
+ 'Trace a request through multiple services using a traceId',
58
+ { traceId: { type: 'string', description: 'The trace ID to follow across services' } },
59
+ async ({ traceId }) => ({
60
+ messages: [{
61
+ role: 'user',
62
+ content: {
63
+ type: 'text',
64
+ text: [
65
+ `Trace request with traceId "${traceId}" through the Goki platform:`,
66
+ '',
67
+ '1. Search log entries for this traceId',
68
+ '2. Search HTTP traffic records for this traceId',
69
+ '3. Search Pub/Sub message history for this traceId in attributes',
70
+ '4. Build a timeline of events across services',
71
+ '5. Identify any errors, delays, or gaps in the flow'
72
+ ].join('\n')
73
+ }
74
+ }]
75
+ })
76
+ )
77
+
78
+ server.prompt(
79
+ 'setup-test-environment',
80
+ 'Prepare the dev-tools platform for a fresh test run',
81
+ { services: { type: 'string', description: 'Comma-separated service names to clear, or "all"' } },
82
+ async ({ services }) => ({
83
+ messages: [{
84
+ role: 'user',
85
+ content: {
86
+ type: 'text',
87
+ text: [
88
+ 'Prepare the Goki dev-tools platform for a fresh test run:',
89
+ '',
90
+ services && services !== 'all'
91
+ ? `1. Clear data for these services: ${services}`
92
+ : '1. Clear all platform data',
93
+ '2. Verify all Docker containers are running',
94
+ '3. Confirm Pub/Sub emulator is ready (list topics)',
95
+ '4. Confirm logging is ready (list services)',
96
+ '5. Trigger a scheduler tick to initialize periodic tasks',
97
+ '',
98
+ 'Report readiness status when done.'
99
+ ].join('\n')
100
+ }
101
+ }]
102
+ })
103
+ )
104
+ }
@@ -0,0 +1,118 @@
1
+ import { readFile } from 'fs/promises'
2
+ import { join, dirname } from 'path'
3
+ import { fileURLToPath } from 'url'
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url))
6
+ const projectRoot = join(__dirname, '..', '..', '..')
7
+
8
+ async function readProjectFile (relativePath) {
9
+ try {
10
+ return await readFile(join(projectRoot, relativePath), 'utf-8')
11
+ } catch {
12
+ return `File not found: ${relativePath}`
13
+ }
14
+ }
15
+
16
+ export function registerPlatformResources (server, apiClient) {
17
+ server.resource(
18
+ 'platform-stats',
19
+ 'devtools://stats',
20
+ async (uri) => {
21
+ try {
22
+ const data = await apiClient.post('/v1/dashboard/stats')
23
+ return {
24
+ contents: [{
25
+ uri: uri.href,
26
+ mimeType: 'application/json',
27
+ text: JSON.stringify(data, null, 2)
28
+ }]
29
+ }
30
+ } catch (error) {
31
+ return {
32
+ contents: [{
33
+ uri: uri.href,
34
+ mimeType: 'text/plain',
35
+ text: `Error fetching stats: ${error.message}`
36
+ }]
37
+ }
38
+ }
39
+ }
40
+ )
41
+
42
+ server.resource(
43
+ 'architecture',
44
+ 'devtools://docs/architecture',
45
+ async (uri) => {
46
+ const text = await readProjectFile('CLAUDE.md')
47
+ return {
48
+ contents: [{
49
+ uri: uri.href,
50
+ mimeType: 'text/markdown',
51
+ text
52
+ }]
53
+ }
54
+ }
55
+ )
56
+
57
+ server.resource(
58
+ 'port-registry',
59
+ 'devtools://config/ports',
60
+ async (uri) => {
61
+ return {
62
+ contents: [{
63
+ uri: uri.href,
64
+ mimeType: 'application/json',
65
+ text: JSON.stringify({
66
+ management: { port: 9000, description: 'Dev Tools API + Web UI' },
67
+ pubsub: { port: 8085, description: 'GCP Pub/Sub emulator' },
68
+ awsIot: { port: 8086, description: 'AWS IoT Core HTTPS API' },
69
+ logging: { port: 8087, description: 'Google Cloud Logging emulator' },
70
+ mqtt: { port: 8883, description: 'MQTT broker (Aedes)' },
71
+ firestore: { port: 8080, description: 'GCP Firestore emulator' },
72
+ postgres: { port: 5432, description: 'PostgreSQL database' },
73
+ redis: { port: 6379, description: 'Redis cache' },
74
+ redisLogs: { port: 6380, description: 'Redis for logs (LRU)' },
75
+ elasticsearch: { port: 9200, description: 'Elasticsearch' },
76
+ kibana: { port: 5601, description: 'Kibana dashboard' }
77
+ }, null, 2)
78
+ }]
79
+ }
80
+ }
81
+ )
82
+
83
+ server.resource(
84
+ 'topic-conventions',
85
+ 'devtools://docs/topic-conventions',
86
+ async (uri) => {
87
+ return {
88
+ contents: [{
89
+ uri: uri.href,
90
+ mimeType: 'text/markdown',
91
+ text: [
92
+ '# Goki MQTT & Pub/Sub Topic Conventions',
93
+ '',
94
+ '## MQTT Topic Patterns',
95
+ '- **Request:** `rq/{action}/{clientId}`',
96
+ '- **Response:** `rs/{action}/{clientId}`',
97
+ '- **Notification:** `nf/{action}/{clientId}`',
98
+ '- **Will:** `w/{action}/{clientId}`',
99
+ '- **Lifecycle:** `simulator/connected`, `simulator/disconnected`',
100
+ '',
101
+ '## Pub/Sub Bridge',
102
+ '- MQTT messages are bridged to Pub/Sub topic `mqttMessageReceived`',
103
+ '- Format: `{ clientId, topicName, packet (base64), receivedAtMilliseconds }`',
104
+ '',
105
+ '## Common Pub/Sub Topics',
106
+ '- `mqttMessageReceived` — MQTT messages bridged to Pub/Sub',
107
+ '- `systemOneMinuteTick` — Scheduler tick every 60 seconds',
108
+ '',
109
+ '## Inter-Service Communication',
110
+ '- Services publish/subscribe via the Pub/Sub emulator on port 8085',
111
+ '- Backend-to-device messages go through AWS IoT HTTPS API on port 8086',
112
+ '- Device-to-backend messages go through MQTT broker on port 8883'
113
+ ].join('\n')
114
+ }]
115
+ }
116
+ }
117
+ )
118
+ }
@@ -0,0 +1,84 @@
1
+ import { z } from 'zod'
2
+
3
+ export function registerDataTools (server, apiClient) {
4
+ server.tool(
5
+ 'platform_stats',
6
+ 'Get platform dashboard statistics (topic count, message count, log entries, MQTT clients, etc.)',
7
+ {},
8
+ async () => {
9
+ try {
10
+ const data = await apiClient.post('/v1/dashboard/stats')
11
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
12
+ } catch (error) {
13
+ return { content: [{ type: 'text', text: `Failed to get platform stats: ${error.message}` }], isError: true }
14
+ }
15
+ }
16
+ )
17
+
18
+ server.tool(
19
+ 'platform_export',
20
+ 'Export all platform data (topics, subscriptions, messages, logs, etc.)',
21
+ {},
22
+ async () => {
23
+ try {
24
+ const data = await apiClient.post('/v1/data/export')
25
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
26
+ } catch (error) {
27
+ return { content: [{ type: 'text', text: `Failed to export platform data: ${error.message}` }], isError: true }
28
+ }
29
+ }
30
+ )
31
+
32
+ server.tool(
33
+ 'platform_import',
34
+ 'Import platform data (optionally clearing existing data first)',
35
+ {
36
+ data: z.record(z.any()).describe('Platform data object to import'),
37
+ clearExisting: z.boolean().optional().describe('Whether to clear existing data before importing')
38
+ },
39
+ async ({ data, clearExisting }) => {
40
+ try {
41
+ const body = { data }
42
+ if (clearExisting !== undefined) body.clearExisting = clearExisting
43
+ const result = await apiClient.post('/v1/data/import', body)
44
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }
45
+ } catch (error) {
46
+ return { content: [{ type: 'text', text: `Failed to import platform data: ${error.message}` }], isError: true }
47
+ }
48
+ }
49
+ )
50
+
51
+ server.tool(
52
+ 'platform_clear',
53
+ 'Clear all platform data (topics, messages, logs, traffic records)',
54
+ {},
55
+ async () => {
56
+ try {
57
+ const data = await apiClient.post('/v1/data/clear')
58
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
59
+ } catch (error) {
60
+ return { content: [{ type: 'text', text: `Failed to clear platform data: ${error.message}` }], isError: true }
61
+ }
62
+ }
63
+ )
64
+
65
+ server.tool(
66
+ 'platform_clear_services',
67
+ 'Clear data for specific services. Useful for resetting test state without clearing everything.',
68
+ {
69
+ services: z.array(z.string()).optional().describe('Optional list of service names to clear'),
70
+ keepSystemData: z.boolean().optional().describe('Whether to keep system/infrastructure data')
71
+ },
72
+ async ({ services, keepSystemData }) => {
73
+ try {
74
+ const body = {}
75
+ if (services !== undefined) body.services = services
76
+ if (keepSystemData !== undefined) body.keepSystemData = keepSystemData
77
+ const data = await apiClient.post('/v1/data/clear-services', body)
78
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
79
+ } catch (error) {
80
+ return { content: [{ type: 'text', text: `Failed to clear service data: ${error.message}` }], isError: true }
81
+ }
82
+ }
83
+ )
84
+ }