@fugood/bricks-project 2.23.4 → 2.23.5-2
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 +14 -0
- package/compile/index.ts +29 -0
- package/package.json +2 -2
- package/skills/bricks-project/rules/architecture-patterns.md +7 -0
- package/skills/bricks-project/rules/buttress.md +9 -6
- package/tools/mcp-tools/huggingface.ts +113 -3
- package/tools/postinstall.ts +113 -28
- package/types/animation.ts +4 -0
- package/types/automation.ts +3 -0
- package/types/bricks/Camera.ts +33 -6
- package/types/bricks/GenerativeMedia.ts +5 -5
- package/types/bricks/Icon.ts +2 -2
- package/types/bricks/Image.ts +3 -3
- package/types/bricks/Items.ts +6 -6
- package/types/bricks/Lottie.ts +3 -3
- package/types/bricks/Maps.ts +3 -3
- package/types/bricks/QrCode.ts +3 -3
- package/types/bricks/Rect.ts +3 -3
- package/types/bricks/RichText.ts +2 -2
- package/types/bricks/Slideshow.ts +1 -1
- package/types/bricks/Svg.ts +2 -2
- package/types/bricks/Text.ts +3 -3
- package/types/bricks/TextInput.ts +10 -6
- package/types/bricks/Video.ts +3 -3
- package/types/bricks/VideoStreaming.ts +2 -2
- package/types/bricks/WebView.ts +3 -3
- package/types/canvas.ts +2 -0
- package/types/common.ts +5 -0
- package/types/data-calc-command.ts +2 -0
- package/types/data-calc.ts +1 -0
- package/types/data.ts +2 -0
- package/types/generators/AlarmClock.ts +4 -4
- package/types/generators/Assistant.ts +53 -8
- package/types/generators/BleCentral.ts +11 -3
- package/types/generators/BlePeripheral.ts +3 -3
- package/types/generators/CanvasMap.ts +3 -3
- package/types/generators/CastlesPay.ts +2 -2
- package/types/generators/DataBank.ts +29 -2
- package/types/generators/File.ts +62 -13
- package/types/generators/GraphQl.ts +2 -2
- package/types/generators/Http.ts +25 -6
- package/types/generators/HttpServer.ts +4 -4
- package/types/generators/Information.ts +1 -1
- package/types/generators/Intent.ts +7 -1
- package/types/generators/Iterator.ts +5 -5
- package/types/generators/Keyboard.ts +15 -5
- package/types/generators/LlmAnthropicCompat.ts +9 -3
- package/types/generators/LlmAppleBuiltin.ts +4 -4
- package/types/generators/LlmGgml.ts +63 -13
- package/types/generators/LlmMlx.ts +210 -0
- package/types/generators/LlmOnnx.ts +13 -4
- package/types/generators/LlmOpenAiCompat.ts +19 -3
- package/types/generators/LlmQualcommAiEngine.ts +29 -5
- package/types/generators/Mcp.ts +331 -16
- package/types/generators/McpServer.ts +34 -7
- package/types/generators/MediaFlow.ts +24 -6
- package/types/generators/MqttBroker.ts +9 -3
- package/types/generators/MqttClient.ts +10 -4
- package/types/generators/Question.ts +4 -4
- package/types/generators/RealtimeTranscription.ts +81 -10
- package/types/generators/RerankerGgml.ts +19 -5
- package/types/generators/SerialPort.ts +5 -5
- package/types/generators/SoundPlayer.ts +1 -1
- package/types/generators/SoundRecorder.ts +16 -5
- package/types/generators/SpeechToTextGgml.ts +27 -7
- package/types/generators/SpeechToTextOnnx.ts +3 -3
- package/types/generators/SpeechToTextPlatform.ts +3 -3
- package/types/generators/SqLite.ts +9 -5
- package/types/generators/Step.ts +2 -2
- package/types/generators/SttAppleBuiltin.ts +4 -4
- package/types/generators/Tcp.ts +3 -3
- package/types/generators/TcpServer.ts +5 -5
- package/types/generators/TextToSpeechAppleBuiltin.ts +3 -3
- package/types/generators/TextToSpeechGgml.ts +3 -3
- package/types/generators/TextToSpeechOnnx.ts +3 -3
- package/types/generators/TextToSpeechOpenAiLike.ts +3 -3
- package/types/generators/ThermalPrinter.ts +4 -4
- package/types/generators/Tick.ts +2 -2
- package/types/generators/Udp.ts +8 -3
- package/types/generators/VadGgml.ts +34 -5
- package/types/generators/VadOnnx.ts +27 -4
- package/types/generators/VadTraditional.ts +13 -7
- package/types/generators/VectorStore.ts +22 -5
- package/types/generators/Watchdog.ts +10 -5
- package/types/generators/WebCrawler.ts +3 -3
- package/types/generators/WebRtc.ts +14 -8
- package/types/generators/WebSocket.ts +4 -4
- package/types/generators/index.ts +1 -0
- package/types/subspace.ts +1 -0
- package/utils/event-props.ts +104 -87
|
@@ -786,6 +786,20 @@ export const templateActionNameMap = {
|
|
|
786
786
|
documents: 'GENERATOR_RERANKER_DOCUMENTS',
|
|
787
787
|
},
|
|
788
788
|
},
|
|
789
|
+
GENERATOR_MLX_LLM: {
|
|
790
|
+
GENERATOR_MLX_LLM_LOAD_MODEL: {
|
|
791
|
+
modelId: 'GENERATOR_MLX_LLM_MODEL_ID',
|
|
792
|
+
vlm: 'GENERATOR_MLX_LLM_VLM',
|
|
793
|
+
},
|
|
794
|
+
GENERATOR_MLX_LLM_COMPLETION: {
|
|
795
|
+
messages: 'GENERATOR_MLX_LLM_MESSAGES',
|
|
796
|
+
tools: 'GENERATOR_MLX_LLM_TOOLS',
|
|
797
|
+
maxTokens: 'GENERATOR_MLX_LLM_MAX_TOKENS',
|
|
798
|
+
temperature: 'GENERATOR_MLX_LLM_TEMPERATURE',
|
|
799
|
+
topP: 'GENERATOR_MLX_LLM_TOP_P',
|
|
800
|
+
repetitionPenalty: 'GENERATOR_MLX_LLM_REPETITION_PENALTY',
|
|
801
|
+
},
|
|
802
|
+
},
|
|
789
803
|
GENERATOR_QNN_LLM: {
|
|
790
804
|
GENERATOR_QNN_LLM_PROCESS: {
|
|
791
805
|
prompt: 'GENERATOR_QNN_LLM_PROMPT',
|
package/compile/index.ts
CHANGED
|
@@ -266,6 +266,7 @@ const compileApplicationSettings = (settings: Application['settings']) => ({
|
|
|
266
266
|
use_gemini_api_key_system_data: settings.ai.useGeminiApiKeySystemData,
|
|
267
267
|
}
|
|
268
268
|
: undefined,
|
|
269
|
+
hide_short_refs: settings?.hideShortRefs,
|
|
269
270
|
})
|
|
270
271
|
|
|
271
272
|
const animationTypeMap = {
|
|
@@ -385,6 +386,7 @@ function compileRunArray(run: unknown[]): unknown[] {
|
|
|
385
386
|
const compileTestCase = (testCase: TestCase) => ({
|
|
386
387
|
id: testCase.id,
|
|
387
388
|
name: testCase.name,
|
|
389
|
+
hide_short_ref: testCase.hideShortRef,
|
|
388
390
|
run: compileRunArray(testCase.run),
|
|
389
391
|
exit_on_failed: testCase.exit_on_failed,
|
|
390
392
|
commented: testCase.commented,
|
|
@@ -449,6 +451,7 @@ const compileAutomationTest = (
|
|
|
449
451
|
return {
|
|
450
452
|
id: testId,
|
|
451
453
|
title: test.title,
|
|
454
|
+
hide_short_ref: test.hideShortRef,
|
|
452
455
|
timeout: test.timeout,
|
|
453
456
|
trigger_type: test.trigger_type,
|
|
454
457
|
cron: test.cron,
|
|
@@ -476,6 +479,7 @@ const compileAutomationTestMap = (testMap: AutomationTestMap, mapId: string) =>
|
|
|
476
479
|
|
|
477
480
|
return {
|
|
478
481
|
title: testMap.title,
|
|
482
|
+
hide_short_ref: testMap.hideShortRef,
|
|
479
483
|
createdAt: testMap.createdAt,
|
|
480
484
|
map: arrayToIdMap(
|
|
481
485
|
testMap.tests,
|
|
@@ -519,6 +523,7 @@ export const compile = async (app: Application) => {
|
|
|
519
523
|
subspaceMap[subspaceId] = {
|
|
520
524
|
title: subspace.title,
|
|
521
525
|
description: subspace.description,
|
|
526
|
+
hide_short_ref: subspace.hideShortRef,
|
|
522
527
|
_expanded: subspace.unexpanded
|
|
523
528
|
? {
|
|
524
529
|
brick: !subspace.unexpanded.brick,
|
|
@@ -556,8 +561,10 @@ export const compile = async (app: Application) => {
|
|
|
556
561
|
if (animation.__typename === 'Animation') {
|
|
557
562
|
const animationDef = animation as AnimationDef
|
|
558
563
|
map[animationId] = {
|
|
564
|
+
alias: animationDef.alias,
|
|
559
565
|
title: animationDef.title,
|
|
560
566
|
description: animationDef.description,
|
|
567
|
+
hide_short_ref: animationDef.hideShortRef,
|
|
561
568
|
animationRunType: animationDef.runType,
|
|
562
569
|
property: animationDef.property,
|
|
563
570
|
type: animationTypeMap[animationDef.config.__type],
|
|
@@ -569,8 +576,10 @@ export const compile = async (app: Application) => {
|
|
|
569
576
|
} else if (animation.__typename === 'AnimationCompose') {
|
|
570
577
|
const animationDef = animation as AnimationComposeDef
|
|
571
578
|
map[animationId] = {
|
|
579
|
+
alias: animationDef.alias,
|
|
572
580
|
title: animationDef.title,
|
|
573
581
|
description: animationDef.description,
|
|
582
|
+
hide_short_ref: animationDef.hideShortRef,
|
|
574
583
|
animationRunType: animationDef.runType,
|
|
575
584
|
compose_type: animationDef.composeType,
|
|
576
585
|
item_list: animationDef.items.map((item, index) => {
|
|
@@ -670,6 +679,8 @@ export const compile = async (app: Application) => {
|
|
|
670
679
|
buildList(item, index, 'brickList'),
|
|
671
680
|
)
|
|
672
681
|
property.brickList = brickList
|
|
682
|
+
} else if (!brickItems.brickList) {
|
|
683
|
+
property.brickList = []
|
|
673
684
|
} else {
|
|
674
685
|
// Not supported Data for brickList
|
|
675
686
|
throw new TypeError('Not supported Data for brickList directly')
|
|
@@ -679,6 +690,8 @@ export const compile = async (app: Application) => {
|
|
|
679
690
|
buildList(item, index, 'brickDetails'),
|
|
680
691
|
)
|
|
681
692
|
property.brickDetails = brickDetails
|
|
693
|
+
} else if (!brickItems.brickDetails) {
|
|
694
|
+
property.brickDetails = []
|
|
682
695
|
} else {
|
|
683
696
|
// Not supported Data for brickList
|
|
684
697
|
throw new TypeError('Not supported Data for brickList directly')
|
|
@@ -686,8 +699,10 @@ export const compile = async (app: Application) => {
|
|
|
686
699
|
}
|
|
687
700
|
map[brickId] = {
|
|
688
701
|
template_key: brick.templateKey,
|
|
702
|
+
alias: brick.alias,
|
|
689
703
|
title: brick.title,
|
|
690
704
|
description: brick.description,
|
|
705
|
+
hide_short_ref: brick.hideShortRef,
|
|
691
706
|
property,
|
|
692
707
|
animation: compileAnimations(
|
|
693
708
|
brick.templateKey,
|
|
@@ -754,8 +769,10 @@ export const compile = async (app: Application) => {
|
|
|
754
769
|
)
|
|
755
770
|
|
|
756
771
|
map[canvasId] = {
|
|
772
|
+
alias: canvas.alias,
|
|
757
773
|
title: canvas.title,
|
|
758
774
|
description: canvas.description,
|
|
775
|
+
hide_short_ref: canvas.hideShortRef,
|
|
759
776
|
property: compileProperty(
|
|
760
777
|
canvas.property,
|
|
761
778
|
`(canvas: ${canvasId}, subspace ${subspaceId})`,
|
|
@@ -834,8 +851,10 @@ export const compile = async (app: Application) => {
|
|
|
834
851
|
|
|
835
852
|
map[generatorId] = {
|
|
836
853
|
template_key: generator.templateKey,
|
|
854
|
+
alias: generator.alias,
|
|
837
855
|
title: generator.title,
|
|
838
856
|
description: generator.description,
|
|
857
|
+
hide_short_ref: generator.hideShortRef,
|
|
839
858
|
local_sync: generator.localSyncRunMode
|
|
840
859
|
? {
|
|
841
860
|
run_mode: generator.localSyncRunMode,
|
|
@@ -904,8 +923,10 @@ export const compile = async (app: Application) => {
|
|
|
904
923
|
)
|
|
905
924
|
|
|
906
925
|
map[dataId] = {
|
|
926
|
+
alias: data.alias,
|
|
907
927
|
title: data.title,
|
|
908
928
|
description: data.description,
|
|
929
|
+
hide_short_ref: data.hideShortRef,
|
|
909
930
|
linked: data.metadata?.linked,
|
|
910
931
|
linkedFrom: data.metadata?.linkedFrom,
|
|
911
932
|
local_sync: data.localSyncUpdateMode
|
|
@@ -939,6 +960,7 @@ export const compile = async (app: Application) => {
|
|
|
939
960
|
const calc: any = {
|
|
940
961
|
title: dataCalc.title,
|
|
941
962
|
description: dataCalc.description,
|
|
963
|
+
hide_short_ref: dataCalc.hideShortRef,
|
|
942
964
|
}
|
|
943
965
|
if (dataCalc.triggerMode) calc.trigger_type = dataCalc.triggerMode
|
|
944
966
|
if (dataCalc.__typename === 'DataCalculationMap') {
|
|
@@ -1025,6 +1047,7 @@ export const compile = async (app: Application) => {
|
|
|
1025
1047
|
acc[getNodeId(dataNode, 'data node', nodeIndex)] = {
|
|
1026
1048
|
title: dataNode.title,
|
|
1027
1049
|
description: dataNode.description,
|
|
1050
|
+
hide_short_ref: dataNode.hideShortRef,
|
|
1028
1051
|
type: 'data-node',
|
|
1029
1052
|
properties: {},
|
|
1030
1053
|
in: generateInputPorts(dataNode.inputs),
|
|
@@ -1050,6 +1073,7 @@ export const compile = async (app: Application) => {
|
|
|
1050
1073
|
acc[getNodeId(commandNode, 'command node', nodeIndex)] = {
|
|
1051
1074
|
title: commandNode.title,
|
|
1052
1075
|
description: commandNode.description,
|
|
1076
|
+
hide_short_ref: commandNode.hideShortRef,
|
|
1053
1077
|
type: `command-node-${type}`,
|
|
1054
1078
|
properties: {
|
|
1055
1079
|
command: commandNode.__commandName,
|
|
@@ -1193,3 +1217,8 @@ export const compile = async (app: Application) => {
|
|
|
1193
1217
|
}
|
|
1194
1218
|
return config
|
|
1195
1219
|
}
|
|
1220
|
+
|
|
1221
|
+
export const checkConfig = async (configPath: string) => {
|
|
1222
|
+
const { $ } = await import('bun')
|
|
1223
|
+
await $`bricks app check-config ${configPath}`
|
|
1224
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugood/bricks-project",
|
|
3
|
-
"version": "2.23.
|
|
3
|
+
"version": "2.23.5-2",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"typecheck": "tsc --noEmit",
|
|
7
7
|
"build": "bun scripts/build.js"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@fugood/bricks-cli": "
|
|
10
|
+
"@fugood/bricks-cli": "2.23.5-1",
|
|
11
11
|
"@huggingface/gguf": "^0.3.2",
|
|
12
12
|
"@iarna/toml": "^3.0.0",
|
|
13
13
|
"@modelcontextprotocol/sdk": "^1.15.0",
|
|
@@ -37,6 +37,13 @@ Built-in commands for direct state and UI changes.
|
|
|
37
37
|
### Data Calculation (Priority 4)
|
|
38
38
|
ONLY for deriving, formatting, or aggregating values from other data. Not for orchestration, side effects, or flow control.
|
|
39
39
|
|
|
40
|
+
## Entity Aliases
|
|
41
|
+
|
|
42
|
+
Set `alias` on entities to give them a stable, human-readable name:
|
|
43
|
+
- Code generation uses alias as the variable name (e.g., `alias: 'submitBtn'` → `export const submitBtn`)
|
|
44
|
+
- At runtime, devtools MCP tools accept alias instead of short ID and selectors can match by alias. Note: bricks-project does not directly interact with devtools MCP — aliases here primarily affect code generation
|
|
45
|
+
- Aliases must be unique across the application — duplicates are excluded from resolution
|
|
46
|
+
|
|
40
47
|
## Flow Decomposition
|
|
41
48
|
|
|
42
49
|
When the user describes a complex flow, decompose it BEFORE writing code:
|
|
@@ -14,8 +14,9 @@ When mobile devices or embedded systems lack hardware for local AI inference (LL
|
|
|
14
14
|
|
|
15
15
|
## Supported Generators
|
|
16
16
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
17
|
+
- LLM (GGML) (LlmMlx.ts) - Local Large Language Model inference with GGML
|
|
18
|
+
- LLM (MLX) (LlmGgml.ts) - Local Large Language Model inference with MLX
|
|
19
|
+
- Speech-to-Text (GGML) (SpeechToTextGgml.ts) - Local Speech-to-Text inference with GGML
|
|
19
20
|
|
|
20
21
|
## Client Configuration
|
|
21
22
|
|
|
@@ -50,10 +51,12 @@ const llmGenerator: GeneratorLLM = {
|
|
|
50
51
|
property: {
|
|
51
52
|
modelUrl: 'https://huggingface.co/ggml-org/gemma-3-12b-it-qat-GGUF/resolve/main/gemma-3-12b-it-qat-q4_0.gguf',
|
|
52
53
|
contextSize: 8192,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
buttressConnectionSettings: {
|
|
55
|
+
enabled: true,
|
|
56
|
+
url: 'http://192.168.1.100:2080',
|
|
57
|
+
fallbackType: 'use-local',
|
|
58
|
+
strategy: 'prefer-best',
|
|
59
|
+
},
|
|
57
60
|
},
|
|
58
61
|
events: {},
|
|
59
62
|
switches: [],
|
|
@@ -102,6 +102,7 @@ type HFSibling = {
|
|
|
102
102
|
rfilename: string
|
|
103
103
|
size?: number
|
|
104
104
|
lfs?: { sha256?: string }
|
|
105
|
+
blobId?: string
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
type HFModel = {
|
|
@@ -132,8 +133,9 @@ type GeneratorType =
|
|
|
132
133
|
| 'GeneratorOnnxLLM'
|
|
133
134
|
| 'GeneratorOnnxSTT'
|
|
134
135
|
| 'GeneratorTTS'
|
|
136
|
+
| 'GeneratorMlxLLM'
|
|
135
137
|
|
|
136
|
-
type ModelKind = 'gguf' | 'onnx'
|
|
138
|
+
type ModelKind = 'gguf' | 'onnx' | 'mlx'
|
|
137
139
|
|
|
138
140
|
interface GeneratorConfig {
|
|
139
141
|
modelKind: ModelKind
|
|
@@ -219,6 +221,11 @@ const generatorConfigs: Record<GeneratorType, GeneratorConfig> = {
|
|
|
219
221
|
filter: 'onnx,text-to-speech',
|
|
220
222
|
hasValidStructure: hasValidOnnxStructure,
|
|
221
223
|
},
|
|
224
|
+
GeneratorMlxLLM: {
|
|
225
|
+
modelKind: 'mlx',
|
|
226
|
+
filter: 'mlx',
|
|
227
|
+
taskFilter: supportedLlmTasks,
|
|
228
|
+
},
|
|
222
229
|
}
|
|
223
230
|
|
|
224
231
|
const searchHFModels = async (filter: string, search?: string, limit = 50): Promise<HFModel[]> => {
|
|
@@ -276,6 +283,7 @@ export function register(server: McpServer) {
|
|
|
276
283
|
'GeneratorOnnxLLM',
|
|
277
284
|
'GeneratorOnnxSTT',
|
|
278
285
|
'GeneratorTTS',
|
|
286
|
+
'GeneratorMlxLLM',
|
|
279
287
|
])
|
|
280
288
|
.describe('Generator type to search models for')
|
|
281
289
|
.default('GeneratorLLM'),
|
|
@@ -407,6 +415,7 @@ export function register(server: McpServer) {
|
|
|
407
415
|
'GeneratorOnnxLLM',
|
|
408
416
|
'GeneratorOnnxSTT',
|
|
409
417
|
'GeneratorTTS',
|
|
418
|
+
'GeneratorMlxLLM',
|
|
410
419
|
])
|
|
411
420
|
.describe('Generator type for model selection')
|
|
412
421
|
.default('GeneratorLLM'),
|
|
@@ -489,7 +498,108 @@ export function register(server: McpServer) {
|
|
|
489
498
|
|
|
490
499
|
// Return JSON for huggingface_select to allow direct parsing by consumers
|
|
491
500
|
return {
|
|
492
|
-
content: [
|
|
501
|
+
content: [
|
|
502
|
+
{
|
|
503
|
+
type: 'text',
|
|
504
|
+
text: `${JSON5.stringify(result, null, 2)} // Please use _hfRepoInfo undocumented field with @ts-ignore`,
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Handle MLX models (repo-level, like ONNX)
|
|
511
|
+
if (config.modelKind === 'mlx') {
|
|
512
|
+
// Fetch config.json for model architecture metadata
|
|
513
|
+
const headers: Record<string, string> = {}
|
|
514
|
+
if (HF_TOKEN) headers['Authorization'] = `Bearer ${HF_TOKEN}`
|
|
515
|
+
let mlxModelConfig: Record<string, any> | null = null
|
|
516
|
+
try {
|
|
517
|
+
const configRes = await fetch(
|
|
518
|
+
`https://huggingface.co/${modelId}/resolve/main/config.json`,
|
|
519
|
+
{ headers },
|
|
520
|
+
)
|
|
521
|
+
if (configRes.ok) mlxModelConfig = await configRes.json()
|
|
522
|
+
} catch {
|
|
523
|
+
// Non-critical
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const modelType =
|
|
527
|
+
mlxModelConfig?.model_type || details.config?.model_type || details.cardData?.model_type
|
|
528
|
+
|
|
529
|
+
// Build MLX model metadata from config.json (for hardware guardrails)
|
|
530
|
+
const cfg = mlxModelConfig || ({} as Record<string, any>)
|
|
531
|
+
const textCfg = cfg.text_config || cfg
|
|
532
|
+
const numHeads = textCfg.num_attention_heads || textCfg.n_heads || 0
|
|
533
|
+
const hiddenSize = textCfg.hidden_size || textCfg.dim || 0
|
|
534
|
+
const kvLoraRank = textCfg.kv_lora_rank || 0
|
|
535
|
+
const quant = cfg.quantization || cfg.quantization_config || null
|
|
536
|
+
|
|
537
|
+
// Sum safetensors/npz file sizes for model weight bytes
|
|
538
|
+
const modelBytes = siblings
|
|
539
|
+
.filter((f) => /\.(safetensors|npz)$/.test(f.rfilename))
|
|
540
|
+
.reduce((sum, f) => sum + (f.size ?? 0), 0)
|
|
541
|
+
|
|
542
|
+
// Build _mlxDownloadFiles list (safetensors, json, jinja, tokenizer.model)
|
|
543
|
+
const mlxDownloadFiles = siblings
|
|
544
|
+
.filter(
|
|
545
|
+
(f) =>
|
|
546
|
+
f.rfilename.endsWith('.safetensors') ||
|
|
547
|
+
f.rfilename.endsWith('.json') ||
|
|
548
|
+
f.rfilename.endsWith('.jinja') ||
|
|
549
|
+
f.rfilename === 'tokenizer.model',
|
|
550
|
+
)
|
|
551
|
+
.map((f) => ({
|
|
552
|
+
url: `https://huggingface.co/${modelId}/resolve/main/${f.rfilename}?download=true`,
|
|
553
|
+
filename: `${modelId.replace('/', '-')}/${f.rfilename}`,
|
|
554
|
+
// eslint-disable-next-line no-nested-ternary
|
|
555
|
+
hash_type: f.lfs ? 'sha256' : f.blobId ? 'sha1' : undefined,
|
|
556
|
+
sha256: f.lfs?.sha256,
|
|
557
|
+
sha1: f.lfs ? undefined : f.blobId,
|
|
558
|
+
}))
|
|
559
|
+
|
|
560
|
+
const result = {
|
|
561
|
+
modelId,
|
|
562
|
+
modelType,
|
|
563
|
+
_mlxDownloadFiles: mlxDownloadFiles,
|
|
564
|
+
_hfRepoInfo: {
|
|
565
|
+
repo: modelId,
|
|
566
|
+
model: {
|
|
567
|
+
id: details.id,
|
|
568
|
+
downloads: details.downloads,
|
|
569
|
+
likes: details.likes,
|
|
570
|
+
author: details.author,
|
|
571
|
+
},
|
|
572
|
+
generatorType,
|
|
573
|
+
modelType,
|
|
574
|
+
...(modelBytes > 0 && { size: modelBytes }),
|
|
575
|
+
...(mlxModelConfig && {
|
|
576
|
+
mlxModelMetadata: {
|
|
577
|
+
hiddenSize,
|
|
578
|
+
numLayers: textCfg.num_hidden_layers || textCfg.n_layers || 0,
|
|
579
|
+
numHeads,
|
|
580
|
+
numKvHeads: textCfg.num_key_value_heads ?? numHeads,
|
|
581
|
+
headDim:
|
|
582
|
+
textCfg.head_dim ||
|
|
583
|
+
textCfg.v_head_dim ||
|
|
584
|
+
(numHeads > 0 && hiddenSize > 0 ? hiddenSize / numHeads : 0),
|
|
585
|
+
maxCtx: textCfg.max_position_embeddings || 0,
|
|
586
|
+
kvLoraRank,
|
|
587
|
+
qkRopeHeadDim: textCfg.qk_rope_head_dim || 0,
|
|
588
|
+
isMLA: kvLoraRank > 0,
|
|
589
|
+
quantBits: quant?.bits || null,
|
|
590
|
+
quantGroupSize: quant?.group_size || null,
|
|
591
|
+
},
|
|
592
|
+
}),
|
|
593
|
+
},
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return {
|
|
597
|
+
content: [
|
|
598
|
+
{
|
|
599
|
+
type: 'text',
|
|
600
|
+
text: `${JSON5.stringify(result, null, 2)} // IMPORTANT: Use _mlxDownloadFiles / _hfRepoInfo undocumented fields with @ts-ignore`,
|
|
601
|
+
},
|
|
602
|
+
],
|
|
493
603
|
}
|
|
494
604
|
}
|
|
495
605
|
|
|
@@ -638,7 +748,7 @@ export function register(server: McpServer) {
|
|
|
638
748
|
content: [
|
|
639
749
|
{
|
|
640
750
|
type: 'text',
|
|
641
|
-
text: `${JSON5.stringify(result, null, 2)} //
|
|
751
|
+
text: `${JSON5.stringify(result, null, 2)} // IMPORTANT: Use _hfRepoInfo undocumented field with @ts-ignore`,
|
|
642
752
|
},
|
|
643
753
|
],
|
|
644
754
|
}
|
package/tools/postinstall.ts
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { $ } from 'bun'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
cp,
|
|
4
|
+
lstat,
|
|
5
|
+
mkdir,
|
|
6
|
+
readFile,
|
|
7
|
+
readdir,
|
|
8
|
+
readlink,
|
|
9
|
+
rm,
|
|
10
|
+
stat,
|
|
11
|
+
symlink,
|
|
12
|
+
writeFile,
|
|
13
|
+
} from 'fs/promises'
|
|
14
|
+
import * as path from 'path'
|
|
3
15
|
import TOML from '@iarna/toml'
|
|
4
16
|
|
|
5
17
|
const cwd = process.cwd()
|
|
18
|
+
const projectSkillsDir = path.join(cwd, '.bricks', 'skills')
|
|
19
|
+
const compatibilitySkillLinks = [
|
|
20
|
+
path.join(cwd, '.claude', 'skills'),
|
|
21
|
+
path.join(cwd, '.codex', 'skills'),
|
|
22
|
+
]
|
|
6
23
|
|
|
7
24
|
async function exists(f: string) {
|
|
8
25
|
try {
|
|
@@ -13,6 +30,15 @@ async function exists(f: string) {
|
|
|
13
30
|
}
|
|
14
31
|
}
|
|
15
32
|
|
|
33
|
+
async function pathExists(f: string) {
|
|
34
|
+
try {
|
|
35
|
+
await lstat(f)
|
|
36
|
+
return true
|
|
37
|
+
} catch {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
16
42
|
// handle flag --skip-copy
|
|
17
43
|
const skipCopyProject = process.argv.includes('--skip-copy-project')
|
|
18
44
|
if (skipCopyProject) {
|
|
@@ -34,6 +60,7 @@ type CodexMcpConfig = {
|
|
|
34
60
|
mcp_servers: Record<string, typeof projectMcpServer>
|
|
35
61
|
}
|
|
36
62
|
|
|
63
|
+
// Claude Code and AGENTS.md projects both use the shared project .mcp.json file.
|
|
37
64
|
const defaultMcpConfig = {
|
|
38
65
|
mcpServers: {
|
|
39
66
|
'bricks-project': projectMcpServer,
|
|
@@ -64,43 +91,101 @@ const hasClaudeCode = await exists(`${cwd}/CLAUDE.md`)
|
|
|
64
91
|
const hasAgentsMd = await exists(`${cwd}/AGENTS.md`)
|
|
65
92
|
|
|
66
93
|
if (hasClaudeCode || hasAgentsMd) {
|
|
94
|
+
// Keep the workspace-level JSON MCP config aligned for tools that read .mcp.json.
|
|
67
95
|
const mcpConfigPath = `${cwd}/.mcp.json`
|
|
68
96
|
await handleMcpConfigOverride(mcpConfigPath)
|
|
69
97
|
}
|
|
70
98
|
|
|
71
|
-
const
|
|
72
|
-
|
|
99
|
+
const copyMissingSkills = async (sourceDir: string, targetDir: string) => {
|
|
100
|
+
if (!(await exists(sourceDir))) return
|
|
101
|
+
|
|
102
|
+
const packageSkills = await readdir(sourceDir, { withFileTypes: true })
|
|
103
|
+
const skillsToInstall = packageSkills.filter(
|
|
104
|
+
(entry) => entry.isDirectory() && !entry.name.startsWith('.'),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
await mkdir(targetDir, { recursive: true })
|
|
108
|
+
|
|
109
|
+
await Promise.all(
|
|
110
|
+
skillsToInstall.map(async (entry) => {
|
|
111
|
+
const targetSkillDir = path.join(targetDir, entry.name)
|
|
112
|
+
if (await exists(targetSkillDir)) {
|
|
113
|
+
console.log(`Skill '${entry.name}' already exists, skipping`)
|
|
114
|
+
} else {
|
|
115
|
+
await cp(path.join(sourceDir, entry.name), targetSkillDir, { recursive: true })
|
|
116
|
+
console.log(`Installed skill '${entry.name}' to ${targetDir}/`)
|
|
117
|
+
}
|
|
118
|
+
}),
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const migrateSkillsDir = async (legacySkillsDir: string, canonicalSkillsDir: string) => {
|
|
123
|
+
if (!(await pathExists(legacySkillsDir))) return
|
|
124
|
+
|
|
125
|
+
const legacyStats = await lstat(legacySkillsDir)
|
|
126
|
+
|
|
127
|
+
if (legacyStats.isSymbolicLink()) {
|
|
128
|
+
const linkTarget = await readlink(legacySkillsDir)
|
|
129
|
+
const resolvedTarget = path.resolve(path.dirname(legacySkillsDir), linkTarget)
|
|
130
|
+
if (resolvedTarget === canonicalSkillsDir) return
|
|
73
131
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
await $`mkdir -p ${skillsDir}`
|
|
79
|
-
|
|
80
|
-
await Promise.all(
|
|
81
|
-
skillsToInstall.map(async (skill) => {
|
|
82
|
-
const targetSkillDir = `${skillsDir}/${skill}`
|
|
83
|
-
if (await exists(targetSkillDir)) {
|
|
84
|
-
console.log(`Skill '${skill}' already exists, skipping`)
|
|
85
|
-
} else {
|
|
86
|
-
await $`cp -r ${packageSkillsDir}/${skill} ${targetSkillDir}`
|
|
87
|
-
console.log(`Installed skill '${skill}' to ${skillsDir}/`)
|
|
88
|
-
}
|
|
89
|
-
}),
|
|
90
|
-
)
|
|
132
|
+
await copyMissingSkills(resolvedTarget, canonicalSkillsDir)
|
|
133
|
+
await rm(legacySkillsDir, { force: true, recursive: true })
|
|
134
|
+
return
|
|
91
135
|
}
|
|
136
|
+
|
|
137
|
+
if (legacyStats.isDirectory()) {
|
|
138
|
+
await copyMissingSkills(legacySkillsDir, canonicalSkillsDir)
|
|
139
|
+
await rm(legacySkillsDir, { force: true, recursive: true })
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.warn(`Skipping skills migration for ${legacySkillsDir}; expected a directory or symlink`)
|
|
92
144
|
}
|
|
93
145
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
146
|
+
const ensureCompatibilitySkillLink = async (linkPath: string, targetDir: string) => {
|
|
147
|
+
await mkdir(path.dirname(linkPath), { recursive: true })
|
|
148
|
+
|
|
149
|
+
if (await pathExists(linkPath)) {
|
|
150
|
+
const linkStats = await lstat(linkPath)
|
|
151
|
+
if (linkStats.isSymbolicLink()) {
|
|
152
|
+
const linkTarget = await readlink(linkPath)
|
|
153
|
+
const resolvedTarget = path.resolve(path.dirname(linkPath), linkTarget)
|
|
154
|
+
if (resolvedTarget === targetDir) return
|
|
155
|
+
} else {
|
|
156
|
+
console.warn(
|
|
157
|
+
`Skipping skills symlink at ${linkPath}; path already exists and is not a symlink`,
|
|
158
|
+
)
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const relativeTarget = path.relative(path.dirname(linkPath), targetDir)
|
|
164
|
+
const symlinkType = process.platform === 'win32' ? 'junction' : 'dir'
|
|
165
|
+
await symlink(relativeTarget, linkPath, symlinkType)
|
|
166
|
+
console.log(`Linked ${linkPath} -> ${relativeTarget}`)
|
|
97
167
|
}
|
|
98
168
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
await
|
|
169
|
+
const setupSkills = async () => {
|
|
170
|
+
const packageSkillsDir = `${__dirname}/../skills`
|
|
171
|
+
await mkdir(projectSkillsDir, { recursive: true })
|
|
172
|
+
await copyMissingSkills(packageSkillsDir, projectSkillsDir)
|
|
173
|
+
|
|
174
|
+
await Promise.all(
|
|
175
|
+
compatibilitySkillLinks.map(async (linkPath) => {
|
|
176
|
+
await migrateSkillsDir(linkPath, projectSkillsDir)
|
|
177
|
+
await ensureCompatibilitySkillLink(linkPath, projectSkillsDir)
|
|
178
|
+
}),
|
|
179
|
+
)
|
|
180
|
+
}
|
|
103
181
|
|
|
182
|
+
if (hasClaudeCode || hasAgentsMd) {
|
|
183
|
+
// Install project skills once and expose them through compatibility symlinks.
|
|
184
|
+
await setupSkills()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (hasAgentsMd) {
|
|
188
|
+
// Codex stores its project-local MCP config in .codex/config.toml.
|
|
104
189
|
const defaultCodexMcpConfig = {
|
|
105
190
|
mcp_servers: {
|
|
106
191
|
'bricks-project': projectMcpServer,
|
|
@@ -128,7 +213,7 @@ if (hasAgentsMd) {
|
|
|
128
213
|
console.log(`Updated ${mcpConfigPath}`)
|
|
129
214
|
}
|
|
130
215
|
|
|
131
|
-
//
|
|
216
|
+
// Keep the Codex TOML MCP config aligned with the same bricks-project server entry.
|
|
132
217
|
const codexConfigPath = `${cwd}/.codex/config.toml`
|
|
133
218
|
await handleCodexMcpConfigOverride(codexConfigPath)
|
|
134
219
|
}
|
package/types/animation.ts
CHANGED
|
@@ -61,8 +61,10 @@ export interface AnimationDecayConfig {
|
|
|
61
61
|
export interface AnimationDef {
|
|
62
62
|
__typename: 'Animation'
|
|
63
63
|
id: string
|
|
64
|
+
alias?: string
|
|
64
65
|
title: string
|
|
65
66
|
description?: string
|
|
67
|
+
hideShortRef?: boolean
|
|
66
68
|
runType?: 'once' | 'loop'
|
|
67
69
|
property:
|
|
68
70
|
| 'transform.translateX'
|
|
@@ -80,8 +82,10 @@ export interface AnimationDef {
|
|
|
80
82
|
export interface AnimationComposeDef {
|
|
81
83
|
__typename: 'AnimationCompose'
|
|
82
84
|
id: string
|
|
85
|
+
alias?: string
|
|
83
86
|
title: string
|
|
84
87
|
description?: string
|
|
88
|
+
hideShortRef?: boolean
|
|
85
89
|
runType?: 'once' | 'loop'
|
|
86
90
|
composeType: 'parallel' | 'sequence'
|
|
87
91
|
items: Array<() => Animation>
|
package/types/automation.ts
CHANGED
|
@@ -182,6 +182,7 @@ export interface TestCase {
|
|
|
182
182
|
__typename: 'TestCase'
|
|
183
183
|
id: string
|
|
184
184
|
name: string
|
|
185
|
+
hideShortRef?: boolean
|
|
185
186
|
run: TestMethodRun
|
|
186
187
|
exit_on_failed: boolean
|
|
187
188
|
commented: boolean
|
|
@@ -203,6 +204,7 @@ export interface AutomationTest {
|
|
|
203
204
|
__typename: 'AutomationTest'
|
|
204
205
|
id: string
|
|
205
206
|
title: string
|
|
207
|
+
hideShortRef?: boolean
|
|
206
208
|
timeout: number
|
|
207
209
|
trigger_type?: TestTriggerType
|
|
208
210
|
cron?: string // Cron expression when trigger_type is 'cron'
|
|
@@ -220,6 +222,7 @@ export interface AutomationTestMap {
|
|
|
220
222
|
__typename: 'AutomationTestMap'
|
|
221
223
|
id: string
|
|
222
224
|
title: string
|
|
225
|
+
hideShortRef?: boolean
|
|
223
226
|
createdAt: number
|
|
224
227
|
tests: AutomationTest[]
|
|
225
228
|
}
|
package/types/bricks/Camera.ts
CHANGED
|
@@ -167,15 +167,42 @@ Default property:
|
|
|
167
167
|
}
|
|
168
168
|
outlets?: {
|
|
169
169
|
/* Camera device and format information */
|
|
170
|
-
info?: () => Data
|
|
170
|
+
info?: () => Data<{ [key: string]: any }>
|
|
171
171
|
/* Picture taken result */
|
|
172
|
-
pictureTaken?: () => Data
|
|
172
|
+
pictureTaken?: () => Data<{
|
|
173
|
+
width?: number
|
|
174
|
+
height?: number
|
|
175
|
+
uri?: string
|
|
176
|
+
base64?: string
|
|
177
|
+
[key: string]: any
|
|
178
|
+
}>
|
|
173
179
|
/* Record video result */
|
|
174
|
-
recordVideo?: () => Data
|
|
180
|
+
recordVideo?: () => Data<{
|
|
181
|
+
uri?: string
|
|
182
|
+
[key: string]: any
|
|
183
|
+
}>
|
|
175
184
|
/* Barcode read result */
|
|
176
|
-
barcodeRead?: () => Data
|
|
185
|
+
barcodeRead?: () => Data<{
|
|
186
|
+
type?: string
|
|
187
|
+
data?: string
|
|
188
|
+
rawData?: string
|
|
189
|
+
bounds?: {
|
|
190
|
+
origin?: {
|
|
191
|
+
x?: number
|
|
192
|
+
y?: number
|
|
193
|
+
[key: string]: any
|
|
194
|
+
}
|
|
195
|
+
size?: {
|
|
196
|
+
width?: number
|
|
197
|
+
height?: number
|
|
198
|
+
[key: string]: any
|
|
199
|
+
}
|
|
200
|
+
[key: string]: any
|
|
201
|
+
}
|
|
202
|
+
[key: string]: any
|
|
203
|
+
}>
|
|
177
204
|
/* Faces detected result */
|
|
178
|
-
faceDetected?: () => Data
|
|
205
|
+
faceDetected?: () => Data<Array<{ [key: string]: any }>>
|
|
179
206
|
}
|
|
180
207
|
animation?: AnimationBasicEvents & {
|
|
181
208
|
stateChange?: Animation
|
|
@@ -188,7 +215,7 @@ Default property:
|
|
|
188
215
|
}
|
|
189
216
|
}
|
|
190
217
|
|
|
191
|
-
/* Camera view brick
|
|
218
|
+
/* Camera view brick */
|
|
192
219
|
export type BrickCamera = Brick &
|
|
193
220
|
BrickCameraDef & {
|
|
194
221
|
templateKey: 'BRICK_CAMERA'
|