@fugood/bricks-project 2.23.0 → 2.23.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.
Files changed (93) hide show
  1. package/compile/action-name-map.ts +26 -0
  2. package/compile/index.ts +353 -130
  3. package/package.json +6 -4
  4. package/skills/bricks-project/rules/automations.md +74 -28
  5. package/tools/deploy.ts +39 -10
  6. package/tools/mcp-server.ts +21 -13
  7. package/tools/postinstall.ts +53 -6
  8. package/tools/preview-main.mjs +8 -7
  9. package/tools/preview.ts +1 -1
  10. package/tsconfig.json +16 -0
  11. package/types/bricks/Camera.ts +1 -1
  12. package/types/bricks/Chart.ts +1 -1
  13. package/types/bricks/GenerativeMedia.ts +1 -1
  14. package/types/bricks/Icon.ts +1 -1
  15. package/types/bricks/Image.ts +1 -1
  16. package/types/bricks/Items.ts +1 -1
  17. package/types/bricks/Lottie.ts +1 -1
  18. package/types/bricks/Maps.ts +1 -1
  19. package/types/bricks/QrCode.ts +1 -1
  20. package/types/bricks/Rect.ts +1 -1
  21. package/types/bricks/RichText.ts +1 -1
  22. package/types/bricks/Rive.ts +1 -1
  23. package/types/bricks/Slideshow.ts +1 -1
  24. package/types/bricks/Svg.ts +1 -1
  25. package/types/bricks/Text.ts +1 -1
  26. package/types/bricks/TextInput.ts +1 -1
  27. package/types/bricks/Video.ts +1 -1
  28. package/types/bricks/VideoStreaming.ts +1 -1
  29. package/types/bricks/WebRtcStream.ts +1 -1
  30. package/types/bricks/WebView.ts +1 -1
  31. package/types/canvas.ts +2 -2
  32. package/types/common.ts +4 -4
  33. package/types/generators/AlarmClock.ts +1 -1
  34. package/types/generators/Assistant.ts +1 -1
  35. package/types/generators/BleCentral.ts +1 -1
  36. package/types/generators/BlePeripheral.ts +1 -1
  37. package/types/generators/CanvasMap.ts +1 -1
  38. package/types/generators/CastlesPay.ts +1 -1
  39. package/types/generators/DataBank.ts +1 -1
  40. package/types/generators/File.ts +1 -1
  41. package/types/generators/GraphQl.ts +1 -1
  42. package/types/generators/Http.ts +1 -1
  43. package/types/generators/HttpServer.ts +1 -1
  44. package/types/generators/Information.ts +1 -1
  45. package/types/generators/Intent.ts +1 -1
  46. package/types/generators/Iterator.ts +1 -1
  47. package/types/generators/Keyboard.ts +1 -1
  48. package/types/generators/LlmAnthropicCompat.ts +1 -1
  49. package/types/generators/LlmAppleBuiltin.ts +1 -1
  50. package/types/generators/LlmGgml.ts +1 -1
  51. package/types/generators/LlmOnnx.ts +1 -1
  52. package/types/generators/LlmOpenAiCompat.ts +1 -1
  53. package/types/generators/LlmQualcommAiEngine.ts +1 -1
  54. package/types/generators/Mcp.ts +1 -1
  55. package/types/generators/McpServer.ts +1 -1
  56. package/types/generators/MediaFlow.ts +1 -1
  57. package/types/generators/MqttBroker.ts +1 -1
  58. package/types/generators/MqttClient.ts +1 -1
  59. package/types/generators/Question.ts +1 -1
  60. package/types/generators/RealtimeTranscription.ts +15 -7
  61. package/types/generators/RerankerGgml.ts +1 -1
  62. package/types/generators/SerialPort.ts +1 -1
  63. package/types/generators/SoundPlayer.ts +1 -1
  64. package/types/generators/SoundRecorder.ts +1 -1
  65. package/types/generators/SpeechToTextGgml.ts +6 -1
  66. package/types/generators/SpeechToTextOnnx.ts +1 -1
  67. package/types/generators/SpeechToTextPlatform.ts +1 -1
  68. package/types/generators/SqLite.ts +1 -1
  69. package/types/generators/Step.ts +1 -1
  70. package/types/generators/SttAppleBuiltin.ts +1 -1
  71. package/types/generators/Tcp.ts +1 -1
  72. package/types/generators/TcpServer.ts +1 -1
  73. package/types/generators/TextToSpeechAppleBuiltin.ts +1 -1
  74. package/types/generators/TextToSpeechGgml.ts +1 -1
  75. package/types/generators/TextToSpeechOnnx.ts +1 -1
  76. package/types/generators/TextToSpeechOpenAiLike.ts +1 -1
  77. package/types/generators/ThermalPrinter.ts +1 -1
  78. package/types/generators/Tick.ts +1 -1
  79. package/types/generators/Udp.ts +1 -1
  80. package/types/generators/VadGgml.ts +1 -1
  81. package/types/generators/VadOnnx.ts +201 -0
  82. package/types/generators/VadTraditional.ts +123 -0
  83. package/types/generators/VectorStore.ts +1 -1
  84. package/types/generators/Watchdog.ts +1 -1
  85. package/types/generators/WebCrawler.ts +1 -1
  86. package/types/generators/WebRtc.ts +1 -1
  87. package/types/generators/WebSocket.ts +1 -1
  88. package/types/generators/index.ts +2 -0
  89. package/utils/calc.ts +16 -8
  90. package/utils/event-props.ts +27 -0
  91. package/utils/id.ts +4 -0
  92. package/api/index.ts +0 -1
  93. package/api/instance.ts +0 -213
package/package.json CHANGED
@@ -1,22 +1,24 @@
1
1
  {
2
2
  "name": "@fugood/bricks-project",
3
- "version": "2.23.0",
3
+ "version": "2.23.3",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
+ "typecheck": "tsc --noEmit",
6
7
  "build": "bun scripts/build.js"
7
8
  },
8
9
  "dependencies": {
9
- "@fugood/bricks-cli": "^2.23.0",
10
+ "@fugood/bricks-cli": "^2.23.2",
10
11
  "@huggingface/gguf": "^0.3.2",
12
+ "@iarna/toml": "^3.0.0",
11
13
  "@modelcontextprotocol/sdk": "^1.15.0",
12
14
  "@toon-format/toon": "^2.1.0",
13
15
  "@types/escodegen": "^0.0.10",
16
+ "@types/bun": "^1.3.9",
14
17
  "@types/lodash": "^4.17.12",
15
18
  "acorn": "^8.13.0",
16
19
  "escodegen": "2.1.0",
17
20
  "fuse.js": "^7.0.0",
18
21
  "lodash": "^4.17.4",
19
22
  "uuid": "^8.3.1"
20
- },
21
- "gitHead": "398352b9923f97e914ac60acab519ca014aa6fb8"
23
+ }
22
24
  }
@@ -4,15 +4,16 @@ E2E testing and scheduled execution for BRICKS applications. Simulates user beha
4
4
 
5
5
  ## Automation Types
6
6
 
7
- | Type | Description |
8
- |------|-------------|
9
- | `launch` | Run on application launch (restarts app when run manually) |
10
- | `anytime` | Execute anytime via manual trigger |
11
- | `cron` | Scheduled execution using crontab expressions |
7
+ | Type | Description |
8
+ | --------- | ---------------------------------------------------------- |
9
+ | `launch` | Run on application launch (restarts app when run manually) |
10
+ | `anytime` | Execute anytime via manual trigger |
11
+ | `cron` | Scheduled execution using crontab expressions |
12
12
 
13
13
  ## Simulation Actions
14
14
 
15
15
  Automations can simulate:
16
+
16
17
  - **Brick Press**: Tap/click on bricks
17
18
  - **Key Events**: Key up/down for keyboard input
18
19
  - **HTTP Request**: API calls
@@ -21,6 +22,7 @@ Automations can simulate:
21
22
  ## Assertions
22
23
 
23
24
  Automations can validate:
25
+
24
26
  - **Brick Exists**: Check if brick is rendered
25
27
  - **Event Triggered**: Verify event from Brick/Generator/Canvas
26
28
  - **Canvas Changed**: Confirm canvas navigation
@@ -33,14 +35,14 @@ Automations can validate:
33
35
  ```typescript
34
36
  const testLoginFlow: AutomationTest = {
35
37
  __typename: 'AutomationTest',
36
- id: 'test-login-flow',
38
+ id: makeId('test'),
37
39
  title: 'Test Login Flow',
38
40
  timeout: 30000,
39
41
  trigger_type: 'launch',
40
42
  cases: [
41
43
  {
42
44
  __typename: 'TestCase',
43
- id: 'wait-login-canvas',
45
+ id: makeId('test_case'),
44
46
  name: 'Wait for login canvas',
45
47
  run: ['wait_until_canvas_change', () => mainSubspace, () => loginCanvas, 5000],
46
48
  exit_on_failed: true,
@@ -51,7 +53,7 @@ const testLoginFlow: AutomationTest = {
51
53
  },
52
54
  {
53
55
  __typename: 'TestCase',
54
- id: 'press-username',
56
+ id: makeId('test_case'),
55
57
  name: 'Press username input',
56
58
  run: ['brick_press', () => mainSubspace, () => usernameInput],
57
59
  exit_on_failed: true,
@@ -62,7 +64,7 @@ const testLoginFlow: AutomationTest = {
62
64
  },
63
65
  {
64
66
  __typename: 'TestCase',
65
- id: 'assert-username',
67
+ id: makeId('test_case'),
66
68
  name: 'Assert username value',
67
69
  run: ['assert_property', () => mainSubspace, () => usernameData, 'testuser'],
68
70
  exit_on_failed: true,
@@ -73,7 +75,7 @@ const testLoginFlow: AutomationTest = {
73
75
  },
74
76
  {
75
77
  __typename: 'TestCase',
76
- id: 'press-login',
78
+ id: makeId('test_case'),
77
79
  name: 'Press login button',
78
80
  run: ['brick_press', () => mainSubspace, () => loginButton],
79
81
  exit_on_failed: true,
@@ -84,7 +86,7 @@ const testLoginFlow: AutomationTest = {
84
86
  },
85
87
  {
86
88
  __typename: 'TestCase',
87
- id: 'wait-dashboard',
89
+ id: makeId('test_case'),
88
90
  name: 'Wait for dashboard',
89
91
  run: ['wait_until_canvas_change', () => mainSubspace, () => dashboardCanvas, 10000],
90
92
  exit_on_failed: true,
@@ -100,25 +102,58 @@ const testLoginFlow: AutomationTest = {
100
102
 
101
103
  ## Test Methods
102
104
 
103
- | Method | Signature | Description |
104
- |--------|-----------|-------------|
105
- | `brick_press` | `[subspace, brick, options?]` | Simulate brick press |
106
- | `brick_exists` | `[subspace, brick, frame?]` | Check brick exists |
107
- | `wait_until_brick_exists` | `[subspace, brick, timeout?, frame?]` | Wait for brick |
108
- | `wait_until_event_trigger` | `[subspace, sender, eventKey, timeout?]` | Wait for event |
109
- | `wait_until_canvas_change` | `[subspace, canvas, timeout?]` | Wait for canvas |
110
- | `keydown` | `[keyCode, pressedKey?, flags?]` | Key down event |
111
- | `keyup` | `[keyCode, pressedKey?, flags?]` | Key up event |
112
- | `http_request` | `[url, options?]` | HTTP request |
113
- | `assert_property` | `[subspace, property, value]` | Assert data value |
114
- | `wait_until_property_change` | `[subspace, property, value, timeout?]` | Wait for value |
115
- | `execute_action` | `[subspace, handler, action, params?, options?]` | Execute action |
116
- | `match_screenshot` | `[name, threshold?, maxRetry?]` | Screenshot compare |
117
- | `delay` | `[subspace?, property?, defaultValue?]` | Delay execution |
105
+ | Method | Signature | Description |
106
+ | ---------------------------- | ------------------------------------------------ | -------------------- |
107
+ | `brick_press` | `[subspace, brick, options?]` | Simulate brick press |
108
+ | `brick_exists` | `[subspace, brick, frame?]` | Check brick exists |
109
+ | `wait_until_brick_exists` | `[subspace, brick, timeout?, frame?]` | Wait for brick |
110
+ | `wait_until_event_trigger` | `[subspace, sender, eventKey, timeout?]` | Wait for event |
111
+ | `wait_until_canvas_change` | `[subspace, canvas, timeout?]` | Wait for canvas |
112
+ | `keydown` | `[keyCode, pressedKey?, flags?]` | Key down event |
113
+ | `keyup` | `[keyCode, pressedKey?, flags?]` | Key up event |
114
+ | `http_request` | `[url, options?]` | HTTP request |
115
+ | `assert_property` | `[subspace, property, value]` | Assert data value |
116
+ | `wait_until_property_change` | `[subspace, property, value, timeout?]` | Wait for value |
117
+ | `execute_action` | `[subspace, handler, action, params?, options?]` | Execute action |
118
+ | `match_screenshot` | `[name, threshold?, maxRetry?]` | Screenshot compare |
119
+ | `delay` | `[subspace?, property?, defaultValue?]` | Delay execution |
120
+
121
+ ### execute_action Params
122
+
123
+ The `params` object in `execute_action` uses **runtime event property keys** from `event-props.ts`, NOT the action config `input` names from type definitions.
124
+
125
+ ```typescript
126
+ // CORRECT — use runtime event property key
127
+ run: ['execute_action', () => subspace0, bricks.bInput.id, 'BRICK_TEXT_INPUT_SET_TEXT',
128
+ { BRICK_TEXT_INPUT_TEXT: 'hello' }]
129
+
130
+ // WRONG — action config input name doesn't work in automation
131
+ run: ['execute_action', () => subspace0, bricks.bInput.id, 'BRICK_TEXT_INPUT_SET_TEXT',
132
+ { text: 'hello' }]
133
+ ```
134
+
135
+ Reference `event-props.ts` for the correct runtime keys (e.g., `BRICK_TEXT_INPUT_TEXT`, `GENERATOR_MQTT_PAYLOAD`).
136
+
137
+ ### Prefer UI Interactions Over Direct Generator Calls
138
+
139
+ For realistic E2E testing, prefer simulating user actions (set text input + press button) over calling generator actions directly:
140
+
141
+ ```typescript
142
+ // GOOD — simulates real user behavior
143
+ { run: ['execute_action', () => sub, bricks.bInput.id, 'BRICK_TEXT_INPUT_SET_TEXT',
144
+ { BRICK_TEXT_INPUT_TEXT: 'hello' }] },
145
+ { run: ['brick_press', () => sub, () => bricks.bSendBtn] },
146
+ { run: ['wait_until_property_change', () => sub, () => data.dPayload, 'hello', 10000] },
147
+
148
+ // AVOID — bypasses UI, doesn't test the full flow
149
+ { run: ['execute_action', () => sub, generators.gClient.id, 'GENERATOR_MQTT_PUBLISH',
150
+ { topic: 'test', payload: 'hello', qos: '0' }] },
151
+ ```
118
152
 
119
153
  ## Recording Automations
120
154
 
121
155
  In BRICKS Editor Preview mode:
156
+
122
157
  1. Perform operations normally
123
158
  2. Open menu (right-bottom corner)
124
159
  3. Select "Record Events as Automation"
@@ -127,12 +162,15 @@ In BRICKS Editor Preview mode:
127
162
  ## Running Automations
128
163
 
129
164
  ### Manual Run
165
+
130
166
  `Menu` → `Automations` → Select automation → `Run`
131
167
 
132
168
  ### On Launch
169
+
133
170
  `Bind Device` → `Select Automation` (only `launch` or `cron` types)
134
171
 
135
172
  ### Scheduled (Cron)
173
+
136
174
  `Bind Device` → `Cron Automation` (allows multi-select)
137
175
 
138
176
  Use [crontab.guru](https://crontab.guru) to build cron expressions.
@@ -144,7 +182,7 @@ Visual regression testing with screenshot comparison:
144
182
  ```typescript
145
183
  {
146
184
  __typename: 'TestCase',
147
- id: 'screenshot-dashboard',
185
+ id: makeId('test_case'),
148
186
  name: 'Match dashboard screenshot',
149
187
  run: ['match_screenshot', 'dashboard-initial-state', 0.01, 3],
150
188
  exit_on_failed: true,
@@ -156,6 +194,7 @@ Visual regression testing with screenshot comparison:
156
194
  ```
157
195
 
158
196
  Screenshots can be stored:
197
+
159
198
  - Local file system
160
199
  - Media Flow workspace
161
200
 
@@ -165,11 +204,18 @@ First run captures baseline. Use "Run with Update" to update baseline.
165
204
 
166
205
  Automations work with Modules. Use Manual Run in Preview mode for module testing.
167
206
 
207
+ ## Important Notes
208
+
209
+ - **Automation map key**: Always use `'AUTOMATION_MAP_DEFAULT'` as the automation map ID (not `makeId()`). The preview test runner reads from `automationMap['AUTOMATION_MAP_DEFAULT']?.map`.
210
+ - **Valid makeId types**: Use `'test'` for AutomationTest, `'test_case'` for TestCase, `'test_var'` for TestVariable. Do NOT use `'automation_test'` or `'automation_test_map'`.
211
+ - **handler in execute_action**: Pass the entity's `.id` string (e.g., `bricks.bInput.id`), not a getter function.
212
+
168
213
  ## Best Practices
169
214
 
170
215
  1. **Test culture**: Create automations for every significant flow
171
216
  2. **CI/CD integration**: Use `launch` automations for deployment validation
172
- 3. **Incremental waits**: Use `EXPECT_*` steps with appropriate timeouts
217
+ 3. **Incremental waits**: Use `wait_until_property_change` with appropriate timeouts
173
218
  4. **Visual testing**: Add screenshot comparisons for critical UI states
174
219
  5. **Cron monitoring**: Schedule health checks for production displays
175
220
  6. **Isolation**: Each automation should be independent and idempotent
221
+ 7. **UI-first testing**: Simulate real user interactions (text input, button press) rather than calling generators directly
package/tools/deploy.ts CHANGED
@@ -4,29 +4,40 @@ import { parseArgs } from 'util'
4
4
  const cwd = process.cwd()
5
5
 
6
6
  const {
7
- values: { changelogs: changelogsArg, 'changelogs-file': changelogsFile, yes },
7
+ values: {
8
+ changelogs: changelogsArg,
9
+ 'changelogs-file': changelogsFile,
10
+ 'auto-commit': autoCommit,
11
+ yes,
12
+ help,
13
+ },
8
14
  } = parseArgs({
9
15
  args: Bun.argv.slice(2),
10
16
  options: {
11
17
  changelogs: { type: 'string' },
12
18
  'changelogs-file': { type: 'string' },
19
+ 'auto-commit': { type: 'boolean' },
13
20
  yes: { type: 'boolean', short: 'y' },
21
+ help: { type: 'boolean', short: 'h' },
14
22
  },
15
23
  allowPositionals: true,
16
24
  })
17
25
 
26
+ if (help) {
27
+ console.log(`Options:
28
+ --changelogs <text> Changelogs text for the release
29
+ --changelogs-file <path> Read changelogs from a file
30
+ --auto-commit Auto-commit unstaged changes before deploying
31
+ -y, --yes Skip all prompts
32
+ -h, --help Show this help message`)
33
+ process.exit(0) // eslint-disable-line unicorn/no-process-exit
34
+ }
35
+
18
36
  // Check git status
19
- const { exitCode } = await $`cd ${cwd} && git status`.nothrow()
37
+ const { exitCode } = await $`cd ${cwd} && git status`.quiet().nothrow()
20
38
  const isGitRepo = exitCode === 0
21
39
 
22
- let commitId = ''
23
- if (isGitRepo) {
24
- const unstagedChanges = await $`cd ${cwd} && git diff --name-only --diff-filter=ACMR`.text()
25
- if (unstagedChanges)
26
- throw new Error('Unstaged changes found, please commit or stash your changes before deploying')
27
-
28
- commitId = (await $`cd ${cwd} && git rev-parse HEAD`.text()).trim()
29
- } else if (!yes) {
40
+ if (!isGitRepo && !yes) {
30
41
  const confirmContinue = prompt('No git repository found, continue? (y/n)')
31
42
  if (confirmContinue !== 'y') throw new Error('Deployment cancelled')
32
43
  }
@@ -53,6 +64,24 @@ if (changelogsArg) {
53
64
  changelogs = prompt('Enter changelogs (optional, press Enter to skip):') || ''
54
65
  }
55
66
 
67
+ // Handle unstaged changes
68
+ let commitId = ''
69
+ if (isGitRepo) {
70
+ const unstagedChanges = await $`cd ${cwd} && git diff --name-only --diff-filter=ACMR`.text()
71
+ if (unstagedChanges) {
72
+ if (autoCommit) {
73
+ const commitMsg = `chore: release ${version || 'new version'}`
74
+ const commitBody = changelogs || '[no changelogs]'
75
+ await $`cd ${cwd} && git add -A && git commit -m ${commitMsg} -m ${commitBody}`
76
+ } else {
77
+ throw new Error(
78
+ 'Unstaged changes found, please commit or stash your changes before deploying',
79
+ )
80
+ }
81
+ }
82
+ commitId = (await $`cd ${cwd} && git rev-parse HEAD`.text()).trim()
83
+ }
84
+
56
85
  // Ask for confirmation
57
86
  if (!yes) {
58
87
  const confirm = prompt('Are you sure you want to deploy? (y/n)')
@@ -1,7 +1,7 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
3
3
  import { z } from 'zod'
4
- import { $ } from 'bun'
4
+ import { $, JSON5 } from 'bun'
5
5
  import * as TOON from '@toon-format/toon'
6
6
  import Fuse from 'fuse.js'
7
7
  import { gguf } from '@huggingface/gguf'
@@ -172,20 +172,23 @@ server.tool(
172
172
  log = `${err.stdout.toString()}\n${err.stderr.toString()}`
173
173
  error = true
174
174
  }
175
- let screenshotBase64: any = null
175
+ let screenshotBase64: string | null = null
176
176
  if (!error && responseImage) {
177
177
  const screenshot = await Bun.file(`${dirname}/screenshot.jpg`).arrayBuffer()
178
178
  screenshotBase64 = Buffer.from(screenshot).toString('base64')
179
179
  }
180
+ const content: Array<
181
+ { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }
182
+ > = [{ type: 'text', text: log }]
183
+ if (screenshotBase64) {
184
+ content.push({
185
+ type: 'image',
186
+ data: screenshotBase64,
187
+ mimeType: 'image/jpeg',
188
+ })
189
+ }
180
190
  return {
181
- content: [
182
- { type: 'text', text: log },
183
- screenshotBase64 && {
184
- type: 'image',
185
- data: screenshotBase64,
186
- mimeType: 'image/jpeg',
187
- },
188
- ].filter(Boolean),
191
+ content,
189
192
  }
190
193
  },
191
194
  )
@@ -729,7 +732,7 @@ server.tool(
729
732
 
730
733
  // Return JSON for huggingface_select to allow direct parsing by consumers
731
734
  return {
732
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
735
+ content: [{ type: 'text', text: JSON5.stringify(result, null, 2) }],
733
736
  }
734
737
  }
735
738
 
@@ -842,7 +845,7 @@ server.tool(
842
845
 
843
846
  // Return JSON for huggingface_select to allow direct parsing by consumers
844
847
  return {
845
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
848
+ content: [{ type: 'text', text: JSON5.stringify(result, null, 2) }],
846
849
  }
847
850
  } else {
848
851
  const result = {
@@ -875,7 +878,12 @@ server.tool(
875
878
 
876
879
  // Return JSON for huggingface_select to allow direct parsing by consumers
877
880
  return {
878
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
881
+ content: [
882
+ {
883
+ type: 'text',
884
+ text: `${JSON5.stringify(result, null, 2)} // Please use _hfRepoInfo undocumented field with @ts-ignore`,
885
+ },
886
+ ],
879
887
  }
880
888
  }
881
889
  } catch (err: any) {
@@ -1,5 +1,6 @@
1
1
  import { $ } from 'bun'
2
2
  import { stat, readFile, writeFile, readdir } from 'fs/promises'
3
+ import TOML from '@iarna/toml'
3
4
 
4
5
  const cwd = process.cwd()
5
6
 
@@ -29,6 +30,10 @@ const projectMcpServer = {
29
30
  args: [`${cwd}/node_modules/@fugood/bricks-project/tools/mcp-server.ts`],
30
31
  }
31
32
 
33
+ type CodexMcpConfig = {
34
+ mcp_servers: Record<string, typeof projectMcpServer>
35
+ }
36
+
32
37
  const defaultMcpConfig = {
33
38
  mcpServers: {
34
39
  'bricks-project': projectMcpServer,
@@ -43,7 +48,7 @@ const handleMcpConfigOverride = async (mcpConfigPath: string) => {
43
48
  mcpConfig = JSON.parse(configStr)
44
49
  if (!mcpConfig?.mcpServers) throw new Error('mcpServers is not defined')
45
50
  mcpConfig.mcpServers['bricks-project'] = projectMcpServer
46
- } catch (e) {
51
+ } catch {
47
52
  mcpConfig = defaultMcpConfig
48
53
  }
49
54
  } else {
@@ -63,9 +68,7 @@ if (hasClaudeCode || hasAgentsMd) {
63
68
  await handleMcpConfigOverride(mcpConfigPath)
64
69
  }
65
70
 
66
- if (hasClaudeCode) {
67
- // Install skills that don't already exist in the project
68
- const skillsDir = `${cwd}/.claude/skills`
71
+ const setupSkills = async (skillsDir) => {
69
72
  const packageSkillsDir = `${__dirname}/../skills`
70
73
 
71
74
  if (await exists(packageSkillsDir)) {
@@ -81,10 +84,54 @@ if (hasClaudeCode) {
81
84
  console.log(`Skill '${skill}' already exists, skipping`)
82
85
  } else {
83
86
  await $`cp -r ${packageSkillsDir}/${skill} ${targetSkillDir}`
84
- console.log(`Installed skill '${skill}' to .claude/skills/`)
87
+ console.log(`Installed skill '${skill}' to ${skillsDir}/`)
85
88
  }
86
89
  }),
87
90
  )
88
91
  }
89
- // TODO: .codex/skills, .cursor/skills if needed
90
92
  }
93
+
94
+ if (hasClaudeCode) {
95
+ // Install skills that don't already exist in the project
96
+ await setupSkills(`${cwd}/.claude/skills`)
97
+ }
98
+
99
+ if (hasAgentsMd) {
100
+ // Handle codex skills
101
+ // Currently no signal file for codex skills, so we just check if AGENTS.md exists
102
+ await setupSkills(`${cwd}/.codex/skills`)
103
+
104
+ const defaultCodexMcpConfig = {
105
+ mcp_servers: {
106
+ 'bricks-project': projectMcpServer,
107
+ },
108
+ }
109
+
110
+ const handleCodexMcpConfigOverride = async (mcpConfigPath: string) => {
111
+ let mcpConfig: CodexMcpConfig | null = null
112
+ if (await exists(mcpConfigPath)) {
113
+ const configStr = await readFile(mcpConfigPath, 'utf-8')
114
+ try {
115
+ const parsed = TOML.parse(configStr) as Partial<CodexMcpConfig>
116
+ if (!parsed?.mcp_servers) throw new Error('mcp_servers is not defined')
117
+ mcpConfig = { mcp_servers: parsed.mcp_servers }
118
+ mcpConfig.mcp_servers['bricks-project'] = projectMcpServer
119
+ } catch {
120
+ mcpConfig = defaultCodexMcpConfig
121
+ }
122
+ } else {
123
+ mcpConfig = defaultCodexMcpConfig
124
+ }
125
+
126
+ await writeFile(mcpConfigPath, `${TOML.stringify(mcpConfig)}\n`)
127
+
128
+ console.log(`Updated ${mcpConfigPath}`)
129
+ }
130
+
131
+ // Setup MCP config (.codex/config.toml)
132
+ const codexConfigPath = `${cwd}/.codex/config.toml`
133
+ await handleCodexMcpConfigOverride(codexConfigPath)
134
+ }
135
+
136
+ // TODO: .cursor/skills if needed
137
+ // TODO: User setting in application.json to avoid unnecessary skills/config setup
@@ -42,13 +42,14 @@ let config = JSON.parse(await readFile(`${cwd}/.bricks/build/application-config.
42
42
  let testId = values['test-id'] || null
43
43
  if (!testId && values['test-title-like']) {
44
44
  const titleLike = values['test-title-like'].toLowerCase()
45
- const testMap = config.test_map || {}
46
- const found = Object.entries(testMap).find(([, test]) =>
47
- test.title?.toLowerCase().includes(titleLike),
48
- )
49
- if (found) {
50
- ;[testId] = found
51
- } else {
45
+ const automationMap = config.automation_map || {}
46
+ const matchedEntry = Object.values(automationMap)
47
+ .flatMap((group) => Object.entries(group.map || {}))
48
+ .find(([, test]) => test.title?.toLowerCase().includes(titleLike))
49
+ if (matchedEntry) {
50
+ ;[testId] = matchedEntry
51
+ }
52
+ if (!testId) {
52
53
  throw new Error(`No automation found matching title: ${values['test-title-like']}`)
53
54
  }
54
55
  }
package/tools/preview.ts CHANGED
@@ -2,7 +2,7 @@ import { $ } from 'bun'
2
2
  import { watch } from 'fs'
3
3
  import type { FSWatcher } from 'fs'
4
4
  import { parseArgs } from 'util'
5
- import { debounce } from 'lodash'
5
+ import debounce from 'lodash/debounce'
6
6
 
7
7
  const { values } = parseArgs({
8
8
  args: Bun.argv,
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext"],
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleDetection": "force",
7
+ "allowJs": true,
8
+ "resolveJsonModule": true,
9
+ "moduleResolution": "bundler",
10
+ "allowImportingTsExtensions": true,
11
+ "verbatimModuleSyntax": true,
12
+ "noEmit": true,
13
+ "skipLibCheck": true,
14
+ },
15
+ "exclude": ["node_modules"],
16
+ }
@@ -192,7 +192,7 @@ Default property:
192
192
  export type BrickCamera = Brick &
193
193
  BrickCameraDef & {
194
194
  templateKey: 'BRICK_CAMERA'
195
- switches: Array<
195
+ switches?: Array<
196
196
  SwitchDef &
197
197
  BrickCameraDef & {
198
198
  conds?: Array<{
@@ -347,7 +347,7 @@ Default property:
347
347
  export type BrickChart = Brick &
348
348
  BrickChartDef & {
349
349
  templateKey: 'BRICK_CHART'
350
- switches: Array<
350
+ switches?: Array<
351
351
  SwitchDef &
352
352
  BrickChartDef & {
353
353
  conds?: Array<{
@@ -241,7 +241,7 @@ Default property:
241
241
  export type GenerativeMedia = Brick &
242
242
  GenerativeMediaDef & {
243
243
  templateKey: 'BRICK_GENERATIVE_MEDIA'
244
- switches: Array<
244
+ switches?: Array<
245
245
  SwitchDef &
246
246
  GenerativeMediaDef & {
247
247
  conds?: Array<{
@@ -75,7 +75,7 @@ Default property:
75
75
  export type BrickIcon = Brick &
76
76
  BrickIconDef & {
77
77
  templateKey: 'BRICK_ICON'
78
- switches: Array<
78
+ switches?: Array<
79
79
  SwitchDef &
80
80
  BrickIconDef & {
81
81
  conds?: Array<{
@@ -91,7 +91,7 @@ Default property:
91
91
  export type BrickImage = Brick &
92
92
  BrickImageDef & {
93
93
  templateKey: 'BRICK_IMAGE'
94
- switches: Array<
94
+ switches?: Array<
95
95
  SwitchDef &
96
96
  BrickImageDef & {
97
97
  conds?: Array<{
@@ -437,7 +437,7 @@ Default property:
437
437
  export type BrickItems = Brick &
438
438
  BrickItemsDef & {
439
439
  templateKey: 'BRICK_ITEMS'
440
- switches: Array<
440
+ switches?: Array<
441
441
  SwitchDef &
442
442
  BrickItemsDef & {
443
443
  conds?: Array<{
@@ -141,7 +141,7 @@ Default property:
141
141
  export type BrickLottie = Brick &
142
142
  BrickLottieDef & {
143
143
  templateKey: 'BRICK_LOTTIE'
144
- switches: Array<
144
+ switches?: Array<
145
145
  SwitchDef &
146
146
  BrickLottieDef & {
147
147
  conds?: Array<{
@@ -235,7 +235,7 @@ Default property:
235
235
  export type BrickMaps = Brick &
236
236
  BrickMapsDef & {
237
237
  templateKey: 'BRICK_MAPS'
238
- switches: Array<
238
+ switches?: Array<
239
239
  SwitchDef &
240
240
  BrickMapsDef & {
241
241
  conds?: Array<{
@@ -94,7 +94,7 @@ Default property:
94
94
  export type BrickQrcode = Brick &
95
95
  BrickQrcodeDef & {
96
96
  templateKey: 'BRICK_QRCODE'
97
- switches: Array<
97
+ switches?: Array<
98
98
  SwitchDef &
99
99
  BrickQrcodeDef & {
100
100
  conds?: Array<{
@@ -92,7 +92,7 @@ Default property:
92
92
  export type BrickRect = Brick &
93
93
  BrickRectDef & {
94
94
  templateKey: 'BRICK_RECT'
95
- switches: Array<
95
+ switches?: Array<
96
96
  SwitchDef &
97
97
  BrickRectDef & {
98
98
  conds?: Array<{
@@ -105,7 +105,7 @@ Default property:
105
105
  export type BrickRichText = Brick &
106
106
  BrickRichTextDef & {
107
107
  templateKey: 'BRICK_RICH_TEXT'
108
- switches: Array<
108
+ switches?: Array<
109
109
  SwitchDef &
110
110
  BrickRichTextDef & {
111
111
  conds?: Array<{
@@ -191,7 +191,7 @@ Default property:
191
191
  export type BrickRive = Brick &
192
192
  BrickRiveDef & {
193
193
  templateKey: 'BRICK_RIVE'
194
- switches: Array<
194
+ switches?: Array<
195
195
  SwitchDef &
196
196
  BrickRiveDef & {
197
197
  conds?: Array<{
@@ -172,7 +172,7 @@ Default property:
172
172
  export type BrickSlideshow = Brick &
173
173
  BrickSlideshowDef & {
174
174
  templateKey: 'BRICK_SLIDESHOW'
175
- switches: Array<
175
+ switches?: Array<
176
176
  SwitchDef &
177
177
  BrickSlideshowDef & {
178
178
  conds?: Array<{