@gokiteam/goki-dev 0.2.1 → 0.2.3
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/cli/NgrokManager.js +22 -9
- package/config.development +0 -1
- package/docker-compose.dev.yml +1 -1
- package/docker-compose.yml +1 -1
- package/package.json +19 -24
- package/src/api/firestore/Controllers.js +14 -0
- package/src/api/firestore/Logic.js +74 -1
- package/src/api/firestore/Router.js +2 -0
- package/src/api/firestore/Schemas.js +160 -0
- package/src/api/snapshots/Logic.js +2 -5
- package/src/configs/Application.js +3 -1
- package/src/mcp/tools/data.js +8 -3
- package/src/mcp/tools/docker.js +6 -2
- package/src/mcp/tools/firestore.js +49 -2
- package/src/mcp/tools/functions.js +15 -4
- package/src/mcp/tools/httpTraffic.js +6 -1
- package/src/mcp/tools/logging.js +10 -1
- package/src/mcp/tools/mqtt.js +2 -0
- package/src/mcp/tools/postgres.js +9 -1
- package/src/mcp/tools/pubsub.js +18 -4
- package/src/mcp/tools/redis.js +10 -2
- package/src/mcp/tools/services.js +11 -3
- package/src/mcp/tools/snapshots.js +6 -2
- package/src/mcp/tools/webhooks.js +5 -1
- package/src/singletons/SqliteStore.js +6 -1
- package/client/dist/client.d.ts +0 -380
- package/client/dist/client.js +0 -557
- package/client/dist/helpers.d.ts +0 -62
- package/client/dist/helpers.js +0 -122
- package/client/dist/index.d.ts +0 -59
- package/client/dist/index.js +0 -78
- package/client/dist/package.json +0 -1
- package/client/dist/types.d.ts +0 -339
- package/client/dist/types.js +0 -7
package/cli/NgrokManager.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { execa } from 'execa'
|
|
2
2
|
import fs from 'fs'
|
|
3
|
+
import os from 'os'
|
|
3
4
|
import path from 'path'
|
|
4
|
-
import { fileURLToPath } from 'url'
|
|
5
5
|
import { Logger } from './Logger.js'
|
|
6
6
|
|
|
7
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
8
7
|
const DEV_TOOLS_API = 'http://localhost:9000'
|
|
9
8
|
const NGROK_API = 'http://localhost:4040'
|
|
10
|
-
const DATA_DIR = path.
|
|
9
|
+
const DATA_DIR = path.join(os.homedir(), '.goki-dev', 'data')
|
|
11
10
|
const PID_FILE = path.join(DATA_DIR, 'ngrok.pid')
|
|
12
11
|
const STATE_FILE = path.join(DATA_DIR, 'ngrok.json')
|
|
12
|
+
const DOMAIN_FILE = path.join(DATA_DIR, 'ngrok-domain.txt')
|
|
13
13
|
|
|
14
14
|
export class NgrokManager {
|
|
15
15
|
// --- PID File Management ---
|
|
@@ -302,6 +302,12 @@ export class NgrokManager {
|
|
|
302
302
|
// --- Domain Management ---
|
|
303
303
|
|
|
304
304
|
static async loadDomain () {
|
|
305
|
+
// Check local domain file first (most reliable, survives tunnel failures)
|
|
306
|
+
try {
|
|
307
|
+
const domain = fs.readFileSync(DOMAIN_FILE, 'utf8').trim()
|
|
308
|
+
if (domain) return domain
|
|
309
|
+
} catch { /* file doesn't exist yet */ }
|
|
310
|
+
// Try dev-tools API
|
|
305
311
|
try {
|
|
306
312
|
const response = await fetch(`${DEV_TOOLS_API}/v1/webhooks/settings/get`, {
|
|
307
313
|
method: 'POST',
|
|
@@ -312,17 +318,24 @@ export class NgrokManager {
|
|
|
312
318
|
if (response.ok) {
|
|
313
319
|
const result = await response.json()
|
|
314
320
|
const domain = result.data?.settings?.ngrok_domain
|
|
315
|
-
if (domain)
|
|
321
|
+
if (domain) {
|
|
322
|
+
// Persist locally for next time
|
|
323
|
+
this.ensureDataDir()
|
|
324
|
+
fs.writeFileSync(DOMAIN_FILE, domain.trim(), 'utf8')
|
|
325
|
+
return domain
|
|
326
|
+
}
|
|
316
327
|
}
|
|
317
|
-
} catch {
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
// Fallback: read from local state file
|
|
328
|
+
} catch { /* API not available */ }
|
|
329
|
+
// Last resort: check tunnel state file
|
|
321
330
|
const state = this.readState()
|
|
322
331
|
return state?.domain || null
|
|
323
332
|
}
|
|
324
333
|
|
|
325
334
|
static async saveDomain (domain) {
|
|
335
|
+
// Always save locally so it survives tunnel failures and API downtime
|
|
336
|
+
this.ensureDataDir()
|
|
337
|
+
fs.writeFileSync(DOMAIN_FILE, domain.trim(), 'utf8')
|
|
338
|
+
// Also save to dev-tools API (best-effort)
|
|
326
339
|
try {
|
|
327
340
|
await fetch(`${DEV_TOOLS_API}/v1/webhooks/settings/update`, {
|
|
328
341
|
method: 'POST',
|
|
@@ -333,7 +346,7 @@ export class NgrokManager {
|
|
|
333
346
|
signal: AbortSignal.timeout(3000)
|
|
334
347
|
})
|
|
335
348
|
} catch (error) {
|
|
336
|
-
Logger.warn(`Failed to save ngrok domain: ${error.message}`)
|
|
349
|
+
Logger.warn(`Failed to save ngrok domain to API: ${error.message}`)
|
|
337
350
|
}
|
|
338
351
|
}
|
|
339
352
|
|
package/config.development
CHANGED
package/docker-compose.dev.yml
CHANGED
package/docker-compose.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,24 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gokiteam/goki-dev",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Unified local development platform for Goki services",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./client/dist/index.js",
|
|
7
|
-
"types": "./client/dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./client/dist/index.d.ts",
|
|
11
|
-
"import": "./client/dist/index.js",
|
|
12
|
-
"require": "./client/dist/index.js"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"typesVersions": {
|
|
16
|
-
"*": {
|
|
17
|
-
".": [
|
|
18
|
-
"./client/dist/index.d.ts"
|
|
19
|
-
]
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
6
|
"bin": {
|
|
23
7
|
"goki-dev": "./bin/goki-dev.js",
|
|
24
8
|
"goki-dev-mcp": "./bin/mcp-server.js",
|
|
@@ -28,7 +12,6 @@
|
|
|
28
12
|
"bin/",
|
|
29
13
|
"cli/",
|
|
30
14
|
"src/",
|
|
31
|
-
"client/dist/",
|
|
32
15
|
"ui/build/",
|
|
33
16
|
"docker-compose.yml",
|
|
34
17
|
"docker-compose.services.yml",
|
|
@@ -48,12 +31,11 @@
|
|
|
48
31
|
"dev:watch": "dotenv -e config.development node --watch src/Server.js",
|
|
49
32
|
"dev:ui": "concurrently \"npm run dev\" \"cd ui && npm start\" --names \"backend,frontend\" --prefix-colors \"blue,green\"",
|
|
50
33
|
"ui:start": "cd ui && npm start",
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"release": "
|
|
55
|
-
"release:
|
|
56
|
-
"release:major": "npm version major && npm run build:client && npm publish",
|
|
34
|
+
"build": "cd ui && npm run build && cd ../client && npm run build",
|
|
35
|
+
"prepublishOnly": "npm run build",
|
|
36
|
+
"release": "standard-version && npm run build && npm publish && cd client && npm publish",
|
|
37
|
+
"release:minor": "standard-version --release-as minor && npm run build && npm publish && cd client && npm publish",
|
|
38
|
+
"release:major": "standard-version --release-as major && npm run build && npm publish && cd client && npm publish",
|
|
57
39
|
"test": "dotenv -e config.test mocha 'tests/**/*.test.js'",
|
|
58
40
|
"test:api": "dotenv -e config.test mocha 'tests/api/**/*.test.js'",
|
|
59
41
|
"test:emulation": "dotenv -e config.test mocha 'tests/emulation/**/*.test.js'",
|
|
@@ -137,6 +119,7 @@
|
|
|
137
119
|
"mqtt": "^5.15.0",
|
|
138
120
|
"playwright": "^1.58.1",
|
|
139
121
|
"standard": "^17.1.0",
|
|
122
|
+
"standard-version": "^9.5.0",
|
|
140
123
|
"supertest": "^6.3.0",
|
|
141
124
|
"typescript": "^5.9.3"
|
|
142
125
|
},
|
|
@@ -145,5 +128,17 @@
|
|
|
145
128
|
"mocha"
|
|
146
129
|
]
|
|
147
130
|
},
|
|
131
|
+
"standard-version": {
|
|
132
|
+
"bumpFiles": [
|
|
133
|
+
{
|
|
134
|
+
"filename": "package.json",
|
|
135
|
+
"type": "json"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"filename": "client/package.json",
|
|
139
|
+
"type": "json"
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
},
|
|
148
143
|
"packageManager": "yarn@3.8.7+sha512.bbe7e310ff7fd20dc63b111110f96fe18192234bb0d4f10441fa6b85d2b644c8923db8fbe6d7886257ace948440ab1f83325ad02af457a1806cdc97f03d2508e"
|
|
149
144
|
}
|
|
@@ -72,6 +72,20 @@ export const Controllers = {
|
|
|
72
72
|
ctx.reply(result)
|
|
73
73
|
},
|
|
74
74
|
|
|
75
|
+
async setDocument (ctx) {
|
|
76
|
+
const { traceId } = ctx.state
|
|
77
|
+
const { collectionPath, documentId, fields, projectId } = ctx.request.body
|
|
78
|
+
const result = await Logic.setDocument({ collectionPath, documentId, fields, projectId, traceId })
|
|
79
|
+
ctx.reply(result)
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
async createBatch (ctx) {
|
|
83
|
+
const { traceId } = ctx.state
|
|
84
|
+
const { collectionPath, documents, projectId } = ctx.request.body
|
|
85
|
+
const result = await Logic.createBatch({ collectionPath, documents, projectId, traceId })
|
|
86
|
+
ctx.reply(result)
|
|
87
|
+
},
|
|
88
|
+
|
|
75
89
|
async executeQuery (ctx) {
|
|
76
90
|
const { traceId } = ctx.state
|
|
77
91
|
const { collectionPath, where, orderBy, limit, projectId } = ctx.request.body
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { initializeApp, getApps
|
|
1
|
+
import { initializeApp, getApps } from 'firebase-admin/app'
|
|
2
2
|
import { getFirestore } from 'firebase-admin/firestore'
|
|
3
3
|
import { SqliteStore } from '../../singletons/SqliteStore.js'
|
|
4
4
|
import { Application } from '../../configs/Application.js'
|
|
@@ -354,6 +354,79 @@ export const Logic = {
|
|
|
354
354
|
}
|
|
355
355
|
},
|
|
356
356
|
|
|
357
|
+
async setDocument (params) {
|
|
358
|
+
const { collectionPath, documentId, fields, traceId } = params
|
|
359
|
+
const projectId = resolveProjectId(params)
|
|
360
|
+
try {
|
|
361
|
+
const url = `${FIRESTORE_API}/v1/projects/${projectId}/databases/${DATABASE}/documents/${collectionPath}/${documentId}`
|
|
362
|
+
const response = await fetch(url, {
|
|
363
|
+
method: 'PATCH',
|
|
364
|
+
headers: { 'Content-Type': 'application/json' },
|
|
365
|
+
body: JSON.stringify({ fields })
|
|
366
|
+
})
|
|
367
|
+
if (!response.ok) {
|
|
368
|
+
throw new Error(`Firestore API error: ${response.statusText}`)
|
|
369
|
+
}
|
|
370
|
+
const document = await response.json()
|
|
371
|
+
trackCollection(collectionPath)
|
|
372
|
+
return {
|
|
373
|
+
document,
|
|
374
|
+
traceId
|
|
375
|
+
}
|
|
376
|
+
} catch (error) {
|
|
377
|
+
return {
|
|
378
|
+
status: 'error',
|
|
379
|
+
message: error.message,
|
|
380
|
+
traceId
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
async createBatch (params) {
|
|
386
|
+
const { collectionPath, documents = [], traceId } = params
|
|
387
|
+
const projectId = resolveProjectId(params)
|
|
388
|
+
const BATCH_SIZE = 20
|
|
389
|
+
try {
|
|
390
|
+
const failedIds = []
|
|
391
|
+
let createdCount = 0
|
|
392
|
+
for (let i = 0; i < documents.length; i += BATCH_SIZE) {
|
|
393
|
+
const batch = documents.slice(i, i + BATCH_SIZE)
|
|
394
|
+
const results = await Promise.allSettled(
|
|
395
|
+
batch.map(doc =>
|
|
396
|
+
this.createDocument({
|
|
397
|
+
collectionPath,
|
|
398
|
+
projectId,
|
|
399
|
+
documentId: doc.documentId,
|
|
400
|
+
fields: doc.fields,
|
|
401
|
+
traceId
|
|
402
|
+
})
|
|
403
|
+
)
|
|
404
|
+
)
|
|
405
|
+
for (let j = 0; j < results.length; j++) {
|
|
406
|
+
const result = results[j]
|
|
407
|
+
if (result.status === 'fulfilled' && !result.value.status) {
|
|
408
|
+
createdCount++
|
|
409
|
+
} else {
|
|
410
|
+
const docId = batch[j].documentId || `index-${i + j}`
|
|
411
|
+
failedIds.push(docId)
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
success: true,
|
|
417
|
+
createdCount,
|
|
418
|
+
failedIds,
|
|
419
|
+
traceId
|
|
420
|
+
}
|
|
421
|
+
} catch (error) {
|
|
422
|
+
return {
|
|
423
|
+
status: 'error',
|
|
424
|
+
message: error.message,
|
|
425
|
+
traceId
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
|
|
357
430
|
async clearCollection (params) {
|
|
358
431
|
const { collectionPath, projectId, traceId } = params
|
|
359
432
|
try {
|
|
@@ -11,6 +11,8 @@ v1.post('/documents/get', Controllers.getDocument)
|
|
|
11
11
|
v1.post('/documents/create', Controllers.createDocument)
|
|
12
12
|
v1.post('/documents/update', Controllers.updateDocument)
|
|
13
13
|
v1.post('/documents/delete', Controllers.deleteDocument)
|
|
14
|
+
v1.post('/documents/set', Controllers.setDocument)
|
|
15
|
+
v1.post('/documents/create-batch', Controllers.createBatch)
|
|
14
16
|
v1.post('/documents/delete-by-query', Controllers.deleteByQuery)
|
|
15
17
|
v1.post('/documents/delete-by-prefix', Controllers.deleteByPrefix)
|
|
16
18
|
v1.post('/documents/delete-batch', Controllers.deleteBatch)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { Joi } from '@gokiteam/koa'
|
|
2
|
+
|
|
3
|
+
const whereClause = Joi.object({
|
|
4
|
+
field: Joi.string().required(),
|
|
5
|
+
operator: Joi.string().valid('==', '!=', '<', '<=', '>', '>=', 'array-contains', 'in', 'array-contains-any').required(),
|
|
6
|
+
value: Joi.any().required()
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
const pageOptions = Joi.object({
|
|
10
|
+
limit: Joi.number().integer().min(1).max(10000).default(50),
|
|
11
|
+
offset: Joi.number().integer().min(0).default(0)
|
|
12
|
+
}).optional()
|
|
13
|
+
|
|
14
|
+
export const Schemas = {
|
|
15
|
+
listProjects: {
|
|
16
|
+
request: {
|
|
17
|
+
body: {}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
listCollections: {
|
|
22
|
+
request: {
|
|
23
|
+
body: {
|
|
24
|
+
projectId: Joi.string().optional()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
listDocuments: {
|
|
30
|
+
request: {
|
|
31
|
+
body: {
|
|
32
|
+
collectionPath: Joi.string().required(),
|
|
33
|
+
projectId: Joi.string().optional(),
|
|
34
|
+
page: pageOptions
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
getDocument: {
|
|
40
|
+
request: {
|
|
41
|
+
body: {
|
|
42
|
+
collectionPath: Joi.string().required(),
|
|
43
|
+
documentId: Joi.string().required(),
|
|
44
|
+
projectId: Joi.string().optional()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
createDocument: {
|
|
50
|
+
request: {
|
|
51
|
+
body: {
|
|
52
|
+
collectionPath: Joi.string().required(),
|
|
53
|
+
documentId: Joi.string().optional(),
|
|
54
|
+
fields: Joi.object().required(),
|
|
55
|
+
projectId: Joi.string().optional()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
setDocument: {
|
|
61
|
+
request: {
|
|
62
|
+
body: {
|
|
63
|
+
collectionPath: Joi.string().required(),
|
|
64
|
+
documentId: Joi.string().required(),
|
|
65
|
+
fields: Joi.object().required(),
|
|
66
|
+
projectId: Joi.string().optional()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
updateDocument: {
|
|
72
|
+
request: {
|
|
73
|
+
body: {
|
|
74
|
+
collectionPath: Joi.string().required(),
|
|
75
|
+
documentId: Joi.string().required(),
|
|
76
|
+
fields: Joi.object().required(),
|
|
77
|
+
projectId: Joi.string().optional()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
deleteDocument: {
|
|
83
|
+
request: {
|
|
84
|
+
body: {
|
|
85
|
+
collectionPath: Joi.string().required(),
|
|
86
|
+
documentId: Joi.string().required(),
|
|
87
|
+
projectId: Joi.string().optional()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
createBatch: {
|
|
93
|
+
request: {
|
|
94
|
+
body: {
|
|
95
|
+
collectionPath: Joi.string().required(),
|
|
96
|
+
documents: Joi.array().items(
|
|
97
|
+
Joi.object({
|
|
98
|
+
documentId: Joi.string().optional(),
|
|
99
|
+
fields: Joi.object().required()
|
|
100
|
+
})
|
|
101
|
+
).min(1).max(500).required(),
|
|
102
|
+
projectId: Joi.string().optional()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
deleteByQuery: {
|
|
108
|
+
request: {
|
|
109
|
+
body: {
|
|
110
|
+
collectionPath: Joi.string().required(),
|
|
111
|
+
where: whereClause.required(),
|
|
112
|
+
projectId: Joi.string().optional()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
deleteByPrefix: {
|
|
118
|
+
request: {
|
|
119
|
+
body: {
|
|
120
|
+
collectionPath: Joi.string().required(),
|
|
121
|
+
prefix: Joi.string().required(),
|
|
122
|
+
projectId: Joi.string().optional()
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
deleteBatch: {
|
|
128
|
+
request: {
|
|
129
|
+
body: {
|
|
130
|
+
collectionPath: Joi.string().required(),
|
|
131
|
+
documentIds: Joi.array().items(Joi.string()).min(1).max(500).required(),
|
|
132
|
+
projectId: Joi.string().optional()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
executeQuery: {
|
|
138
|
+
request: {
|
|
139
|
+
body: {
|
|
140
|
+
collectionPath: Joi.string().required(),
|
|
141
|
+
where: Joi.array().items(whereClause).optional(),
|
|
142
|
+
orderBy: Joi.object({
|
|
143
|
+
field: Joi.string().required(),
|
|
144
|
+
direction: Joi.string().valid('ASCENDING', 'DESCENDING').default('ASCENDING')
|
|
145
|
+
}).optional(),
|
|
146
|
+
limit: Joi.number().integer().min(1).max(10000).default(50),
|
|
147
|
+
projectId: Joi.string().optional()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
clearCollection: {
|
|
153
|
+
request: {
|
|
154
|
+
body: {
|
|
155
|
+
collectionPath: Joi.string().required(),
|
|
156
|
+
projectId: Joi.string().optional()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execa } from 'execa'
|
|
2
2
|
import fs from 'fs/promises'
|
|
3
|
+
import os from 'os'
|
|
3
4
|
import path from 'path'
|
|
4
|
-
import { fileURLToPath } from 'url'
|
|
5
5
|
import { Logic as FirestoreLogic } from '../firestore/Logic.js'
|
|
6
6
|
import { Logic as DockerLogic } from '../docker/Logic.js'
|
|
7
7
|
import { Logic as PubSubLogic } from '../pubsub/Logic.js'
|
|
@@ -23,10 +23,7 @@ import {
|
|
|
23
23
|
PUBSUB_MESSAGE_HISTORY
|
|
24
24
|
} from '../../db/Tables.js'
|
|
25
25
|
|
|
26
|
-
const
|
|
27
|
-
const __dirname = path.dirname(__filename)
|
|
28
|
-
const PROJECT_ROOT = path.join(__dirname, '../../..')
|
|
29
|
-
const SNAPSHOTS_DIR = path.join(PROJECT_ROOT, '.goki-dev/snapshots')
|
|
26
|
+
const SNAPSHOTS_DIR = path.join(os.homedir(), '.goki-dev', 'snapshots')
|
|
30
27
|
|
|
31
28
|
// Ensure snapshot directories exist
|
|
32
29
|
async function ensureSnapshotDirs () {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import Moment from 'moment'
|
|
2
|
+
import os from 'os'
|
|
3
|
+
import path from 'path'
|
|
2
4
|
|
|
3
5
|
const {
|
|
4
6
|
NODE_ENV = 'development',
|
|
@@ -77,7 +79,7 @@ export const Application = {
|
|
|
77
79
|
shadowSubscriptionCheckIntervalMs: parseInt(SHADOW_SUBSCRIPTION_CHECK_INTERVAL_MS) || 5000
|
|
78
80
|
},
|
|
79
81
|
storage: {
|
|
80
|
-
dataDir: DATA_DIR
|
|
82
|
+
dataDir: DATA_DIR || path.join(os.homedir(), '.goki-dev', 'data')
|
|
81
83
|
},
|
|
82
84
|
redis: {
|
|
83
85
|
host: REDIS_HOST || 'localhost',
|
package/src/mcp/tools/data.js
CHANGED
|
@@ -5,6 +5,7 @@ export function registerDataTools (server, apiClient) {
|
|
|
5
5
|
'platform_stats',
|
|
6
6
|
'Get platform dashboard statistics (topic count, message count, log entries, MQTT clients, etc.)',
|
|
7
7
|
{},
|
|
8
|
+
{ readOnlyHint: true },
|
|
8
9
|
async () => {
|
|
9
10
|
try {
|
|
10
11
|
const data = await apiClient.post('/v1/dashboard/stats')
|
|
@@ -19,6 +20,7 @@ export function registerDataTools (server, apiClient) {
|
|
|
19
20
|
'platform_export',
|
|
20
21
|
'Export all platform data (topics, subscriptions, messages, logs, etc.)',
|
|
21
22
|
{},
|
|
23
|
+
{ readOnlyHint: true },
|
|
22
24
|
async () => {
|
|
23
25
|
try {
|
|
24
26
|
const data = await apiClient.post('/v1/data/export')
|
|
@@ -31,11 +33,12 @@ export function registerDataTools (server, apiClient) {
|
|
|
31
33
|
|
|
32
34
|
server.tool(
|
|
33
35
|
'platform_import',
|
|
34
|
-
'Import platform data (optionally clearing existing data first)',
|
|
36
|
+
'[DESTRUCTIVE] Import platform data (optionally clearing existing data first). Ask the user for confirmation before calling this tool.',
|
|
35
37
|
{
|
|
36
38
|
data: z.record(z.any()).describe('Platform data object to import'),
|
|
37
39
|
clearExisting: z.boolean().optional().describe('Whether to clear existing data before importing')
|
|
38
40
|
},
|
|
41
|
+
{ destructiveHint: true },
|
|
39
42
|
async ({ data, clearExisting }) => {
|
|
40
43
|
try {
|
|
41
44
|
const body = { data }
|
|
@@ -50,8 +53,9 @@ export function registerDataTools (server, apiClient) {
|
|
|
50
53
|
|
|
51
54
|
server.tool(
|
|
52
55
|
'platform_clear',
|
|
53
|
-
'Clear all platform data (topics, messages, logs, traffic records)',
|
|
56
|
+
'[DESTRUCTIVE] Clear all platform data (topics, messages, logs, traffic records). Ask the user for confirmation before calling this tool.',
|
|
54
57
|
{},
|
|
58
|
+
{ destructiveHint: true },
|
|
55
59
|
async () => {
|
|
56
60
|
try {
|
|
57
61
|
const data = await apiClient.post('/v1/data/clear')
|
|
@@ -64,11 +68,12 @@ export function registerDataTools (server, apiClient) {
|
|
|
64
68
|
|
|
65
69
|
server.tool(
|
|
66
70
|
'platform_clear_services',
|
|
67
|
-
'Clear data for specific services. Useful for resetting test state without clearing everything.',
|
|
71
|
+
'[DESTRUCTIVE] Clear data for specific services. Useful for resetting test state without clearing everything. Ask the user for confirmation before calling this tool.',
|
|
68
72
|
{
|
|
69
73
|
services: z.array(z.string()).optional().describe('Optional list of service names to clear'),
|
|
70
74
|
keepSystemData: z.boolean().optional().describe('Whether to keep system/infrastructure data')
|
|
71
75
|
},
|
|
76
|
+
{ destructiveHint: true },
|
|
72
77
|
async ({ services, keepSystemData }) => {
|
|
73
78
|
try {
|
|
74
79
|
const body = {}
|
package/src/mcp/tools/docker.js
CHANGED
|
@@ -63,6 +63,7 @@ export function registerDockerTools (server, apiClient) {
|
|
|
63
63
|
'docker_list_containers',
|
|
64
64
|
'List all Docker containers on the goki-network with their status, ports, and uptime',
|
|
65
65
|
{},
|
|
66
|
+
{ readOnlyHint: true },
|
|
66
67
|
async () => {
|
|
67
68
|
try {
|
|
68
69
|
const { stdout } = await execAsync(
|
|
@@ -114,10 +115,11 @@ export function registerDockerTools (server, apiClient) {
|
|
|
114
115
|
|
|
115
116
|
server.tool(
|
|
116
117
|
'docker_stop_container',
|
|
117
|
-
'Stop a running Docker container by name',
|
|
118
|
+
'[DESTRUCTIVE] Stop a running Docker container by name. Ask the user for confirmation before calling this tool.',
|
|
118
119
|
{
|
|
119
120
|
containerName: z.string().describe('Name of the Docker container to stop')
|
|
120
121
|
},
|
|
122
|
+
{ destructiveHint: true },
|
|
121
123
|
async ({ containerName }) => {
|
|
122
124
|
try {
|
|
123
125
|
await execAsync(`docker stop ${containerName}`)
|
|
@@ -131,10 +133,11 @@ export function registerDockerTools (server, apiClient) {
|
|
|
131
133
|
|
|
132
134
|
server.tool(
|
|
133
135
|
'docker_restart_container',
|
|
134
|
-
'Restart a Docker container by name',
|
|
136
|
+
'[DESTRUCTIVE] Restart a Docker container by name. Ask the user for confirmation before calling this tool.',
|
|
135
137
|
{
|
|
136
138
|
containerName: z.string().describe('Name of the Docker container to restart')
|
|
137
139
|
},
|
|
140
|
+
{ destructiveHint: true },
|
|
138
141
|
async ({ containerName }) => {
|
|
139
142
|
try {
|
|
140
143
|
await execAsync(`docker restart ${containerName}`)
|
|
@@ -153,6 +156,7 @@ export function registerDockerTools (server, apiClient) {
|
|
|
153
156
|
containerName: z.string().describe('Name of the Docker container'),
|
|
154
157
|
lines: z.number().optional().describe('Number of recent log lines to retrieve (default 100)')
|
|
155
158
|
},
|
|
159
|
+
{ readOnlyHint: true },
|
|
156
160
|
async ({ containerName, lines = 100 }) => {
|
|
157
161
|
try {
|
|
158
162
|
const { stdout, stderr } = await execAsync(`docker logs --tail ${lines} ${containerName} 2>&1`)
|