@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 CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@fugood/bricks-project",
3
- "version": "2.22.0-beta.1",
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": "c55f6effe15a6c481e6fc1147f8bff0c747169ef"
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)
@@ -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
+ }
@@ -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.png`
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
- const mainWindow = new BrowserWindow({ width: 1280, height: 768 })
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, closeAfter } = takeScreenshotConfig
70
+ const { delay, width, height, path, keepOpen = false } = takeScreenshotConfig
63
71
  setTimeout(() => {
64
72
  console.log('Taking screenshot')
65
- mainWindow.webContents.capturePage({ width, height }).then((image) => {
73
+ mainWindow.webContents.capturePage().then((image) => {
66
74
  console.log('Writing screenshot to', path)
67
- writeFile(path, image.toPNG())
68
- if (closeAfter) {
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-close-after': { type: 'boolean' },
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
- if (values['screenshot-delay'] || values['screenshot-close-after']) {
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['take-screenshot-delay']) || 1000,
49
- width: Number(values['screenshot-width']) || 1920,
50
- height: Number(values['screenshot-height']) || 1080,
51
- path: values['screenshot-path'] || `${cwd}/screenshot.png`,
52
- closeAfter: values['take-screenshot-close-after'] ?? true,
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()
@@ -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