@fugood/bricks-ctor 2.25.0-beta.8 → 2.25.0-beta.80

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 (137) hide show
  1. package/package.json +4 -24
  2. package/tools/__tests__/legacy-forwarder.test.js +91 -0
  3. package/tools/_forward.ts +26 -0
  4. package/tools/deploy.ts +3 -164
  5. package/tools/mcp-server.ts +3 -27
  6. package/tools/postinstall.ts +3 -234
  7. package/tools/pull.ts +3 -121
  8. package/tools/push-config.ts +4 -0
  9. package/tools/simulator.ts +4 -0
  10. package/compile/__tests__/index.test.js +0 -115
  11. package/compile/action-name-map.ts +0 -1015
  12. package/compile/index.ts +0 -1458
  13. package/compile/util.ts +0 -358
  14. package/index.ts +0 -6
  15. package/skills/bricks-ctor/SKILL.md +0 -32
  16. package/skills/bricks-ctor/rules/animation.md +0 -160
  17. package/skills/bricks-ctor/rules/architecture-patterns.md +0 -69
  18. package/skills/bricks-ctor/rules/automations.md +0 -221
  19. package/skills/bricks-ctor/rules/buttress.md +0 -156
  20. package/skills/bricks-ctor/rules/data-calculation.md +0 -209
  21. package/skills/bricks-ctor/rules/local-sync.md +0 -129
  22. package/skills/bricks-ctor/rules/media-flow.md +0 -158
  23. package/skills/bricks-ctor/rules/remote-data-bank.md +0 -196
  24. package/skills/bricks-ctor/rules/standby-transition.md +0 -124
  25. package/skills/bricks-design/LICENSE.txt +0 -180
  26. package/skills/bricks-design/SKILL.md +0 -66
  27. package/skills/rive-marketplace/SKILL.md +0 -99
  28. package/tools/_git-author.ts +0 -29
  29. package/tools/_shell.ts +0 -173
  30. package/tools/icons/.gitattributes +0 -1
  31. package/tools/icons/fa6pro-glyphmap.json +0 -4686
  32. package/tools/icons/fa6pro-meta.json +0 -1
  33. package/tools/mcp-tools/compile.ts +0 -92
  34. package/tools/mcp-tools/huggingface.ts +0 -762
  35. package/tools/mcp-tools/icons.ts +0 -81
  36. package/tools/mcp-tools/lottie.ts +0 -102
  37. package/tools/mcp-tools/media.ts +0 -110
  38. package/tools/preview-main.mjs +0 -306
  39. package/tools/preview.ts +0 -150
  40. package/types/animation.ts +0 -111
  41. package/types/automation.ts +0 -235
  42. package/types/brick-base.ts +0 -80
  43. package/types/bricks/Camera.ts +0 -246
  44. package/types/bricks/Chart.ts +0 -372
  45. package/types/bricks/GenerativeMedia.ts +0 -290
  46. package/types/bricks/Icon.ts +0 -98
  47. package/types/bricks/Image.ts +0 -126
  48. package/types/bricks/Items.ts +0 -476
  49. package/types/bricks/Lottie.ts +0 -168
  50. package/types/bricks/Maps.ts +0 -262
  51. package/types/bricks/QrCode.ts +0 -117
  52. package/types/bricks/Rect.ts +0 -150
  53. package/types/bricks/RichText.ts +0 -128
  54. package/types/bricks/Rive.ts +0 -220
  55. package/types/bricks/Sketch.ts +0 -254
  56. package/types/bricks/Slideshow.ts +0 -201
  57. package/types/bricks/Svg.ts +0 -99
  58. package/types/bricks/Text.ts +0 -148
  59. package/types/bricks/TextInput.ts +0 -242
  60. package/types/bricks/Video.ts +0 -175
  61. package/types/bricks/VideoStreaming.ts +0 -112
  62. package/types/bricks/WebRtcStream.ts +0 -65
  63. package/types/bricks/WebView.ts +0 -168
  64. package/types/bricks/index.ts +0 -22
  65. package/types/canvas.ts +0 -82
  66. package/types/common.ts +0 -144
  67. package/types/data-calc-command.ts +0 -7005
  68. package/types/data-calc-script.ts +0 -21
  69. package/types/data-calc.ts +0 -11
  70. package/types/data.ts +0 -95
  71. package/types/generators/AlarmClock.ts +0 -110
  72. package/types/generators/Assistant.ts +0 -621
  73. package/types/generators/BleCentral.ts +0 -247
  74. package/types/generators/BlePeripheral.ts +0 -208
  75. package/types/generators/CanvasMap.ts +0 -74
  76. package/types/generators/CastlesPay.ts +0 -87
  77. package/types/generators/DataBank.ts +0 -160
  78. package/types/generators/File.ts +0 -432
  79. package/types/generators/GraphQl.ts +0 -132
  80. package/types/generators/Http.ts +0 -222
  81. package/types/generators/HttpServer.ts +0 -176
  82. package/types/generators/Information.ts +0 -103
  83. package/types/generators/Intent.ts +0 -168
  84. package/types/generators/Iterator.ts +0 -108
  85. package/types/generators/Keyboard.ts +0 -105
  86. package/types/generators/LlmAnthropicCompat.ts +0 -212
  87. package/types/generators/LlmAppleBuiltin.ts +0 -159
  88. package/types/generators/LlmGgml.ts +0 -861
  89. package/types/generators/LlmMediaTekNeuroPilot.ts +0 -235
  90. package/types/generators/LlmMlx.ts +0 -227
  91. package/types/generators/LlmOnnx.ts +0 -213
  92. package/types/generators/LlmOpenAiCompat.ts +0 -312
  93. package/types/generators/LlmQualcommAiEngine.ts +0 -247
  94. package/types/generators/Mcp.ts +0 -637
  95. package/types/generators/McpServer.ts +0 -289
  96. package/types/generators/MediaFlow.ts +0 -170
  97. package/types/generators/MqttBroker.ts +0 -141
  98. package/types/generators/MqttClient.ts +0 -141
  99. package/types/generators/Question.ts +0 -408
  100. package/types/generators/RealtimeTranscription.ts +0 -287
  101. package/types/generators/RerankerGgml.ts +0 -191
  102. package/types/generators/SerialPort.ts +0 -151
  103. package/types/generators/SoundPlayer.ts +0 -94
  104. package/types/generators/SoundRecorder.ts +0 -130
  105. package/types/generators/SpeechToTextGgml.ts +0 -419
  106. package/types/generators/SpeechToTextOnnx.ts +0 -236
  107. package/types/generators/SpeechToTextPlatform.ts +0 -85
  108. package/types/generators/SqLite.ts +0 -159
  109. package/types/generators/Step.ts +0 -107
  110. package/types/generators/SttAppleBuiltin.ts +0 -130
  111. package/types/generators/Tcp.ts +0 -126
  112. package/types/generators/TcpServer.ts +0 -147
  113. package/types/generators/TextToSpeechAppleBuiltin.ts +0 -127
  114. package/types/generators/TextToSpeechGgml.ts +0 -221
  115. package/types/generators/TextToSpeechOnnx.ts +0 -178
  116. package/types/generators/TextToSpeechOpenAiLike.ts +0 -121
  117. package/types/generators/ThermalPrinter.ts +0 -191
  118. package/types/generators/Tick.ts +0 -83
  119. package/types/generators/Udp.ts +0 -120
  120. package/types/generators/VadGgml.ts +0 -250
  121. package/types/generators/VadOnnx.ts +0 -231
  122. package/types/generators/VadTraditional.ts +0 -138
  123. package/types/generators/VectorStore.ts +0 -257
  124. package/types/generators/Watchdog.ts +0 -107
  125. package/types/generators/WebCrawler.ts +0 -103
  126. package/types/generators/WebRtc.ts +0 -181
  127. package/types/generators/WebSocket.ts +0 -148
  128. package/types/generators/index.ts +0 -57
  129. package/types/index.ts +0 -13
  130. package/types/subspace.ts +0 -60
  131. package/types/switch.ts +0 -51
  132. package/types/system.ts +0 -707
  133. package/utils/__tests__/id.test.js +0 -58
  134. package/utils/calc.ts +0 -126
  135. package/utils/data.ts +0 -497
  136. package/utils/event-props.ts +0 -886
  137. package/utils/id.ts +0 -80
package/package.json CHANGED
@@ -1,29 +1,9 @@
1
1
  {
2
2
  "name": "@fugood/bricks-ctor",
3
- "version": "2.25.0-beta.8",
4
- "main": "index.ts",
5
- "scripts": {
6
- "typecheck": "tsc --noEmit",
7
- "build": "bun scripts/build.js"
8
- },
3
+ "version": "2.25.0-beta.80",
4
+ "description": "Deprecated: ctor tooling moved to the `bricks ctor` commands in @fugood/bricks-cli. This package now only forwards legacy project scripts and triggers migration.",
9
5
  "dependencies": {
10
- "@fugood/bricks-cli": "^2.25.0-beta.8",
11
- "@huggingface/gguf": "^0.3.2",
12
- "@iarna/toml": "^3.0.0",
13
- "@modelcontextprotocol/sdk": "^1.15.0",
14
- "@toon-format/toon": "^2.1.0",
15
- "@types/bun": "^1.3.9",
16
- "@types/escodegen": "^0.0.10",
17
- "@types/lodash": "^4.17.12",
18
- "acorn": "^8.13.0",
19
- "escodegen": "2.1.0",
20
- "fuse.js": "^7.0.0",
21
- "json5": "^2.0.1",
22
- "lodash": "^4.17.4",
23
- "uuid": "^8.3.1"
24
- },
25
- "peerDependencies": {
26
- "oxfmt": "^0.36.0"
6
+ "@fugood/bricks-cli": "^2.25.0-beta.80"
27
7
  },
28
- "gitHead": "9b2439118ca2226b7eeb88f986f9da236c340d83"
8
+ "gitHead": "19cfd3d6a366cfb3e6c598a0e5fd737a930ac14d"
29
9
  }
@@ -0,0 +1,91 @@
1
+ const mockSpawnSync = jest.fn()
2
+ const mockExistsSync = jest.fn()
3
+
4
+ jest.mock('node:child_process', () => ({
5
+ __esModule: true,
6
+ spawnSync: (...args) => mockSpawnSync(...args),
7
+ }))
8
+
9
+ jest.mock('node:fs', () => ({
10
+ __esModule: true,
11
+ existsSync: (...args) => mockExistsSync(...args),
12
+ }))
13
+
14
+ const loadForwarder = () => require('../_forward.ts')
15
+
16
+ describe('legacy bricks-ctor forwarder', () => {
17
+ let exitSpy
18
+ let errorSpy
19
+
20
+ beforeEach(() => {
21
+ jest.resetModules()
22
+ mockSpawnSync.mockReset()
23
+ mockExistsSync.mockReset()
24
+ exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {})
25
+ errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
26
+ })
27
+
28
+ afterEach(() => {
29
+ exitSpy.mockRestore()
30
+ errorSpy.mockRestore()
31
+ })
32
+
33
+ it('prefers the local bricks CLI next to the legacy package', () => {
34
+ mockExistsSync.mockReturnValue(true)
35
+ mockSpawnSync.mockReturnValue({ status: 0 })
36
+ const { forwardLegacyCtorTool } = loadForwarder()
37
+
38
+ forwardLegacyCtorTool(
39
+ 'deploy',
40
+ 'file:///repo/node_modules/@fugood/bricks-ctor/tools/deploy.ts',
41
+ ['--target', 'device'],
42
+ )
43
+
44
+ expect(mockSpawnSync).toHaveBeenCalledWith(
45
+ '/repo/node_modules/.bin/bricks',
46
+ ['ctor', 'deploy', '--target', 'device'],
47
+ { stdio: 'inherit' },
48
+ )
49
+ expect(exitSpy).toHaveBeenCalledWith(0)
50
+ })
51
+
52
+ it('falls back to PATH when the local bricks CLI is missing', () => {
53
+ mockExistsSync.mockReturnValue(false)
54
+ mockSpawnSync.mockReturnValue({ status: 3 })
55
+ const { forwardLegacyCtorTool } = loadForwarder()
56
+
57
+ forwardLegacyCtorTool('pull', 'file:///repo/node_modules/@fugood/bricks-ctor/tools/pull.ts', [])
58
+
59
+ expect(mockSpawnSync).toHaveBeenCalledWith('bricks', ['ctor', 'pull'], { stdio: 'inherit' })
60
+ expect(exitSpy).toHaveBeenCalledWith(3)
61
+ })
62
+
63
+ it('reports spawn errors and exits with failure', () => {
64
+ mockExistsSync.mockReturnValue(false)
65
+ mockSpawnSync.mockReturnValue({ error: new Error('ENOENT') })
66
+ const { forwardLegacyCtorTool } = loadForwarder()
67
+
68
+ forwardLegacyCtorTool(
69
+ 'mcp',
70
+ 'file:///repo/node_modules/@fugood/bricks-ctor/tools/mcp-server.ts',
71
+ [],
72
+ )
73
+
74
+ expect(errorSpy).toHaveBeenCalledWith('bricks-ctor forwarder failed to run `bricks`: ENOENT')
75
+ expect(exitSpy).toHaveBeenCalledWith(1)
76
+ })
77
+
78
+ it('treats a missing child status as failure', () => {
79
+ mockExistsSync.mockReturnValue(false)
80
+ mockSpawnSync.mockReturnValue({})
81
+ const { forwardLegacyCtorTool } = loadForwarder()
82
+
83
+ forwardLegacyCtorTool(
84
+ 'simulator',
85
+ 'file:///repo/node_modules/@fugood/bricks-ctor/tools/simulator.ts',
86
+ [],
87
+ )
88
+
89
+ expect(exitSpy).toHaveBeenCalledWith(1)
90
+ })
91
+ })
@@ -0,0 +1,26 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ import { existsSync } from 'node:fs'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ // @fugood/bricks-ctor is deprecated; all tooling now lives in @fugood/bricks-cli.
6
+ // This shared forwarder keeps legacy project scripts working until postinstall migrates them.
7
+ export function resolveLegacyBricksCommand(baseUrl: string) {
8
+ const localBricks = fileURLToPath(new URL('../../../.bin/bricks', baseUrl))
9
+ return existsSync(localBricks) ? localBricks : 'bricks'
10
+ }
11
+
12
+ export function forwardLegacyCtorTool(
13
+ command: string,
14
+ baseUrl: string,
15
+ args = process.argv.slice(2),
16
+ ) {
17
+ const bricks = resolveLegacyBricksCommand(baseUrl)
18
+ const result = spawnSync(bricks, ['ctor', command, ...args], { stdio: 'inherit' })
19
+
20
+ if (result.error) {
21
+ console.error(`bricks-ctor forwarder failed to run \`${bricks}\`: ${result.error.message}`)
22
+ process.exit(1)
23
+ }
24
+
25
+ process.exit(result.status ?? 1)
26
+ }
package/tools/deploy.ts CHANGED
@@ -1,165 +1,4 @@
1
- import { access, readFile, writeFile } from 'node:fs/promises'
2
- import { parseArgs } from 'util'
3
- import { sh } from './_shell'
4
- import { buildCommitArgs } from './_git-author'
1
+ #!/usr/bin/env bun
2
+ import { forwardLegacyCtorTool } from './_forward.ts'
5
3
 
6
- const cwd = process.cwd()
7
-
8
- const exists = async (p: string) => {
9
- try {
10
- await access(p)
11
- return true
12
- } catch {
13
- return false
14
- }
15
- }
16
-
17
- const readJson = async (p: string) => JSON.parse(await readFile(p, 'utf8'))
18
-
19
- const {
20
- values: {
21
- changelogs: changelogsArg,
22
- 'changelogs-file': changelogsFile,
23
- 'auto-commit': autoCommit,
24
- 'auto-version': autoVersion,
25
- version: versionArg,
26
- yes,
27
- help,
28
- },
29
- } = parseArgs({
30
- args: process.argv.slice(2),
31
- options: {
32
- changelogs: { type: 'string' },
33
- 'changelogs-file': { type: 'string' },
34
- 'auto-commit': { type: 'boolean' },
35
- 'auto-version': { type: 'boolean' },
36
- version: { type: 'string' },
37
- yes: { type: 'boolean', short: 'y' },
38
- help: { type: 'boolean', short: 'h' },
39
- },
40
- allowPositionals: true,
41
- })
42
-
43
- if (help) {
44
- console.log(`Options:
45
- --changelogs <text> Changelogs text for the release
46
- --changelogs-file <path> Read changelogs from a file
47
- --auto-commit Auto-commit unstaged changes before deploying
48
- --auto-version Auto-bump patch version before deploying
49
- --version <version> Set explicit version for the release
50
- -y, --yes Skip all prompts
51
- -h, --help Show this help message`)
52
- process.exit(0)
53
- }
54
-
55
- // Check git status
56
- const { exitCode } = await sh`cd ${cwd} && git status`.quiet().nothrow()
57
- const isGitRepo = exitCode === 0
58
-
59
- if (!isGitRepo && !yes) {
60
- const confirmContinue = prompt('No git repository found, continue? (y/n)')
61
- if (confirmContinue !== 'y') throw new Error('Deployment cancelled')
62
- }
63
-
64
- // Read application.json
65
- const app = await readJson(`${cwd}/application.json`)
66
- const config = await readJson(`${cwd}/.bricks/build/application-config.json`)
67
-
68
- // Resolve version: explicit flag > auto-bump > package.json
69
- const pkgPath = `${cwd}/package.json`
70
- const pkgExists = await exists(pkgPath)
71
- let version: string | undefined
72
-
73
- if (versionArg) {
74
- version = versionArg
75
- if (pkgExists) {
76
- const pkg = await readJson(pkgPath)
77
- pkg.version = version
78
- await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
79
- }
80
- } else if (autoVersion && pkgExists) {
81
- const pkg = await readJson(pkgPath)
82
- const parts = (pkg.version || '0.0.0').split('.')
83
- parts[2] = String(Number(parts[2] || 0) + 1)
84
- version = parts.join('.')
85
- pkg.version = version
86
- await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
87
- } else {
88
- version = pkgExists ? (await readJson(pkgPath)).version : undefined
89
- }
90
-
91
- // Get changelog from flag or file
92
- let changelogs = ''
93
- if (changelogsArg) {
94
- changelogs = changelogsArg
95
- } else if (changelogsFile) {
96
- if (!(await exists(changelogsFile))) {
97
- throw new Error(`Changelogs file not found: ${changelogsFile}`)
98
- }
99
- changelogs = await readFile(changelogsFile, 'utf8')
100
- } else if (!yes) {
101
- changelogs = prompt('Enter changelogs (optional, press Enter to skip):') || ''
102
- }
103
-
104
- // Handle unstaged changes
105
- let commitId = ''
106
- if (isGitRepo) {
107
- const unstagedChanges = await sh`cd ${cwd} && git diff --name-only --diff-filter=ACMR`.text()
108
- if (unstagedChanges) {
109
- if (autoCommit) {
110
- const commitMsg = `chore: release ${version || 'new version'}`
111
- const commitBody = changelogs || '[no changelogs]'
112
- await sh`cd ${cwd} && git add -A`
113
- const commitArgs = await buildCommitArgs(cwd, [commitMsg, commitBody])
114
- await sh`cd ${cwd} && git ${commitArgs}`
115
- } else {
116
- throw new Error(
117
- 'Unstaged changes found, please commit or stash your changes before deploying',
118
- )
119
- }
120
- }
121
- commitId = (await sh`cd ${cwd} && git rev-parse HEAD`.text()).trim()
122
- }
123
-
124
- // Ask for confirmation
125
- if (!yes) {
126
- const confirm = prompt('Are you sure you want to deploy? (y/n)')
127
- if (confirm !== 'y') throw new Error('Deployment cancelled')
128
- }
129
-
130
- const isModule = app.type === 'module'
131
- const command = isModule ? 'module' : 'app'
132
-
133
- // Add project-specific fields to config and write to temp file
134
- const releaseConfig = {
135
- ...config,
136
- title: version || config.title,
137
- bricks_project_last_commit_id: commitId || undefined,
138
- }
139
- const configPath = `${cwd}/.bricks/build/release-config.json`
140
- await writeFile(configPath, JSON.stringify(releaseConfig))
141
-
142
- const args = ['bricks', command, 'release', app.id, '-c', configPath, '--json']
143
-
144
- if (version) {
145
- args.push('--version', version)
146
- }
147
-
148
- if (changelogs) {
149
- args.push('--changelogs', changelogs)
150
- }
151
-
152
- const result = await sh`${args}`.quiet().nothrow()
153
-
154
- if (result.exitCode !== 0) {
155
- const output = result.stderr.toString() || result.stdout.toString()
156
- try {
157
- const json = JSON.parse(output)
158
- throw new Error(json.error || 'Release failed')
159
- } catch {
160
- throw new Error(output || 'Release failed')
161
- }
162
- }
163
-
164
- const output = JSON.parse(result.stdout.toString())
165
- console.log(`${isModule ? 'Module' : 'App'} deployed: ${output.name}`)
4
+ forwardLegacyCtorTool('deploy', import.meta.url)
@@ -1,28 +1,4 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
1
+ #!/usr/bin/env bun
2
+ import { forwardLegacyCtorTool } from './_forward.ts'
3
3
 
4
- import { register as registerCompile } from './mcp-tools/compile'
5
- import { register as registerLottie } from './mcp-tools/lottie'
6
- import { register as registerIcons } from './mcp-tools/icons'
7
- import { register as registerHuggingface } from './mcp-tools/huggingface'
8
- import { register as registerMedia } from './mcp-tools/media'
9
-
10
- const server = new McpServer({
11
- name: 'bricks-ctor',
12
- version: '1.0.0',
13
- })
14
-
15
- const { dirname } = import.meta
16
- const projectDir = String(dirname).split('/node_modules/')[0]
17
-
18
- // NOTE: Cursor (Or VSCode) seems set ELECTRON_RUN_AS_NODE to 1, so we need to unset it
19
- process.env.ELECTRON_RUN_AS_NODE = ''
20
-
21
- registerCompile(server, projectDir)
22
- registerLottie(server)
23
- registerIcons(server)
24
- registerHuggingface(server)
25
- registerMedia(server, projectDir)
26
-
27
- const transport = new StdioServerTransport()
28
- await server.connect(transport)
4
+ forwardLegacyCtorTool('mcp', import.meta.url)
@@ -1,235 +1,4 @@
1
- import {
2
- cp,
3
- lstat,
4
- mkdir,
5
- readFile,
6
- readdir,
7
- readlink,
8
- rm,
9
- stat,
10
- symlink,
11
- writeFile,
12
- } from 'fs/promises'
13
- import * as path from 'path'
14
- import TOML from '@iarna/toml'
1
+ #!/usr/bin/env bun
2
+ import { forwardLegacyCtorTool } from './_forward.ts'
15
3
 
16
- const cwd = process.cwd()
17
- const projectSkillsDir = path.join(cwd, '.bricks', 'skills')
18
- const compatibilitySkillLinks = [
19
- path.join(cwd, '.claude', 'skills'),
20
- path.join(cwd, '.codex', 'skills'),
21
- ]
22
-
23
- async function exists(f: string) {
24
- try {
25
- await stat(f)
26
- return true
27
- } catch {
28
- return false
29
- }
30
- }
31
-
32
- async function pathExists(f: string) {
33
- try {
34
- await lstat(f)
35
- return true
36
- } catch {
37
- return false
38
- }
39
- }
40
-
41
- // Migrate old projects: remove legacy project/ directory
42
- const oldProjectDir = path.join(cwd, 'project')
43
- if (await exists(oldProjectDir)) {
44
- await rm(oldProjectDir, { recursive: true, force: true })
45
- console.log('Removed legacy project/ directory')
46
- }
47
-
48
- // handle flag --skip-copy
49
- const skipCopyProject = process.argv.includes('--skip-copy-project')
50
- if (skipCopyProject) {
51
- console.log('Skipping copy of files to ctor/')
52
- } else {
53
- const libFiles = ['types', 'utils', 'index.ts']
54
-
55
- const ctorDir = path.join(cwd, 'ctor')
56
- await mkdir(ctorDir, { recursive: true })
57
- await Promise.all(
58
- libFiles.map((file) =>
59
- cp(path.join(import.meta.dirname, '..', file), path.join(ctorDir, file), {
60
- recursive: true,
61
- }),
62
- ),
63
- )
64
- console.log('Copied files to ctor/')
65
- }
66
-
67
- const projectMcpServer = {
68
- command: 'bun',
69
- args: [`${cwd}/node_modules/@fugood/bricks-ctor/tools/mcp-server.ts`],
70
- }
71
-
72
- type CodexMcpConfig = {
73
- mcp_servers: Record<string, typeof projectMcpServer>
74
- }
75
-
76
- // Claude Code and AGENTS.md projects both use the shared project .mcp.json file.
77
- const defaultMcpConfig = {
78
- mcpServers: {
79
- 'bricks-ctor': projectMcpServer,
80
- },
81
- }
82
-
83
- const handleMcpConfigOverride = async (mcpConfigPath: string) => {
84
- let mcpConfig: { mcpServers: Record<string, typeof projectMcpServer> } | null = null
85
- if (await exists(mcpConfigPath)) {
86
- const configStr = await readFile(mcpConfigPath, 'utf-8')
87
- try {
88
- mcpConfig = JSON.parse(configStr)
89
- if (!mcpConfig?.mcpServers) throw new Error('mcpServers is not defined')
90
- mcpConfig.mcpServers['bricks-ctor'] = projectMcpServer
91
- delete mcpConfig.mcpServers['bricks-project']
92
- } catch {
93
- mcpConfig = defaultMcpConfig
94
- }
95
- } else {
96
- mcpConfig = defaultMcpConfig
97
- }
98
-
99
- await writeFile(mcpConfigPath, `${JSON.stringify(mcpConfig, null, 2)}\n`)
100
-
101
- console.log(`Updated ${mcpConfigPath}`)
102
- }
103
-
104
- const hasClaudeCode = await exists(`${cwd}/CLAUDE.md`)
105
- const hasAgentsMd = await exists(`${cwd}/AGENTS.md`)
106
-
107
- if (hasClaudeCode || hasAgentsMd) {
108
- // Keep the workspace-level JSON MCP config aligned for tools that read .mcp.json.
109
- const mcpConfigPath = `${cwd}/.mcp.json`
110
- await handleMcpConfigOverride(mcpConfigPath)
111
- }
112
-
113
- const copyMissingSkills = async (sourceDir: string, targetDir: string) => {
114
- if (!(await exists(sourceDir))) return
115
-
116
- const packageSkills = await readdir(sourceDir, { withFileTypes: true })
117
- const skillsToInstall = packageSkills.filter(
118
- (entry) => entry.isDirectory() && !entry.name.startsWith('.'),
119
- )
120
-
121
- await mkdir(targetDir, { recursive: true })
122
-
123
- await Promise.all(
124
- skillsToInstall.map(async (entry) => {
125
- const targetSkillDir = path.join(targetDir, entry.name)
126
- if (await exists(targetSkillDir)) {
127
- console.log(`Skill '${entry.name}' already exists, skipping`)
128
- } else {
129
- await cp(path.join(sourceDir, entry.name), targetSkillDir, { recursive: true })
130
- console.log(`Installed skill '${entry.name}' to ${targetDir}/`)
131
- }
132
- }),
133
- )
134
- }
135
-
136
- const migrateSkillsDir = async (legacySkillsDir: string, canonicalSkillsDir: string) => {
137
- if (!(await pathExists(legacySkillsDir))) return
138
-
139
- const legacyStats = await lstat(legacySkillsDir)
140
-
141
- if (legacyStats.isSymbolicLink()) {
142
- const linkTarget = await readlink(legacySkillsDir)
143
- const resolvedTarget = path.resolve(path.dirname(legacySkillsDir), linkTarget)
144
- if (resolvedTarget === canonicalSkillsDir) return
145
-
146
- await copyMissingSkills(resolvedTarget, canonicalSkillsDir)
147
- await rm(legacySkillsDir, { force: true, recursive: true })
148
- return
149
- }
150
-
151
- if (legacyStats.isDirectory()) {
152
- await copyMissingSkills(legacySkillsDir, canonicalSkillsDir)
153
- await rm(legacySkillsDir, { force: true, recursive: true })
154
- return
155
- }
156
-
157
- console.warn(`Skipping skills migration for ${legacySkillsDir}; expected a directory or symlink`)
158
- }
159
-
160
- const ensureCompatibilitySkillLink = async (linkPath: string, targetDir: string) => {
161
- await mkdir(path.dirname(linkPath), { recursive: true })
162
-
163
- if (await pathExists(linkPath)) {
164
- const linkStats = await lstat(linkPath)
165
- if (linkStats.isSymbolicLink()) {
166
- const linkTarget = await readlink(linkPath)
167
- const resolvedTarget = path.resolve(path.dirname(linkPath), linkTarget)
168
- if (resolvedTarget === targetDir) return
169
- } else {
170
- console.warn(
171
- `Skipping skills symlink at ${linkPath}; path already exists and is not a symlink`,
172
- )
173
- return
174
- }
175
- }
176
-
177
- const relativeTarget = path.relative(path.dirname(linkPath), targetDir)
178
- const symlinkType = process.platform === 'win32' ? 'junction' : 'dir'
179
- await symlink(relativeTarget, linkPath, symlinkType)
180
- console.log(`Linked ${linkPath} -> ${relativeTarget}`)
181
- }
182
-
183
- const setupSkills = async () => {
184
- const packageSkillsDir = path.join(import.meta.dirname, '..', 'skills')
185
- await mkdir(projectSkillsDir, { recursive: true })
186
- await copyMissingSkills(packageSkillsDir, projectSkillsDir)
187
-
188
- for (const linkPath of compatibilitySkillLinks) {
189
- await migrateSkillsDir(linkPath, projectSkillsDir)
190
- await ensureCompatibilitySkillLink(linkPath, projectSkillsDir)
191
- }
192
- }
193
-
194
- if (hasClaudeCode || hasAgentsMd) {
195
- // Install project skills once and expose them through compatibility symlinks.
196
- await setupSkills()
197
- }
198
-
199
- if (hasAgentsMd) {
200
- // Codex stores its project-local MCP config in .codex/config.toml.
201
- const defaultCodexMcpConfig = {
202
- mcp_servers: {
203
- 'bricks-ctor': projectMcpServer,
204
- },
205
- }
206
-
207
- const handleCodexMcpConfigOverride = async (mcpConfigPath: string) => {
208
- let mcpConfig: CodexMcpConfig | null = null
209
- if (await exists(mcpConfigPath)) {
210
- const configStr = await readFile(mcpConfigPath, 'utf-8')
211
- try {
212
- const parsed = TOML.parse(configStr) as Partial<CodexMcpConfig>
213
- if (!parsed?.mcp_servers) throw new Error('mcp_servers is not defined')
214
- mcpConfig = { mcp_servers: parsed.mcp_servers }
215
- mcpConfig.mcp_servers['bricks-ctor'] = projectMcpServer
216
- delete mcpConfig.mcp_servers['bricks-project']
217
- } catch {
218
- mcpConfig = defaultCodexMcpConfig
219
- }
220
- } else {
221
- mcpConfig = defaultCodexMcpConfig
222
- }
223
-
224
- await writeFile(mcpConfigPath, `${TOML.stringify(mcpConfig)}\n`)
225
-
226
- console.log(`Updated ${mcpConfigPath}`)
227
- }
228
-
229
- // Keep the Codex TOML MCP config aligned with the same bricks-ctor server entry.
230
- const codexConfigPath = `${cwd}/.codex/config.toml`
231
- await handleCodexMcpConfigOverride(codexConfigPath)
232
- }
233
-
234
- // TODO: .cursor/skills if needed
235
- // TODO: User setting in application.json to avoid unnecessary skills/config setup
4
+ forwardLegacyCtorTool('postinstall', import.meta.url)