@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.
- package/compile/action-name-map.ts +26 -0
- package/compile/index.ts +353 -130
- package/package.json +6 -4
- package/skills/bricks-project/rules/automations.md +74 -28
- package/tools/deploy.ts +39 -10
- package/tools/mcp-server.ts +21 -13
- package/tools/postinstall.ts +53 -6
- package/tools/preview-main.mjs +8 -7
- package/tools/preview.ts +1 -1
- package/tsconfig.json +16 -0
- package/types/bricks/Camera.ts +1 -1
- package/types/bricks/Chart.ts +1 -1
- package/types/bricks/GenerativeMedia.ts +1 -1
- package/types/bricks/Icon.ts +1 -1
- package/types/bricks/Image.ts +1 -1
- package/types/bricks/Items.ts +1 -1
- package/types/bricks/Lottie.ts +1 -1
- package/types/bricks/Maps.ts +1 -1
- package/types/bricks/QrCode.ts +1 -1
- package/types/bricks/Rect.ts +1 -1
- package/types/bricks/RichText.ts +1 -1
- package/types/bricks/Rive.ts +1 -1
- package/types/bricks/Slideshow.ts +1 -1
- package/types/bricks/Svg.ts +1 -1
- package/types/bricks/Text.ts +1 -1
- package/types/bricks/TextInput.ts +1 -1
- package/types/bricks/Video.ts +1 -1
- package/types/bricks/VideoStreaming.ts +1 -1
- package/types/bricks/WebRtcStream.ts +1 -1
- package/types/bricks/WebView.ts +1 -1
- package/types/canvas.ts +2 -2
- package/types/common.ts +4 -4
- package/types/generators/AlarmClock.ts +1 -1
- package/types/generators/Assistant.ts +1 -1
- package/types/generators/BleCentral.ts +1 -1
- package/types/generators/BlePeripheral.ts +1 -1
- package/types/generators/CanvasMap.ts +1 -1
- package/types/generators/CastlesPay.ts +1 -1
- package/types/generators/DataBank.ts +1 -1
- package/types/generators/File.ts +1 -1
- package/types/generators/GraphQl.ts +1 -1
- package/types/generators/Http.ts +1 -1
- package/types/generators/HttpServer.ts +1 -1
- package/types/generators/Information.ts +1 -1
- package/types/generators/Intent.ts +1 -1
- package/types/generators/Iterator.ts +1 -1
- package/types/generators/Keyboard.ts +1 -1
- package/types/generators/LlmAnthropicCompat.ts +1 -1
- package/types/generators/LlmAppleBuiltin.ts +1 -1
- package/types/generators/LlmGgml.ts +1 -1
- package/types/generators/LlmOnnx.ts +1 -1
- package/types/generators/LlmOpenAiCompat.ts +1 -1
- package/types/generators/LlmQualcommAiEngine.ts +1 -1
- package/types/generators/Mcp.ts +1 -1
- package/types/generators/McpServer.ts +1 -1
- package/types/generators/MediaFlow.ts +1 -1
- package/types/generators/MqttBroker.ts +1 -1
- package/types/generators/MqttClient.ts +1 -1
- package/types/generators/Question.ts +1 -1
- package/types/generators/RealtimeTranscription.ts +15 -7
- package/types/generators/RerankerGgml.ts +1 -1
- package/types/generators/SerialPort.ts +1 -1
- package/types/generators/SoundPlayer.ts +1 -1
- package/types/generators/SoundRecorder.ts +1 -1
- package/types/generators/SpeechToTextGgml.ts +6 -1
- package/types/generators/SpeechToTextOnnx.ts +1 -1
- package/types/generators/SpeechToTextPlatform.ts +1 -1
- package/types/generators/SqLite.ts +1 -1
- package/types/generators/Step.ts +1 -1
- package/types/generators/SttAppleBuiltin.ts +1 -1
- package/types/generators/Tcp.ts +1 -1
- package/types/generators/TcpServer.ts +1 -1
- package/types/generators/TextToSpeechAppleBuiltin.ts +1 -1
- package/types/generators/TextToSpeechGgml.ts +1 -1
- package/types/generators/TextToSpeechOnnx.ts +1 -1
- package/types/generators/TextToSpeechOpenAiLike.ts +1 -1
- package/types/generators/ThermalPrinter.ts +1 -1
- package/types/generators/Tick.ts +1 -1
- package/types/generators/Udp.ts +1 -1
- package/types/generators/VadGgml.ts +1 -1
- package/types/generators/VadOnnx.ts +201 -0
- package/types/generators/VadTraditional.ts +123 -0
- package/types/generators/VectorStore.ts +1 -1
- package/types/generators/Watchdog.ts +1 -1
- package/types/generators/WebCrawler.ts +1 -1
- package/types/generators/WebRtc.ts +1 -1
- package/types/generators/WebSocket.ts +1 -1
- package/types/generators/index.ts +2 -0
- package/utils/calc.ts +16 -8
- package/utils/event-props.ts +27 -0
- package/utils/id.ts +4 -0
- package/api/index.ts +0 -1
- 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.
|
|
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.
|
|
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
|
|
8
|
-
|
|
9
|
-
| `launch`
|
|
10
|
-
| `anytime` | Execute anytime via manual trigger
|
|
11
|
-
| `cron`
|
|
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
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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
|
|
104
|
-
|
|
105
|
-
| `brick_press`
|
|
106
|
-
| `brick_exists`
|
|
107
|
-
| `wait_until_brick_exists`
|
|
108
|
-
| `wait_until_event_trigger`
|
|
109
|
-
| `wait_until_canvas_change`
|
|
110
|
-
| `keydown`
|
|
111
|
-
| `keyup`
|
|
112
|
-
| `http_request`
|
|
113
|
-
| `assert_property`
|
|
114
|
-
| `wait_until_property_change` | `[subspace, property, value, timeout?]`
|
|
115
|
-
| `execute_action`
|
|
116
|
-
| `match_screenshot`
|
|
117
|
-
| `delay`
|
|
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: '
|
|
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 `
|
|
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: {
|
|
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
|
-
|
|
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)')
|
package/tools/mcp-server.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
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:
|
|
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:
|
|
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: [
|
|
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) {
|
package/tools/postinstall.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
package/tools/preview-main.mjs
CHANGED
|
@@ -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
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
;[testId] =
|
|
51
|
-
}
|
|
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
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
|
+
}
|
package/types/bricks/Camera.ts
CHANGED
package/types/bricks/Chart.ts
CHANGED
package/types/bricks/Icon.ts
CHANGED
package/types/bricks/Image.ts
CHANGED
package/types/bricks/Items.ts
CHANGED
package/types/bricks/Lottie.ts
CHANGED
package/types/bricks/Maps.ts
CHANGED
package/types/bricks/QrCode.ts
CHANGED
package/types/bricks/Rect.ts
CHANGED
package/types/bricks/RichText.ts
CHANGED
package/types/bricks/Rive.ts
CHANGED