@fugood/bricks-project 2.22.0-beta.1 → 2.22.0-beta.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/package.json +3 -2
- package/tools/mcp-server.ts +86 -0
- package/tools/postinstall.ts +54 -2
- package/tools/preview-main.mjs +14 -6
- package/tools/preview.ts +33 -24
- package/utils/event-props.ts +0 -15
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugood/bricks-project",
|
|
3
|
-
"version": "2.22.0-beta.
|
|
3
|
+
"version": "2.22.0-beta.3",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "node scripts/build.js"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
+
"@modelcontextprotocol/sdk": "^1.7.0",
|
|
9
10
|
"@types/escodegen": "^0.0.10",
|
|
10
11
|
"@types/lodash": "^4.17.12",
|
|
11
12
|
"acorn": "^8.13.0",
|
|
@@ -13,5 +14,5 @@
|
|
|
13
14
|
"lodash": "^4.17.4",
|
|
14
15
|
"uuid": "^8.3.1"
|
|
15
16
|
},
|
|
16
|
-
"gitHead": "
|
|
17
|
+
"gitHead": "cbb283b7ce73520369f855f7fbe86e8e07b831eb"
|
|
17
18
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
import { $ } from 'bun'
|
|
5
|
+
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: 'bricks-project',
|
|
8
|
+
version: '1.0.0',
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const dirname = import.meta.dirname
|
|
12
|
+
const projectDir = String(dirname).split('/node_modules/')[0]
|
|
13
|
+
|
|
14
|
+
server.tool('compile', {}, async () => {
|
|
15
|
+
let log
|
|
16
|
+
try {
|
|
17
|
+
log = await $`bun compile`.cwd(projectDir).text()
|
|
18
|
+
} catch (err) {
|
|
19
|
+
log = err.stdout.toString() + '\n' + err.stderr.toString()
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: 'text', text: log || 'Compiled successfully' }],
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// NOTE: Cursor (Or VSCode) seems set ELECTRON_RUN_AS_NODE to 1, so we need to unset it
|
|
27
|
+
process.env.ELECTRON_RUN_AS_NODE = ''
|
|
28
|
+
|
|
29
|
+
server.tool(
|
|
30
|
+
'take-preview-screenshot',
|
|
31
|
+
{
|
|
32
|
+
delay: z
|
|
33
|
+
.number()
|
|
34
|
+
.describe('Delay in milliseconds before taking screenshot')
|
|
35
|
+
.optional()
|
|
36
|
+
.default(3000),
|
|
37
|
+
width: z.number().describe('Width of the screenshot').optional().default(600),
|
|
38
|
+
height: z.number().optional().default(480),
|
|
39
|
+
responseImage: z
|
|
40
|
+
.boolean()
|
|
41
|
+
.describe(
|
|
42
|
+
'Whether to response image content (base64 encoded jpeg). If false, only saved path will be responded as text.',
|
|
43
|
+
)
|
|
44
|
+
.optional()
|
|
45
|
+
.default(false),
|
|
46
|
+
} as any,
|
|
47
|
+
async ({ delay, width, height, responseImage }: any) => {
|
|
48
|
+
let log = ''
|
|
49
|
+
let error = false
|
|
50
|
+
try {
|
|
51
|
+
const args = [
|
|
52
|
+
'--take-screenshot',
|
|
53
|
+
JSON.stringify({
|
|
54
|
+
delay,
|
|
55
|
+
width,
|
|
56
|
+
height,
|
|
57
|
+
path: `${dirname}/screenshot.jpg`,
|
|
58
|
+
closeAfter: true,
|
|
59
|
+
headless: true,
|
|
60
|
+
}),
|
|
61
|
+
]
|
|
62
|
+
log = await $`bunx --bun electron ${dirname}/preview-main.mjs ${args}`.cwd(projectDir).text()
|
|
63
|
+
} catch (err) {
|
|
64
|
+
log = err.stdout.toString() + '\n' + err.stderr.toString()
|
|
65
|
+
error = true
|
|
66
|
+
}
|
|
67
|
+
let screenshotBase64: any = null
|
|
68
|
+
if (!error && responseImage) {
|
|
69
|
+
const screenshot = await Bun.file(`${dirname}/screenshot.jpg`).arrayBuffer()
|
|
70
|
+
screenshotBase64 = Buffer.from(screenshot).toString('base64')
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{ type: 'text', text: log },
|
|
75
|
+
screenshotBase64 && {
|
|
76
|
+
type: 'image',
|
|
77
|
+
data: screenshotBase64,
|
|
78
|
+
mimeType: 'image/jpeg',
|
|
79
|
+
},
|
|
80
|
+
].filter(Boolean),
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
const transport = new StdioServerTransport()
|
|
86
|
+
await server.connect(transport)
|
package/tools/postinstall.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { $ } from 'bun'
|
|
2
|
-
|
|
2
|
+
import { stat, readFile, writeFile } from 'fs/promises'
|
|
3
3
|
const cwd = process.cwd()
|
|
4
4
|
|
|
5
5
|
const libFiles = ['types', 'utils', 'index.ts']
|
|
@@ -9,4 +9,56 @@ for (const file of libFiles) {
|
|
|
9
9
|
await $`cp -r ${__dirname}/../${file} ${cwd}/project`
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
console.log('Copied files to project/')
|
|
12
|
+
console.log('Copied files to project/')
|
|
13
|
+
|
|
14
|
+
async function exists(f: string) {
|
|
15
|
+
try {
|
|
16
|
+
await stat(f)
|
|
17
|
+
return true
|
|
18
|
+
} catch {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const projectMcpServer = {
|
|
24
|
+
command: 'bun',
|
|
25
|
+
args: [`${cwd}/node_modules/@fugood/bricks-project/tools/mcp-server.ts`],
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const defaultMcpConfig = {
|
|
29
|
+
mcpServers: {
|
|
30
|
+
'bricks-project': projectMcpServer,
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const handleMcpConfigOverride = async (mcpConfigPath: string) => {
|
|
35
|
+
let mcpConfig: { mcpServers: Record<string, typeof projectMcpServer> } | null = null
|
|
36
|
+
if (await exists(mcpConfigPath)) {
|
|
37
|
+
const configStr = await readFile(mcpConfigPath, 'utf-8')
|
|
38
|
+
try {
|
|
39
|
+
mcpConfig = JSON.parse(configStr)
|
|
40
|
+
if (!mcpConfig?.mcpServers) throw new Error('mcpServers is not defined')
|
|
41
|
+
mcpConfig.mcpServers['bricks-project'] = projectMcpServer
|
|
42
|
+
} catch (e) {
|
|
43
|
+
mcpConfig = defaultMcpConfig
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
mcpConfig = defaultMcpConfig
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await writeFile(mcpConfigPath, `${JSON.stringify(mcpConfig, null, 2)}\n`)
|
|
50
|
+
|
|
51
|
+
console.log(`Updated ${mcpConfigPath}`)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (await exists(`${cwd}/.cursorrules`)) {
|
|
55
|
+
await $`mkdir -p ${cwd}/.cursor`
|
|
56
|
+
const cursorMcpConfigPath = `${cwd}/.cursor/mcp.json`
|
|
57
|
+
await handleMcpConfigOverride(cursorMcpConfigPath)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (await exists(`${cwd}/CLAUDE.md`)) {
|
|
61
|
+
await $`mkdir -p ${cwd}/.cursor`
|
|
62
|
+
const claudeCodeMcpConfigPath = `${cwd}/.mcp.json`
|
|
63
|
+
await handleMcpConfigOverride(claudeCodeMcpConfigPath)
|
|
64
|
+
}
|
package/tools/preview-main.mjs
CHANGED
|
@@ -20,7 +20,7 @@ let takeScreenshotConfig = null
|
|
|
20
20
|
try {
|
|
21
21
|
if (values['take-screenshot']) {
|
|
22
22
|
takeScreenshotConfig = JSON.parse(values['take-screenshot'])
|
|
23
|
-
if (!takeScreenshotConfig.path) takeScreenshotConfig.path = `${cwd}/screenshot.
|
|
23
|
+
if (!takeScreenshotConfig.path) takeScreenshotConfig.path = `${cwd}/screenshot.jpg`
|
|
24
24
|
if (!takeScreenshotConfig.width) takeScreenshotConfig.width = 1280
|
|
25
25
|
if (!takeScreenshotConfig.height) takeScreenshotConfig.height = 768
|
|
26
26
|
if (!takeScreenshotConfig.delay) takeScreenshotConfig.delay = 1000
|
|
@@ -45,7 +45,14 @@ const previewUrl = previewUrlMap[stage]
|
|
|
45
45
|
if (!previewUrl) throw new Error(`Invalid BRICKS_STAGE: ${stage}`)
|
|
46
46
|
|
|
47
47
|
app.on('ready', () => {
|
|
48
|
-
|
|
48
|
+
let show = true
|
|
49
|
+
if (takeScreenshotConfig && !takeScreenshotConfig.noHeadless) show = false
|
|
50
|
+
const mainWindow = new BrowserWindow({
|
|
51
|
+
width: takeScreenshotConfig?.width || 1280,
|
|
52
|
+
height: takeScreenshotConfig?.height || 768,
|
|
53
|
+
frame: !takeScreenshotConfig,
|
|
54
|
+
show,
|
|
55
|
+
})
|
|
49
56
|
mainWindow.setBackgroundColor('#333')
|
|
50
57
|
mainWindow.loadURL(previewUrl)
|
|
51
58
|
|
|
@@ -54,18 +61,19 @@ app.on('ready', () => {
|
|
|
54
61
|
type: 'config',
|
|
55
62
|
configFile: { _originTitle: 'Test', ...config, title: Math.random().toString() },
|
|
56
63
|
workspace: { billing: { lock: {}, plan: 'free' } },
|
|
64
|
+
showMenu: false,
|
|
57
65
|
}
|
|
58
66
|
mainWindow.webContents.executeJavaScript(
|
|
59
67
|
`window.postMessage(JSON.stringify(${JSON.stringify(payload)}))`,
|
|
60
68
|
)
|
|
61
69
|
if (takeScreenshotConfig) {
|
|
62
|
-
const { delay, width, height, path,
|
|
70
|
+
const { delay, width, height, path, keepOpen = false } = takeScreenshotConfig
|
|
63
71
|
setTimeout(() => {
|
|
64
72
|
console.log('Taking screenshot')
|
|
65
|
-
mainWindow.webContents.capturePage(
|
|
73
|
+
mainWindow.webContents.capturePage().then((image) => {
|
|
66
74
|
console.log('Writing screenshot to', path)
|
|
67
|
-
writeFile(path, image.
|
|
68
|
-
if (
|
|
75
|
+
writeFile(path, image.resize({ width, height }).toJPEG(75))
|
|
76
|
+
if (!keepOpen) {
|
|
69
77
|
console.log('Closing app')
|
|
70
78
|
app.quit()
|
|
71
79
|
}
|
package/tools/preview.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { $ } from 'bun'
|
|
2
2
|
import { watch } from 'fs'
|
|
3
|
+
import type { FSWatcher } from 'fs'
|
|
3
4
|
import { parseArgs } from 'util'
|
|
4
5
|
import { debounce } from 'lodash'
|
|
5
6
|
|
|
@@ -8,52 +9,60 @@ const { values } = parseArgs({
|
|
|
8
9
|
options: {
|
|
9
10
|
'skip-typecheck': { type: 'boolean' },
|
|
10
11
|
'clear-cache': { type: 'boolean' },
|
|
12
|
+
screenshot: { type: 'boolean' },
|
|
11
13
|
'screenshot-delay': { type: 'string' },
|
|
12
14
|
'screenshot-width': { type: 'string' },
|
|
13
15
|
'screenshot-height': { type: 'string' },
|
|
14
16
|
'screenshot-path': { type: 'string' },
|
|
15
|
-
'screenshot-
|
|
17
|
+
'screenshot-keep-open': { type: 'boolean' },
|
|
18
|
+
'screenshot-no-headless': { type: 'boolean' },
|
|
16
19
|
},
|
|
17
20
|
strict: true,
|
|
18
21
|
allowPositionals: true,
|
|
19
22
|
})
|
|
20
23
|
|
|
21
|
-
const useTypecheck = !values['skip-typecheck']
|
|
22
|
-
|
|
23
|
-
const compile = async () => {
|
|
24
|
-
if (useTypecheck) await $`bun typecheck`
|
|
25
|
-
await $`bun compile`
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
await compile()
|
|
29
|
-
|
|
30
|
-
const compileDebounced = debounce(compile, 500)
|
|
31
|
-
|
|
32
24
|
const cwd = process.cwd()
|
|
33
25
|
|
|
34
|
-
const watcher = watch(`${cwd}/subspaces`, { recursive: true }, async (event, filename) => {
|
|
35
|
-
console.log(`Detected ${event} in ${filename}`)
|
|
36
|
-
compileDebounced()
|
|
37
|
-
})
|
|
38
|
-
|
|
39
26
|
const app = await Bun.file(`${cwd}/application.json`).json()
|
|
40
27
|
|
|
41
28
|
let args: string[] = []
|
|
42
29
|
if (values['clear-cache']) args.push('--clear-cache')
|
|
43
30
|
|
|
44
|
-
|
|
31
|
+
let needWatcher = true
|
|
32
|
+
if (values['screenshot']) {
|
|
45
33
|
args.push(`--take-screenshot`)
|
|
34
|
+
const keepOpen = values['screenshot-keep-open'] ?? false
|
|
46
35
|
args.push(
|
|
47
36
|
JSON.stringify({
|
|
48
|
-
delay: Number(values['
|
|
49
|
-
width: Number(values['screenshot-width']) ||
|
|
50
|
-
height: Number(values['screenshot-height']) ||
|
|
51
|
-
path: values['screenshot-path'] || `${cwd}/screenshot.
|
|
52
|
-
|
|
37
|
+
delay: Number(values['screenshot-delay']) || 1000,
|
|
38
|
+
width: Number(values['screenshot-width']) || 600,
|
|
39
|
+
height: Number(values['screenshot-height']) || 480,
|
|
40
|
+
path: values['screenshot-path'] || `${cwd}/screenshot.jpg`,
|
|
41
|
+
keepOpen,
|
|
42
|
+
noHeadless: values['screenshot-no-headless'] ?? false,
|
|
53
43
|
}),
|
|
54
44
|
)
|
|
45
|
+
needWatcher = keepOpen
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const useTypecheck = !values['skip-typecheck']
|
|
49
|
+
|
|
50
|
+
const compile = async () => {
|
|
51
|
+
if (useTypecheck) await $`bun typecheck`
|
|
52
|
+
await $`bun compile`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await compile()
|
|
56
|
+
|
|
57
|
+
let watcher: FSWatcher | null = null
|
|
58
|
+
if (needWatcher) {
|
|
59
|
+
const compileDebounced = debounce(compile, 500)
|
|
60
|
+
watcher = watch(`${cwd}/subspaces`, { recursive: true }, async (event, filename) => {
|
|
61
|
+
console.log(`Detected ${event} in ${filename}`)
|
|
62
|
+
compileDebounced()
|
|
63
|
+
})
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
await $`BRICKS_STAGE=${app.stage || 'production'} bunx --bun electron ${__dirname}/preview-main.mjs ${args}`
|
|
58
67
|
|
|
59
|
-
watcher.close()
|
|
68
|
+
if (watcher) watcher.close()
|
package/utils/event-props.ts
CHANGED
|
@@ -221,10 +221,6 @@ export const templateEventPropsMap = {
|
|
|
221
221
|
'BRICK_GENERATIVE_MEDIA_ERROR', // type: string
|
|
222
222
|
],
|
|
223
223
|
},
|
|
224
|
-
BRICK_CHART_LINE: {},
|
|
225
|
-
BRICK_CHART_BAR: {},
|
|
226
|
-
BRICK_CHART_PIE: {},
|
|
227
|
-
BRICK_CHART_PROGRESS: {},
|
|
228
224
|
GENERATOR_TICK: {
|
|
229
225
|
ticking: [
|
|
230
226
|
'GENERATOR_TICK_COUNTDOWN', // type: number
|
|
@@ -332,7 +328,6 @@ export const templateEventPropsMap = {
|
|
|
332
328
|
'GENERATOR_FILE_LIST_PATHS', // type: array
|
|
333
329
|
],
|
|
334
330
|
},
|
|
335
|
-
GENERATOR_URL_FILE_SYNC: {},
|
|
336
331
|
GENERATOR_MEDIA_FLOW: {
|
|
337
332
|
fetchError: [
|
|
338
333
|
'GENERATOR_MEDIA_FLOW_ERROR', // type: string
|
|
@@ -548,11 +543,6 @@ export const templateEventPropsMap = {
|
|
|
548
543
|
'GENERATOR_WEBRTC_TEXT_MESSAGE', // type: string
|
|
549
544
|
],
|
|
550
545
|
},
|
|
551
|
-
GENERATOR_TENSORFLOW_INFERENCE: {
|
|
552
|
-
onError: [
|
|
553
|
-
'GENERATOR_TENSORFLOW_INFERENCE_ERROR_MESSAGE', // type: string
|
|
554
|
-
],
|
|
555
|
-
},
|
|
556
546
|
GENERATOR_WEB_CRAWLER: {},
|
|
557
547
|
GENERATOR_SOUND_RECORDER: {
|
|
558
548
|
chunk: [
|
|
@@ -563,11 +553,6 @@ export const templateEventPropsMap = {
|
|
|
563
553
|
],
|
|
564
554
|
},
|
|
565
555
|
GENERATOR_BLE_PERIPHERAL: {},
|
|
566
|
-
GENERATOR_PROMPT: {
|
|
567
|
-
onError: [
|
|
568
|
-
'GENERATOR_PROMPT_ERROR_MESSAGE', // type: string
|
|
569
|
-
],
|
|
570
|
-
},
|
|
571
556
|
GENERATOR_QUESTION: {
|
|
572
557
|
onError: [
|
|
573
558
|
'GENERATOR_QUESTION_ERROR_MESSAGE', // type: string
|