@fugood/bricks-ctor 2.25.0-beta.7 → 2.25.0-beta.70

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 (135) 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/action-name-map.ts +0 -1015
  11. package/compile/index.ts +0 -1278
  12. package/compile/util.ts +0 -358
  13. package/index.ts +0 -6
  14. package/skills/bricks-ctor/SKILL.md +0 -32
  15. package/skills/bricks-ctor/rules/animation.md +0 -159
  16. package/skills/bricks-ctor/rules/architecture-patterns.md +0 -69
  17. package/skills/bricks-ctor/rules/automations.md +0 -221
  18. package/skills/bricks-ctor/rules/buttress.md +0 -156
  19. package/skills/bricks-ctor/rules/data-calculation.md +0 -209
  20. package/skills/bricks-ctor/rules/local-sync.md +0 -129
  21. package/skills/bricks-ctor/rules/media-flow.md +0 -158
  22. package/skills/bricks-ctor/rules/remote-data-bank.md +0 -196
  23. package/skills/bricks-ctor/rules/standby-transition.md +0 -124
  24. package/skills/bricks-design/LICENSE.txt +0 -180
  25. package/skills/bricks-design/SKILL.md +0 -66
  26. package/skills/rive-marketplace/SKILL.md +0 -99
  27. package/tools/_git-author.ts +0 -29
  28. package/tools/_shell.ts +0 -173
  29. package/tools/icons/.gitattributes +0 -1
  30. package/tools/icons/fa6pro-glyphmap.json +0 -4686
  31. package/tools/icons/fa6pro-meta.json +0 -1
  32. package/tools/mcp-tools/compile.ts +0 -92
  33. package/tools/mcp-tools/huggingface.ts +0 -762
  34. package/tools/mcp-tools/icons.ts +0 -81
  35. package/tools/mcp-tools/lottie.ts +0 -102
  36. package/tools/mcp-tools/media.ts +0 -110
  37. package/tools/preview-main.mjs +0 -293
  38. package/tools/preview.ts +0 -150
  39. package/types/animation.ts +0 -100
  40. package/types/automation.ts +0 -235
  41. package/types/brick-base.ts +0 -80
  42. package/types/bricks/Camera.ts +0 -246
  43. package/types/bricks/Chart.ts +0 -372
  44. package/types/bricks/GenerativeMedia.ts +0 -290
  45. package/types/bricks/Icon.ts +0 -98
  46. package/types/bricks/Image.ts +0 -126
  47. package/types/bricks/Items.ts +0 -476
  48. package/types/bricks/Lottie.ts +0 -168
  49. package/types/bricks/Maps.ts +0 -262
  50. package/types/bricks/QrCode.ts +0 -117
  51. package/types/bricks/Rect.ts +0 -150
  52. package/types/bricks/RichText.ts +0 -128
  53. package/types/bricks/Rive.ts +0 -220
  54. package/types/bricks/Sketch.ts +0 -254
  55. package/types/bricks/Slideshow.ts +0 -201
  56. package/types/bricks/Svg.ts +0 -99
  57. package/types/bricks/Text.ts +0 -148
  58. package/types/bricks/TextInput.ts +0 -242
  59. package/types/bricks/Video.ts +0 -175
  60. package/types/bricks/VideoStreaming.ts +0 -112
  61. package/types/bricks/WebRtcStream.ts +0 -65
  62. package/types/bricks/WebView.ts +0 -168
  63. package/types/bricks/index.ts +0 -22
  64. package/types/canvas.ts +0 -82
  65. package/types/common.ts +0 -144
  66. package/types/data-calc-command.ts +0 -7005
  67. package/types/data-calc-script.ts +0 -21
  68. package/types/data-calc.ts +0 -11
  69. package/types/data.ts +0 -95
  70. package/types/generators/AlarmClock.ts +0 -110
  71. package/types/generators/Assistant.ts +0 -621
  72. package/types/generators/BleCentral.ts +0 -247
  73. package/types/generators/BlePeripheral.ts +0 -208
  74. package/types/generators/CanvasMap.ts +0 -74
  75. package/types/generators/CastlesPay.ts +0 -87
  76. package/types/generators/DataBank.ts +0 -160
  77. package/types/generators/File.ts +0 -432
  78. package/types/generators/GraphQl.ts +0 -132
  79. package/types/generators/Http.ts +0 -222
  80. package/types/generators/HttpServer.ts +0 -176
  81. package/types/generators/Information.ts +0 -103
  82. package/types/generators/Intent.ts +0 -168
  83. package/types/generators/Iterator.ts +0 -108
  84. package/types/generators/Keyboard.ts +0 -105
  85. package/types/generators/LlmAnthropicCompat.ts +0 -212
  86. package/types/generators/LlmAppleBuiltin.ts +0 -159
  87. package/types/generators/LlmGgml.ts +0 -861
  88. package/types/generators/LlmMediaTekNeuroPilot.ts +0 -235
  89. package/types/generators/LlmMlx.ts +0 -227
  90. package/types/generators/LlmOnnx.ts +0 -213
  91. package/types/generators/LlmOpenAiCompat.ts +0 -312
  92. package/types/generators/LlmQualcommAiEngine.ts +0 -247
  93. package/types/generators/Mcp.ts +0 -637
  94. package/types/generators/McpServer.ts +0 -289
  95. package/types/generators/MediaFlow.ts +0 -170
  96. package/types/generators/MqttBroker.ts +0 -141
  97. package/types/generators/MqttClient.ts +0 -141
  98. package/types/generators/Question.ts +0 -408
  99. package/types/generators/RealtimeTranscription.ts +0 -287
  100. package/types/generators/RerankerGgml.ts +0 -191
  101. package/types/generators/SerialPort.ts +0 -151
  102. package/types/generators/SoundPlayer.ts +0 -94
  103. package/types/generators/SoundRecorder.ts +0 -130
  104. package/types/generators/SpeechToTextGgml.ts +0 -419
  105. package/types/generators/SpeechToTextOnnx.ts +0 -236
  106. package/types/generators/SpeechToTextPlatform.ts +0 -85
  107. package/types/generators/SqLite.ts +0 -159
  108. package/types/generators/Step.ts +0 -107
  109. package/types/generators/SttAppleBuiltin.ts +0 -130
  110. package/types/generators/Tcp.ts +0 -126
  111. package/types/generators/TcpServer.ts +0 -147
  112. package/types/generators/TextToSpeechAppleBuiltin.ts +0 -127
  113. package/types/generators/TextToSpeechGgml.ts +0 -221
  114. package/types/generators/TextToSpeechOnnx.ts +0 -178
  115. package/types/generators/TextToSpeechOpenAiLike.ts +0 -121
  116. package/types/generators/ThermalPrinter.ts +0 -191
  117. package/types/generators/Tick.ts +0 -83
  118. package/types/generators/Udp.ts +0 -120
  119. package/types/generators/VadGgml.ts +0 -250
  120. package/types/generators/VadOnnx.ts +0 -231
  121. package/types/generators/VadTraditional.ts +0 -138
  122. package/types/generators/VectorStore.ts +0 -257
  123. package/types/generators/Watchdog.ts +0 -107
  124. package/types/generators/WebCrawler.ts +0 -103
  125. package/types/generators/WebRtc.ts +0 -181
  126. package/types/generators/WebSocket.ts +0 -148
  127. package/types/generators/index.ts +0 -57
  128. package/types/index.ts +0 -13
  129. package/types/subspace.ts +0 -60
  130. package/types/switch.ts +0 -51
  131. package/types/system.ts +0 -707
  132. package/utils/calc.ts +0 -126
  133. package/utils/data.ts +0 -497
  134. package/utils/event-props.ts +0 -886
  135. 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.7",
4
- "main": "index.ts",
5
- "scripts": {
6
- "typecheck": "tsc --noEmit",
7
- "build": "bun scripts/build.js"
8
- },
3
+ "version": "2.25.0-beta.70",
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.6",
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.70"
27
7
  },
28
- "gitHead": "e43200271cec0b015b1044e1b0041cad575f030f"
8
+ "gitHead": "3587a7e806a776b3ecc868738def1d4630f485ec"
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)