@brms/ai-skills 0.1.0

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 (90) hide show
  1. package/README.md +256 -0
  2. package/bin/brms-skills.mjs +411 -0
  3. package/package.json +30 -0
  4. package/skills/brms-prototype-generator/SKILL.md +129 -0
  5. package/skills/brms-prototype-generator/agents/openai.yaml +7 -0
  6. package/skills/brms-prototype-generator/examples/few-shot-examples.md +577 -0
  7. package/skills/brms-prototype-generator/references/01-list-query.md +444 -0
  8. package/skills/brms-prototype-generator/references/02-form-entry.md +129 -0
  9. package/skills/brms-prototype-generator/references/03-detail-display.md +125 -0
  10. package/skills/brms-prototype-generator/references/04-composite-page-package.md +339 -0
  11. package/skills/brms-prototype-generator/references/05-dialog-patterns.md +113 -0
  12. package/skills/brms-prototype-generator/references/06-backend-request-patterns.md +118 -0
  13. package/skills/brms-prototype-generator/references/resource-index.md +46 -0
  14. package/skills/brms-prototype-generator/references/system-prompt.md +242 -0
  15. package/skills/brms-prototype-generator/scripts/analyze-doc.mjs +554 -0
  16. package/skills/brms-prototype-generator/scripts/check-project.mjs +228 -0
  17. package/skills/brms-prototype-generator/scripts/discover-targets.mjs +158 -0
  18. package/skills/brms-prototype-generator/scripts/install-codex.mjs +74 -0
  19. package/skills/brms-prototype-generator/scripts/plan-pages.mjs +390 -0
  20. package/skills/brms-prototype-generator/scripts/validate-generated.mjs +838 -0
  21. package/skills/brms-prototype-generator/templates/user-input-template.md +182 -0
  22. package/skills/brms-vxe-plus-developer/SKILL.md +105 -0
  23. package/skills/brms-vxe-plus-developer/agents/openai.yaml +7 -0
  24. package/skills/brms-vxe-plus-developer/references/prototype-to-real.md +54 -0
  25. package/skills/brms-vxe-plus-developer/references/real-base-development.md +110 -0
  26. package/skills/brms-vxe-plus-developer/references/resource-index.md +43 -0
  27. package/skills/brms-vxe-plus-developer/references/review-checklist.md +49 -0
  28. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/01-mental-model.md +150 -0
  29. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/02-vxe-plus-form.md +302 -0
  30. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/03-vxe-plus-table.md +253 -0
  31. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/04-example-map.md +488 -0
  32. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/05-request-and-eiinfo.md +170 -0
  33. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/90-anti-patterns.md +137 -0
  34. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/README.md +43 -0
  35. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/README.md +21 -0
  36. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/A1/P0/A1P01601.vue +483 -0
  37. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/A1/P1/A1P11011.vue +444 -0
  38. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/AB/BP/ABBP0201.vue +1648 -0
  39. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/AM/AF/component/AMAF0601/Bidding/formConfig.ts +228 -0
  40. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/AM/AF/component/AMAF0601/Record/columns.ts +110 -0
  41. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/BM/BR/BMBR01.vue +130 -0
  42. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/BM/BR/component/BMBR01/columns.ts +94 -0
  43. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/BM/BR/component/BMBR01/formConfig.ts +108 -0
  44. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Change/formConfig.ts +123 -0
  45. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Change/index.vue +103 -0
  46. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Clause/columns.ts +48 -0
  47. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Clause/index.vue +202 -0
  48. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Correcte/formConfig.ts +117 -0
  49. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Correcte/index.vue +103 -0
  50. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Pay/Payment/formConfig.ts +90 -0
  51. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Pay/Payment/index.vue +42 -0
  52. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Pay/columns.ts +376 -0
  53. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Pay/index.vue +619 -0
  54. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/Domestic/formConfig.ts +73 -0
  55. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/Domestic/index.vue +47 -0
  56. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/Foreign/formConfig.ts +141 -0
  57. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/Foreign/index.vue +42 -0
  58. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/columns.ts +123 -0
  59. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/index.vue +593 -0
  60. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/index.vue +68 -0
  61. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Fee/columns.ts +150 -0
  62. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Fee/index.vue +235 -0
  63. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Files/columns.ts +63 -0
  64. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Files/index.vue +117 -0
  65. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Goods/columns.ts +327 -0
  66. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Goods/index.vue +790 -0
  67. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Approve/formConfig.ts +341 -0
  68. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Approve/index.vue +63 -0
  69. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Approve2/formConfig.ts +232 -0
  70. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Approve2/index.vue +27 -0
  71. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Diff/columns.ts +46 -0
  72. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Diff/index.vue +92 -0
  73. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/formConfig.ts +979 -0
  74. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/index.vue +62 -0
  75. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Other/formConfig.ts +179 -0
  76. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Other/index.vue +140 -0
  77. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Sign/formConfig.ts +118 -0
  78. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Sign/index.vue +44 -0
  79. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/index.vue +168 -0
  80. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Party/Major/formConfig.ts +257 -0
  81. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Party/Major/index.vue +47 -0
  82. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Party/columns.ts +256 -0
  83. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Party/index.vue +738 -0
  84. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Price/formConfig.ts +174 -0
  85. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Price/index.vue +51 -0
  86. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Top/index.vue +924 -0
  87. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/SM/SW/SMSW0101.vue +567 -0
  88. package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/demo/index.vue +448 -0
  89. package/skills/brms-vxe-plus-developer/scripts/check-project.mjs +259 -0
  90. package/skills/brms-vxe-plus-developer/scripts/check-vxe-plus-page.mjs +137 -0
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+
5
+ function parseArgs(argv) {
6
+ const args = {
7
+ repoRoot: process.cwd(),
8
+ targetProject: '',
9
+ format: 'markdown',
10
+ }
11
+
12
+ for (let i = 0; i < argv.length; i += 1) {
13
+ const arg = argv[i]
14
+ if (arg === '--repo-root') {
15
+ args.repoRoot = argv[i + 1] || ''
16
+ i += 1
17
+ }
18
+ else if (arg === '--target-project') {
19
+ args.targetProject = argv[i + 1] || ''
20
+ i += 1
21
+ }
22
+ else if (arg === '--format') {
23
+ args.format = argv[i + 1] || ''
24
+ i += 1
25
+ }
26
+ else if (arg === '--help' || arg === '-h') {
27
+ args.help = true
28
+ }
29
+ else {
30
+ throw new Error(`Unknown argument: ${arg}`)
31
+ }
32
+ }
33
+
34
+ if (!['json', 'markdown', 'both'].includes(args.format))
35
+ throw new Error('--format must be one of: json, markdown, both')
36
+
37
+ return args
38
+ }
39
+
40
+ function existsDir(filePath) {
41
+ return fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()
42
+ }
43
+
44
+ function existsFile(filePath) {
45
+ return fs.existsSync(filePath) && fs.statSync(filePath).isFile()
46
+ }
47
+
48
+ function readJson(filePath) {
49
+ if (!existsFile(filePath))
50
+ return null
51
+ try {
52
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'))
53
+ }
54
+ catch {
55
+ return null
56
+ }
57
+ }
58
+
59
+ function rel(filePath, root) {
60
+ return path.relative(root, filePath).replaceAll(path.sep, '/')
61
+ }
62
+
63
+ function hasAnyDependency(pkg, names) {
64
+ const deps = {
65
+ ...(pkg?.dependencies || {}),
66
+ ...(pkg?.devDependencies || {}),
67
+ }
68
+ return names.some(name => Object.prototype.hasOwnProperty.call(deps, name))
69
+ }
70
+
71
+ function findProjects(repoRoot, targetProject) {
72
+ const projectRoot = path.join(repoRoot, 'project')
73
+ if (!existsDir(projectRoot))
74
+ return []
75
+
76
+ const explicitTarget = Boolean(targetProject)
77
+ const requested = targetProject
78
+ ? [targetProject.replaceAll('\\', '/').replace(/^project\//, '')]
79
+ : fs.readdirSync(projectRoot, { withFileTypes: true }).filter(entry => entry.isDirectory()).map(entry => entry.name)
80
+
81
+ return requested.flatMap((name) => {
82
+ const projectDir = path.join(projectRoot, name)
83
+ const packageJson = path.join(projectDir, 'package.json')
84
+ const pkg = readJson(packageJson)
85
+ const srcDir = path.join(projectDir, 'src')
86
+ const viewsDir = path.join(srcDir, 'views')
87
+ const routeConfig = path.join(srcDir, 'routers', 'config', 'index.ts')
88
+ const localMenu = path.join(srcDir, 'routers', 'localMenuList.json')
89
+ const dynamicRouter = path.join(srcDir, 'routers', 'modules', 'dynamicRouter.ts')
90
+
91
+ if (!explicitTarget && !existsDir(viewsDir))
92
+ return []
93
+
94
+ return [{
95
+ id: name,
96
+ exists: existsDir(projectDir),
97
+ packageName: pkg?.name || '',
98
+ projectDir: existsDir(projectDir) ? rel(projectDir, repoRoot) : `project/${name}`,
99
+ srcDir: existsDir(srcDir) ? rel(srcDir, repoRoot) : '',
100
+ viewsDir: existsDir(viewsDir) ? rel(viewsDir, repoRoot) : '',
101
+ hasVueVite: hasAnyDependency(pkg, ['vue', 'vite', '@vitejs/plugin-vue']),
102
+ hasVxePlus: hasAnyDependency(pkg, ['@vxe-plus/components', 'vxe-table', 'vxe-pc-ui']),
103
+ hasEiInfo: hasAnyDependency(pkg, ['@eplat/ei']),
104
+ routeStyle: existsFile(routeConfig)
105
+ ? 'config'
106
+ : existsFile(localMenu)
107
+ ? 'local-menu'
108
+ : existsFile(dynamicRouter)
109
+ ? 'dynamic-router'
110
+ : 'unknown',
111
+ routeFile: existsFile(routeConfig)
112
+ ? rel(routeConfig, repoRoot)
113
+ : existsFile(localMenu)
114
+ ? rel(localMenu, repoRoot)
115
+ : existsFile(dynamicRouter)
116
+ ? rel(dynamicRouter, repoRoot)
117
+ : '',
118
+ }]
119
+ })
120
+ }
121
+
122
+ function check(repoRoot, targetProject) {
123
+ const rootPkg = readJson(path.join(repoRoot, 'package.json'))
124
+ const workspaceFile = path.join(repoRoot, 'pnpm-workspace.yaml')
125
+ const blockers = []
126
+ const warnings = []
127
+ const signals = []
128
+
129
+ if (!rootPkg)
130
+ blockers.push('repo root is missing a readable package.json')
131
+ else
132
+ signals.push(`repo package: ${rootPkg.name || '(unnamed)'}`)
133
+
134
+ if (!existsFile(workspaceFile))
135
+ blockers.push('repo root is missing pnpm-workspace.yaml')
136
+ else
137
+ signals.push('pnpm workspace detected')
138
+
139
+ if (rootPkg?.volta?.node && rootPkg.volta.node !== '18.17.0')
140
+ warnings.push(`repo Volta node is ${rootPkg.volta.node}; skill was validated on 18.17.0`)
141
+ if (rootPkg?.volta?.pnpm && rootPkg.volta.pnpm !== '8.10.4')
142
+ warnings.push(`repo Volta pnpm is ${rootPkg.volta.pnpm}; skill was validated on 8.10.4`)
143
+
144
+ if (!hasAnyDependency(rootPkg, ['@eplat/ei']))
145
+ warnings.push('root package does not declare @eplat/ei; prototype can still use mock mode')
146
+
147
+ const hasExplicitTarget = Boolean(targetProject)
148
+ const projects = findProjects(repoRoot, targetProject)
149
+ if (!projects.length)
150
+ blockers.push('no project candidates found under project/*')
151
+ if (!hasExplicitTarget && projects.length > 1)
152
+ blockers.push('multiple project candidates found; ask the user to choose one and rerun with --target-project project/<name>')
153
+
154
+ for (const project of projects) {
155
+ if (!project.exists) {
156
+ blockers.push(`${project.projectDir} does not exist`)
157
+ continue
158
+ }
159
+ if (!project.srcDir)
160
+ blockers.push(`${project.projectDir} is missing src/`)
161
+ if (!project.viewsDir)
162
+ blockers.push(`${project.projectDir} is missing src/views/`)
163
+ if (!project.hasVueVite)
164
+ warnings.push(`${project.projectDir} does not clearly declare Vue/Vite dependencies`)
165
+ if (!project.hasVxePlus)
166
+ warnings.push(`${project.projectDir} does not clearly declare VxePlus/Vxe dependencies`)
167
+ if (project.routeStyle === 'unknown')
168
+ warnings.push(`${project.projectDir} route/menu registration style was not detected; plan route handoff manually`)
169
+ else
170
+ signals.push(`${project.projectDir} route style: ${project.routeStyle} (${project.routeFile})`)
171
+ }
172
+
173
+ return {
174
+ ok: blockers.length === 0,
175
+ repoRoot,
176
+ skill: 'brms-prototype-generator',
177
+ needsTargetProject: !hasExplicitTarget && projects.length > 1,
178
+ blockers,
179
+ warnings,
180
+ signals,
181
+ projects,
182
+ }
183
+ }
184
+
185
+ function toMarkdown(report) {
186
+ const lines = [`# ${report.skill} Project Check`, '', `- Status: ${report.ok ? 'ok' : 'blocked'}`]
187
+ if (report.blockers.length) {
188
+ lines.push('', '## Blockers', ...report.blockers.map(item => `- ${item}`))
189
+ }
190
+ if (report.warnings.length) {
191
+ lines.push('', '## Warnings', ...report.warnings.map(item => `- ${item}`))
192
+ }
193
+ if (report.signals.length) {
194
+ lines.push('', '## Signals', ...report.signals.map(item => `- ${item}`))
195
+ }
196
+ if (report.projects.length) {
197
+ lines.push('', '## Projects')
198
+ for (const project of report.projects) {
199
+ lines.push(`- ${project.projectDir}: ${project.packageName || '(no package name)'}`)
200
+ }
201
+ }
202
+ return lines.join('\n')
203
+ }
204
+
205
+ function main() {
206
+ const args = parseArgs(process.argv.slice(2))
207
+ if (args.help) {
208
+ console.log('Usage: node scripts/check-project.mjs [--repo-root <repo>] [--target-project project/name] [--format json|markdown|both]')
209
+ process.exit(0)
210
+ }
211
+
212
+ const report = check(path.resolve(args.repoRoot), args.targetProject)
213
+ if (args.format === 'json' || args.format === 'both')
214
+ console.log(JSON.stringify(report, null, 2))
215
+ if (args.format === 'both')
216
+ console.log('\n---\n')
217
+ if (args.format === 'markdown' || args.format === 'both')
218
+ console.log(toMarkdown(report))
219
+ process.exit(report.ok ? 0 : 1)
220
+ }
221
+
222
+ try {
223
+ main()
224
+ }
225
+ catch (error) {
226
+ console.error(error instanceof Error ? error.message : String(error))
227
+ process.exit(1)
228
+ }
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+
5
+ function parseArgs(argv) {
6
+ const args = {
7
+ repoRoot: process.cwd(),
8
+ format: 'markdown',
9
+ }
10
+
11
+ for (let i = 0; i < argv.length; i += 1) {
12
+ const arg = argv[i]
13
+ if (arg === '--repo-root') {
14
+ args.repoRoot = argv[i + 1] || ''
15
+ i += 1
16
+ }
17
+ else if (arg === '--format') {
18
+ args.format = argv[i + 1] || ''
19
+ i += 1
20
+ }
21
+ else if (arg === '--help' || arg === '-h') {
22
+ args.help = true
23
+ }
24
+ else {
25
+ throw new Error(`Unknown argument: ${arg}`)
26
+ }
27
+ }
28
+
29
+ if (!['json', 'markdown', 'both'].includes(args.format))
30
+ throw new Error('--format must be one of: json, markdown, both')
31
+
32
+ return args
33
+ }
34
+
35
+ function existsDir(filePath) {
36
+ return fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()
37
+ }
38
+
39
+ function existsFile(filePath) {
40
+ return fs.existsSync(filePath) && fs.statSync(filePath).isFile()
41
+ }
42
+
43
+ function toRepoRelative(filePath, repoRoot) {
44
+ return path.relative(repoRoot, filePath).replaceAll(path.sep, '/')
45
+ }
46
+
47
+ function readPackageName(projectDir) {
48
+ const packageJson = path.join(projectDir, 'package.json')
49
+ if (!existsFile(packageJson))
50
+ return ''
51
+
52
+ try {
53
+ return JSON.parse(fs.readFileSync(packageJson, 'utf8')).name || ''
54
+ }
55
+ catch {
56
+ return ''
57
+ }
58
+ }
59
+
60
+ function discover(repoRoot) {
61
+ const projectRoot = path.join(repoRoot, 'project')
62
+ const candidates = []
63
+
64
+ if (!existsDir(projectRoot))
65
+ return { repoRoot, candidates }
66
+
67
+ for (const entry of fs.readdirSync(projectRoot, { withFileTypes: true })) {
68
+ if (!entry.isDirectory())
69
+ continue
70
+
71
+ const projectDir = path.join(projectRoot, entry.name)
72
+ const srcDir = path.join(projectDir, 'src')
73
+ const viewsDir = path.join(srcDir, 'views')
74
+ if (!existsDir(srcDir) && !existsDir(viewsDir))
75
+ continue
76
+
77
+ const routeConfigDir = path.join(srcDir, 'routers', 'config')
78
+ const routeIndex = path.join(routeConfigDir, 'index.ts')
79
+ const localMenu = path.join(srcDir, 'routers', 'localMenuList.json')
80
+ const dynamicRouter = path.join(srcDir, 'routers', 'modules', 'dynamicRouter.ts')
81
+ const routeStyle = existsFile(routeIndex)
82
+ ? 'config'
83
+ : existsFile(localMenu)
84
+ ? 'local-menu'
85
+ : existsFile(dynamicRouter)
86
+ ? 'dynamic-router'
87
+ : 'unknown'
88
+ const routeFile = existsFile(routeIndex)
89
+ ? routeIndex
90
+ : existsFile(localMenu)
91
+ ? localMenu
92
+ : existsFile(dynamicRouter)
93
+ ? dynamicRouter
94
+ : ''
95
+
96
+ candidates.push({
97
+ id: entry.name,
98
+ packageName: readPackageName(projectDir),
99
+ projectDir: toRepoRelative(projectDir, repoRoot),
100
+ srcDir: existsDir(srcDir) ? toRepoRelative(srcDir, repoRoot) : '',
101
+ viewsDir: existsDir(viewsDir) ? toRepoRelative(viewsDir, repoRoot) : '',
102
+ routeConfigDir: existsDir(routeConfigDir) ? toRepoRelative(routeConfigDir, repoRoot) : '',
103
+ routeConfigIndex: existsFile(routeIndex) ? toRepoRelative(routeIndex, repoRoot) : '',
104
+ routeStyle,
105
+ routeFile: routeFile ? toRepoRelative(routeFile, repoRoot) : '',
106
+ canRegisterStaticRoute: routeStyle === 'config',
107
+ })
108
+ }
109
+
110
+ return { repoRoot, candidates }
111
+ }
112
+
113
+ function toMarkdown(report) {
114
+ const lines = ['# BRMS Prototype Target Discovery', '']
115
+ if (!report.candidates.length) {
116
+ lines.push('- No project candidates found under `project/*`.')
117
+ return lines.join('\n')
118
+ }
119
+
120
+ report.candidates.forEach((candidate, index) => {
121
+ lines.push(`## ${index + 1}. ${candidate.id}`)
122
+ lines.push(`- Project: \`${candidate.projectDir}\``)
123
+ if (candidate.packageName)
124
+ lines.push(`- Package: \`${candidate.packageName}\``)
125
+ lines.push(`- Views: ${candidate.viewsDir ? `\`${candidate.viewsDir}\`` : 'missing'}`)
126
+ lines.push(`- Route style: \`${candidate.routeStyle}\`${candidate.routeFile ? ` (\`${candidate.routeFile}\`)` : ''}`)
127
+ if (candidate.routeStyle !== 'config')
128
+ lines.push('- Menu handoff: inspect this route/menu style before writing route files; permission-center or local-menu projects may not use static route config.')
129
+ lines.push('')
130
+ })
131
+
132
+ return lines.join('\n')
133
+ }
134
+
135
+ function main() {
136
+ const args = parseArgs(process.argv.slice(2))
137
+ if (args.help) {
138
+ console.log('Usage: node scripts/discover-targets.mjs [--repo-root <repo>] [--format json|markdown|both]')
139
+ process.exit(0)
140
+ }
141
+
142
+ const repoRoot = path.resolve(args.repoRoot)
143
+ const report = discover(repoRoot)
144
+ if (args.format === 'json' || args.format === 'both')
145
+ console.log(JSON.stringify(report, null, 2))
146
+ if (args.format === 'both')
147
+ console.log('\n---\n')
148
+ if (args.format === 'markdown' || args.format === 'both')
149
+ console.log(toMarkdown(report))
150
+ }
151
+
152
+ try {
153
+ main()
154
+ }
155
+ catch (error) {
156
+ console.error(error instanceof Error ? error.message : String(error))
157
+ process.exit(1)
158
+ }
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ function parseArgs(argv) {
8
+ const args = { target: '' }
9
+
10
+ for (let i = 0; i < argv.length; i += 1) {
11
+ const arg = argv[i]
12
+ if (arg === '--target') {
13
+ args.target = argv[i + 1] || ''
14
+ i += 1
15
+ }
16
+ else if (arg === '--help' || arg === '-h') {
17
+ args.help = true
18
+ }
19
+ else {
20
+ throw new Error(`Unknown argument: ${arg}`)
21
+ }
22
+ }
23
+
24
+ return args
25
+ }
26
+
27
+ function defaultTargetRoot() {
28
+ if (process.env.CODEX_HOME)
29
+ return path.join(process.env.CODEX_HOME, 'skills')
30
+
31
+ return path.join(os.homedir(), '.codex', 'skills')
32
+ }
33
+
34
+ function copySkill(sourceDir, targetDir) {
35
+ const source = path.resolve(sourceDir)
36
+ const target = path.resolve(targetDir)
37
+
38
+ if (source === target)
39
+ throw new Error('Source and target are the same directory.')
40
+
41
+ if (target.startsWith(`${source}${path.sep}`))
42
+ throw new Error('Target cannot be inside the source skill directory.')
43
+
44
+ fs.rmSync(target, { recursive: true, force: true })
45
+ fs.mkdirSync(path.dirname(target), { recursive: true })
46
+ fs.cpSync(source, target, {
47
+ recursive: true,
48
+ filter: src => !src.includes(`${path.sep}.DS_Store`),
49
+ })
50
+ }
51
+
52
+ function main() {
53
+ const args = parseArgs(process.argv.slice(2))
54
+ if (args.help) {
55
+ console.log('Usage: node scripts/install-codex.mjs [--target <codex-skills-dir>]')
56
+ process.exit(0)
57
+ }
58
+
59
+ const scriptPath = fileURLToPath(import.meta.url)
60
+ const skillDir = path.resolve(path.dirname(scriptPath), '..')
61
+ const targetRoot = path.resolve(args.target || defaultTargetRoot())
62
+ const targetDir = path.join(targetRoot, path.basename(skillDir))
63
+
64
+ copySkill(skillDir, targetDir)
65
+ console.log(`Installed ${path.basename(skillDir)} to ${targetDir}`)
66
+ }
67
+
68
+ try {
69
+ main()
70
+ }
71
+ catch (error) {
72
+ console.error(error instanceof Error ? error.message : String(error))
73
+ process.exit(1)
74
+ }