@retailcrm/embed-ui-v1-endpoint 0.9.21 → 0.9.22-alpha.1
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/README.md +70 -11
- package/bin/embed-ui-v1-endpoint-mcp.mjs +10 -0
- package/bin/embed-ui-v1-endpoint.mjs +416 -0
- package/dist/common/targets.cjs +61 -130
- package/dist/common/targets.d.ts +38 -27
- package/dist/common/targets.documentation.d.ts +302 -0
- package/dist/common/targets.js +61 -130
- package/dist/mcp/server.cjs +115 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.js +97 -0
- package/dist/meta.json +618 -0
- package/docs/README.md +8 -6
- package/docs/create-endpoint.md +8 -10
- package/docs/define-page-runner.md +3 -3
- package/docs/define-widget-runner.md +2 -2
- package/docs/layout.md +14 -37
- package/docs/menu-placements.md +11 -13
- package/docs/page-routes.md +12 -14
- package/docs/run-endpoint.md +4 -4
- package/docs/targets/customer-card-communications-after.yml +48 -0
- package/docs/targets/customer-card-inwork-after.yml +48 -0
- package/docs/targets/customer-card-inwork-before.yml +48 -0
- package/docs/targets/customer-card-phone.yml +49 -0
- package/docs/targets/order-card-comment-manager-before.yml +51 -0
- package/docs/targets/order-card-common-after.yml +51 -0
- package/docs/targets/order-card-common-before.yml +51 -0
- package/docs/targets/order-card-customer-after.yml +51 -0
- package/docs/targets/order-card-customer-before.yml +51 -0
- package/docs/targets/order-card-customer-email.yml +51 -0
- package/docs/targets/order-card-customer-phone.yml +51 -0
- package/docs/targets/order-card-delivery-address.yml +51 -0
- package/docs/targets/order-card-delivery-after.yml +51 -0
- package/docs/targets/order-card-delivery-before.yml +51 -0
- package/docs/targets/order-card-dimensions-before.yml +51 -0
- package/docs/targets/order-card-list-after.yml +51 -0
- package/docs/targets/order-card-list-before.yml +51 -0
- package/docs/targets/order-card-payment-before.yml +51 -0
- package/docs/targets/order-card-store-before.yml +51 -0
- package/docs/targets/order-mg-delivery-after.yml +51 -0
- package/docs/targets/order-mg-delivery-before.yml +51 -0
- package/docs/targets/order-mg-list-after.yml +51 -0
- package/docs/targets/order-mg-list-before.yml +51 -0
- package/docs/targets/order-mg-payment-after.yml +51 -0
- package/docs/targets/order-mg-payment-before.yml +51 -0
- package/docs/targets.md +42 -17
- package/package.json +27 -7
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@retailcrm/embed-ui-v1-endpoint)
|
|
4
4
|
|
|
5
|
-
`@retailcrm/embed-ui-v1-endpoint`
|
|
6
|
-
виджетов и страниц в RetailCRM:
|
|
7
|
-
|
|
5
|
+
`@retailcrm/embed-ui-v1-endpoint` предоставляет endpoint API для встраиваемых
|
|
6
|
+
виджетов и страниц в RetailCRM: обработку вызовов host-части, монтирование Vue-приложения
|
|
7
|
+
в endpoint root и освобождение ресурсов.
|
|
8
8
|
|
|
9
9
|
Пакет покрывает два сценария:
|
|
10
10
|
|
|
@@ -28,19 +28,78 @@ const runner = defineRunner({
|
|
|
28
28
|
runEndpoint(runner)
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Для продакшен-использования обычно достаточно `runEndpoint(...)` в
|
|
31
|
+
Для продакшен-использования обычно достаточно `runEndpoint(...)` в точке входа веб-воркера.
|
|
32
32
|
Если нужен более низкоуровневый контроль транспорта — используйте `createEndpoint(...)`.
|
|
33
33
|
|
|
34
|
-
##
|
|
34
|
+
## Документация
|
|
35
35
|
|
|
36
36
|
Подробная документация по методам находится в каталоге [`docs/`](./docs/README.md):
|
|
37
37
|
|
|
38
38
|
- [`defineRunner`](./docs/define-runner.md) — как объединить page и widget runners в один endpoint runner.
|
|
39
|
-
- [`definePageRunner`](./docs/define-page-runner.md) — как запускать
|
|
40
|
-
- [`defineWidgetRunner`](./docs/define-widget-runner.md) — как запускать
|
|
39
|
+
- [`definePageRunner`](./docs/define-page-runner.md) — как запускать встраиваемые страницы по `code`.
|
|
40
|
+
- [`defineWidgetRunner`](./docs/define-widget-runner.md) — как запускать встраиваемые виджеты по `target`.
|
|
41
41
|
- [`createEndpoint`](./docs/create-endpoint.md) — как вручную создать endpoint с transport и messenger.
|
|
42
|
-
- [`runEndpoint`](./docs/run-endpoint.md) — как поднять endpoint в
|
|
42
|
+
- [`runEndpoint`](./docs/run-endpoint.md) — как поднять endpoint в точке входа веб-воркера одной строкой.
|
|
43
43
|
- [`targets` и `defineTarget`](./docs/targets.md) — как типизировать цели виджетов и маршрутизировать их по target.
|
|
44
|
-
- [`menu-placements`](./docs/menu-placements.md) — как описывать меню и пункты навигации для
|
|
45
|
-
- [`page-routes`](./docs/page-routes.md) — как связывать page `code`, CRM
|
|
46
|
-
- [`layout`](./docs/layout.md) — как выбирать
|
|
44
|
+
- [`menu-placements`](./docs/menu-placements.md) — как описывать меню и пункты навигации для встраиваемых страниц.
|
|
45
|
+
- [`page-routes`](./docs/page-routes.md) — как связывать page `code`, CRM-маршрут и `definePageRunner`.
|
|
46
|
+
- [`layout`](./docs/layout.md) — как выбирать паттерны компоновки страниц, `modal sidebar` и `modal window`, и из каких `v1-components` их собирать.
|
|
47
|
+
|
|
48
|
+
## MCP для AI-ассистентов
|
|
49
|
+
|
|
50
|
+
Пакет поставляет MCP-сервер с AI-friendly описаниями встроенных widget targets.
|
|
51
|
+
Сервер читает сгенерированные YAML-профили из `docs/targets/*.yml` и отдаёт их как MCP resources.
|
|
52
|
+
|
|
53
|
+
Запуск через опубликованный пакет:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx -p @retailcrm/embed-ui-v1-endpoint embed-ui-v1-endpoint-mcp
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Пример stdio-конфига для MCP-клиента:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"command": "npx",
|
|
64
|
+
"args": ["-y", "-p", "@retailcrm/embed-ui-v1-endpoint", "embed-ui-v1-endpoint-mcp"]
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Базовые resources:
|
|
69
|
+
|
|
70
|
+
- `embed-ui-v1-endpoint://targets` — JSON-индекс всех target profiles.
|
|
71
|
+
- `embed-ui-v1-endpoint://targets/<encoded-target>` — YAML-профиль конкретного target.
|
|
72
|
+
|
|
73
|
+
## AI и инициализация `AGENTS.md`
|
|
74
|
+
|
|
75
|
+
Чтобы агент понимал, когда использовать MCP-сервер пакета, можно добавить в
|
|
76
|
+
целевой проект секцию с инструкциями:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx @retailcrm/embed-ui-v1-endpoint init-agents
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Если `AGENTS.md` ещё нет, команда создаст файл. Если файл уже есть, команда
|
|
83
|
+
допишет в конец английский блок для `@retailcrm/embed-ui-v1-endpoint`, если
|
|
84
|
+
такого блока там ещё нет. С `--force` можно обновить уже существующий блок
|
|
85
|
+
пакета.
|
|
86
|
+
|
|
87
|
+
## Инициализация MCP-конфига
|
|
88
|
+
|
|
89
|
+
Пакет также может сам добавить project-level MCP-настройки в целевой проект:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx @retailcrm/embed-ui-v1-endpoint init-config
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Команда создаёт или дополняет корневой `.mcp.json`, добавляет заметку в
|
|
96
|
+
`README.md` и не дублирует уже существующую настройку. Клиентские project-level
|
|
97
|
+
конфиги создаются только явно:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx @retailcrm/embed-ui-v1-endpoint init-config --mcp-client-configs cursor,junie,vscode
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
С `--force` можно обновить уже существующие управляемые записи. Команда
|
|
104
|
+
обновляет только запись `retailcrm-embed-ui-v1-endpoint`, а остальные серверы и
|
|
105
|
+
пользовательские настройки клиентских конфигов оставляет без изменений.
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import process from 'node:process'
|
|
6
|
+
|
|
7
|
+
const PACKAGE_NAME = '@retailcrm/embed-ui-v1-endpoint'
|
|
8
|
+
const DEFAULT_NEWLINE = '\n'
|
|
9
|
+
const AGENTS_SECTION_HEADER = '## @retailcrm/embed-ui-v1-endpoint'
|
|
10
|
+
const README_MCP_SECTION_HEADER = '## MCP For AI Assistants'
|
|
11
|
+
const README_MCP_MARKER = 'embed-ui-v1-endpoint://targets'
|
|
12
|
+
const MCP_SERVER_NAME = 'retailcrm-embed-ui-v1-endpoint'
|
|
13
|
+
const MCP_SERVER_CONFIG = {
|
|
14
|
+
command: 'npx',
|
|
15
|
+
args: ['-y', '-p', PACKAGE_NAME, 'embed-ui-v1-endpoint-mcp'],
|
|
16
|
+
}
|
|
17
|
+
const MCP_CLIENT_CONFIGS = {
|
|
18
|
+
cursor: {
|
|
19
|
+
filePath: '.cursor/mcp.json',
|
|
20
|
+
rootField: 'mcpServers',
|
|
21
|
+
},
|
|
22
|
+
junie: {
|
|
23
|
+
filePath: '.junie/mcp/mcp.json',
|
|
24
|
+
rootField: 'mcpServers',
|
|
25
|
+
},
|
|
26
|
+
vscode: {
|
|
27
|
+
filePath: '.vscode/mcp.json',
|
|
28
|
+
rootField: 'servers',
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const HELP_TEXT = `Usage:
|
|
33
|
+
npx ${PACKAGE_NAME} init-agents [target] [options]
|
|
34
|
+
npx ${PACKAGE_NAME} init-config [target] [options]
|
|
35
|
+
|
|
36
|
+
Options:
|
|
37
|
+
-f, --force Replace existing managed sections and MCP server entries
|
|
38
|
+
--mcp-client-configs Comma-separated MCP client configs to create (cursor,junie,vscode)
|
|
39
|
+
--dry-run Print planned config changes without writing files
|
|
40
|
+
-h, --help Show this help
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
npx ${PACKAGE_NAME} init-agents
|
|
44
|
+
npx ${PACKAGE_NAME} init-agents ./my-project
|
|
45
|
+
npx ${PACKAGE_NAME} init-agents --force
|
|
46
|
+
npx ${PACKAGE_NAME} init-config ./my-project
|
|
47
|
+
npx ${PACKAGE_NAME} init-config ./my-project --mcp-client-configs cursor,junie,vscode
|
|
48
|
+
`
|
|
49
|
+
|
|
50
|
+
const parseArgs = (argv) => {
|
|
51
|
+
const options = {
|
|
52
|
+
command: null,
|
|
53
|
+
target: process.cwd(),
|
|
54
|
+
force: false,
|
|
55
|
+
dryRun: false,
|
|
56
|
+
mcpClientConfigs: [],
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const positionals = []
|
|
60
|
+
|
|
61
|
+
for (let index = 0; index < argv.length; index++) {
|
|
62
|
+
const argument = argv[index]
|
|
63
|
+
|
|
64
|
+
if (argument === '-h' || argument === '--help') {
|
|
65
|
+
console.log(HELP_TEXT)
|
|
66
|
+
process.exit(0)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (argument === '-f' || argument === '--force') {
|
|
70
|
+
options.force = true
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (argument === '--dry-run') {
|
|
75
|
+
options.dryRun = true
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (argument === '--mcp-client-configs') {
|
|
80
|
+
const value = argv[index + 1]
|
|
81
|
+
if (!value || value.startsWith('-')) {
|
|
82
|
+
throw new Error('--mcp-client-configs requires a comma-separated value')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
options.mcpClientConfigs = value
|
|
86
|
+
.split(',')
|
|
87
|
+
.map((entry) => entry.trim())
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
index++
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (argument.startsWith('-')) {
|
|
94
|
+
throw new Error(`Unknown option: ${argument}`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
positionals.push(argument)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!positionals.length) {
|
|
101
|
+
throw new Error('Command is required')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
options.command = positionals[0]
|
|
105
|
+
|
|
106
|
+
if (positionals.length >= 2) {
|
|
107
|
+
options.target = path.resolve(process.cwd(), positionals[1])
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (positionals.length > 2) {
|
|
111
|
+
throw new Error('Too many positional arguments')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return options
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const createAgentsSection = () => {
|
|
118
|
+
return `${AGENTS_SECTION_HEADER}
|
|
119
|
+
|
|
120
|
+
When working with \`${PACKAGE_NAME}\` in this project:
|
|
121
|
+
|
|
122
|
+
1. Read \`./node_modules/${PACKAGE_NAME}/README.md\`.
|
|
123
|
+
2. Then read the relevant guide from \`./node_modules/${PACKAGE_NAME}/docs/README.md\`.
|
|
124
|
+
3. Use documented public entrypoints instead of package internals:
|
|
125
|
+
- \`${PACKAGE_NAME}/remote\`
|
|
126
|
+
- \`${PACKAGE_NAME}/common/targets\`
|
|
127
|
+
4. Do not import from \`${PACKAGE_NAME}/dist/*\`, source files, or repository-only paths.
|
|
128
|
+
5. When the task involves widget targets, target placement, target contexts, target metadata, or choosing a target, use the package MCP server if it is available.
|
|
129
|
+
6. First read \`embed-ui-v1-endpoint://targets\` to discover available target profiles.
|
|
130
|
+
7. Then read the relevant \`embed-ui-v1-endpoint://targets/<encoded-target>\` resource before answering or changing code related to that target.
|
|
131
|
+
8. If MCP resources are not available, use the generated YAML profiles from \`./node_modules/${PACKAGE_NAME}/docs/targets/*.yml\` as the fallback source.
|
|
132
|
+
9. Prefer target profiles over guessing target placement, contexts, or semantic intent from names alone.
|
|
133
|
+
|
|
134
|
+
Suggested MCP stdio server configuration:
|
|
135
|
+
|
|
136
|
+
\`\`\`json
|
|
137
|
+
{
|
|
138
|
+
"command": "npx",
|
|
139
|
+
"args": ["-y", "-p", "${PACKAGE_NAME}", "embed-ui-v1-endpoint-mcp"]
|
|
140
|
+
}
|
|
141
|
+
\`\`\`
|
|
142
|
+
`
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const createMcpReadmeSection = (clientConfigs) => {
|
|
146
|
+
const clientConfigText = clientConfigs.length
|
|
147
|
+
? `Client MCP configs were also requested: ${clientConfigs.map((clientConfig) => `\`${clientConfig}\``).join(', ')}. Review the generated files and restart the AI client if it is already open.`
|
|
148
|
+
: 'Client MCP configs are not created by default. For supported project-level configs, rerun init with `--mcp-client-configs cursor,junie,vscode`.'
|
|
149
|
+
|
|
150
|
+
return `${README_MCP_SECTION_HEADER}
|
|
151
|
+
|
|
152
|
+
The project has an MCP server configuration for \`${PACKAGE_NAME}\`.
|
|
153
|
+
It exposes AI-friendly widget target descriptions as MCP resources.
|
|
154
|
+
|
|
155
|
+
Basic check:
|
|
156
|
+
|
|
157
|
+
\`\`\`bash
|
|
158
|
+
npx -p ${PACKAGE_NAME} embed-ui-v1-endpoint-mcp
|
|
159
|
+
\`\`\`
|
|
160
|
+
|
|
161
|
+
Primary resources:
|
|
162
|
+
|
|
163
|
+
- \`${README_MCP_MARKER}\` is the widget target index.
|
|
164
|
+
- \`embed-ui-v1-endpoint://targets/<encoded-target>\` is a YAML profile for one target.
|
|
165
|
+
|
|
166
|
+
${clientConfigText}
|
|
167
|
+
|
|
168
|
+
### User-Level MCP Clients
|
|
169
|
+
|
|
170
|
+
Some clients store MCP servers in a user-level config outside this repository. Init does not edit
|
|
171
|
+
those files. Add the same server manually and restart the client.
|
|
172
|
+
|
|
173
|
+
Claude Desktop config paths:
|
|
174
|
+
|
|
175
|
+
- macOS: \`~/Library/Application Support/Claude/claude_desktop_config.json\`
|
|
176
|
+
- Windows: \`%APPDATA%\\Claude\\claude_desktop_config.json\`
|
|
177
|
+
|
|
178
|
+
Config snippet:
|
|
179
|
+
|
|
180
|
+
\`\`\`json
|
|
181
|
+
{
|
|
182
|
+
"mcpServers": {
|
|
183
|
+
"${MCP_SERVER_NAME}": {
|
|
184
|
+
"command": "npx",
|
|
185
|
+
"args": ["-y", "-p", "${PACKAGE_NAME}", "embed-ui-v1-endpoint-mcp"]
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
\`\`\`
|
|
190
|
+
`
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const createAgentsTemplate = () => {
|
|
194
|
+
return `# AGENTS.md
|
|
195
|
+
|
|
196
|
+
${createAgentsSection()}` + DEFAULT_NEWLINE
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const hasPackageSection = (content) => content.includes(AGENTS_SECTION_HEADER)
|
|
200
|
+
|
|
201
|
+
const appendSection = (content, section) => {
|
|
202
|
+
const trimmed = content.replace(/\s+$/u, '')
|
|
203
|
+
|
|
204
|
+
if (!trimmed.length) {
|
|
205
|
+
return `${section}${DEFAULT_NEWLINE}`
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return `${trimmed}${DEFAULT_NEWLINE}${DEFAULT_NEWLINE}${section}${DEFAULT_NEWLINE}`
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const replaceSection = (content, section) => {
|
|
212
|
+
const escapedHeader = AGENTS_SECTION_HEADER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
213
|
+
const sectionPattern = new RegExp(`${escapedHeader}[\\s\\S]*?(?=\\n##\\s|$)`, 'u')
|
|
214
|
+
|
|
215
|
+
if (!sectionPattern.test(content)) {
|
|
216
|
+
return appendSection(content, section)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return content
|
|
220
|
+
.replace(sectionPattern, section.trimEnd())
|
|
221
|
+
.replace(/\s+$/u, '') + DEFAULT_NEWLINE
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const replaceReadmeMcpSection = (content, section) => {
|
|
225
|
+
const escapedHeader = README_MCP_SECTION_HEADER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
226
|
+
const sectionPattern = new RegExp(`${escapedHeader}[\\s\\S]*?(?=\\n##\\s|$)`, 'u')
|
|
227
|
+
|
|
228
|
+
if (!sectionPattern.test(content)) {
|
|
229
|
+
return appendSection(content, section)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return content
|
|
233
|
+
.replace(sectionPattern, section.trimEnd())
|
|
234
|
+
.replace(/\s+$/u, '') + DEFAULT_NEWLINE
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const readJsonObject = (filePath) => {
|
|
238
|
+
if (!fs.existsSync(filePath)) {
|
|
239
|
+
return {}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
243
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
244
|
+
throw new Error(`${filePath} must contain a JSON object`)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return parsed
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const ensureObjectField = (object, field, filePath) => {
|
|
251
|
+
const value = object[field]
|
|
252
|
+
|
|
253
|
+
if (!value) {
|
|
254
|
+
object[field] = {}
|
|
255
|
+
return object[field]
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
259
|
+
throw new Error(`${filePath} field "${field}" must be a JSON object`)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return value
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const writeJson = (filePath, value, dryRun) => {
|
|
266
|
+
if (dryRun) {
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
271
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}${DEFAULT_NEWLINE}`, 'utf8')
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const initAgents = (target, force) => {
|
|
275
|
+
if (!fs.existsSync(target)) {
|
|
276
|
+
throw new Error(`Target path does not exist: ${target}`)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const stat = fs.statSync(target)
|
|
280
|
+
|
|
281
|
+
if (!stat.isDirectory()) {
|
|
282
|
+
throw new Error(`Target path is not a directory: ${target}`)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const agentsPath = path.join(target, 'AGENTS.md')
|
|
286
|
+
const section = createAgentsSection()
|
|
287
|
+
|
|
288
|
+
if (!fs.existsSync(agentsPath)) {
|
|
289
|
+
fs.writeFileSync(agentsPath, createAgentsTemplate(), 'utf8')
|
|
290
|
+
|
|
291
|
+
console.log(`AGENTS.md was created at ${agentsPath}`)
|
|
292
|
+
console.log('Next step: review it and adjust project-specific rules if needed.')
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const currentContent = fs.readFileSync(agentsPath, 'utf8')
|
|
297
|
+
|
|
298
|
+
if (force) {
|
|
299
|
+
fs.writeFileSync(agentsPath, replaceSection(currentContent, section), 'utf8')
|
|
300
|
+
console.log(`AGENTS.md was updated at ${agentsPath}`)
|
|
301
|
+
console.log(`The ${PACKAGE_NAME} section was refreshed.`)
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (hasPackageSection(currentContent)) {
|
|
306
|
+
console.log(`AGENTS.md already contains a ${PACKAGE_NAME} section at ${agentsPath}`)
|
|
307
|
+
console.log('Nothing was changed. Re-run with --force to refresh that section.')
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
fs.writeFileSync(agentsPath, appendSection(currentContent, section), 'utf8')
|
|
312
|
+
|
|
313
|
+
console.log(`AGENTS.md was updated at ${agentsPath}`)
|
|
314
|
+
console.log(`The ${PACKAGE_NAME} instructions were appended to the end of the file.`)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const writeMcpServerConfig = (target, relativePath, rootField, options) => {
|
|
318
|
+
const filePath = path.join(target, relativePath)
|
|
319
|
+
const fileExists = fs.existsSync(filePath)
|
|
320
|
+
const config = readJsonObject(filePath)
|
|
321
|
+
const servers = ensureObjectField(config, rootField, filePath)
|
|
322
|
+
|
|
323
|
+
if (servers[MCP_SERVER_NAME] && !options.force) {
|
|
324
|
+
console.log(`${relativePath} already contains ${MCP_SERVER_NAME}`)
|
|
325
|
+
console.log('Nothing was changed. Re-run with --force to refresh that server entry.')
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
servers[MCP_SERVER_NAME] = MCP_SERVER_CONFIG
|
|
330
|
+
writeJson(filePath, config, options.dryRun)
|
|
331
|
+
|
|
332
|
+
const action = fileExists ? 'updated' : 'created'
|
|
333
|
+
console.log(`${relativePath} ${options.dryRun ? `would be ${action}` : `was ${action}`}`)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const resolveMcpClientConfigs = (tokens) => {
|
|
337
|
+
for (const token of tokens) {
|
|
338
|
+
if (!(token in MCP_CLIENT_CONFIGS)) {
|
|
339
|
+
throw new Error(`Unknown MCP client config: ${token}`)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return tokens
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const updateMcpReadmeNotes = (target, clientConfigs, options) => {
|
|
347
|
+
const readmePath = path.join(target, 'README.md')
|
|
348
|
+
const fileExists = fs.existsSync(readmePath)
|
|
349
|
+
const currentContent = fileExists
|
|
350
|
+
? fs.readFileSync(readmePath, 'utf8')
|
|
351
|
+
: '# README.md\n'
|
|
352
|
+
const section = createMcpReadmeSection(clientConfigs)
|
|
353
|
+
|
|
354
|
+
if (currentContent.includes(README_MCP_MARKER) && !options.force) {
|
|
355
|
+
console.log(`README.md already contains MCP setup notes at ${readmePath}`)
|
|
356
|
+
console.log('Nothing was changed. Re-run with --force to refresh that section.')
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const nextContent = replaceReadmeMcpSection(currentContent, section)
|
|
361
|
+
|
|
362
|
+
if (!options.dryRun) {
|
|
363
|
+
fs.writeFileSync(readmePath, nextContent, 'utf8')
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const action = fileExists ? 'updated' : 'created'
|
|
367
|
+
console.log(`README.md ${options.dryRun ? `would be ${action}` : `was ${action}`} with MCP setup notes`)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const initConfig = (target, options) => {
|
|
371
|
+
if (!fs.existsSync(target)) {
|
|
372
|
+
throw new Error(`Target path does not exist: ${target}`)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const stat = fs.statSync(target)
|
|
376
|
+
|
|
377
|
+
if (!stat.isDirectory()) {
|
|
378
|
+
throw new Error(`Target path is not a directory: ${target}`)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const clientConfigs = resolveMcpClientConfigs(options.mcpClientConfigs)
|
|
382
|
+
|
|
383
|
+
writeMcpServerConfig(target, '.mcp.json', 'mcpServers', options)
|
|
384
|
+
|
|
385
|
+
for (const clientConfig of clientConfigs) {
|
|
386
|
+
const config = MCP_CLIENT_CONFIGS[clientConfig]
|
|
387
|
+
writeMcpServerConfig(target, config.filePath, config.rootField, options)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
updateMcpReadmeNotes(target, clientConfigs, options)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const main = () => {
|
|
394
|
+
try {
|
|
395
|
+
const options = parseArgs(process.argv.slice(2))
|
|
396
|
+
|
|
397
|
+
if (options.command === 'init-agents') {
|
|
398
|
+
initAgents(options.target, options.force)
|
|
399
|
+
return
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (options.command === 'init-config') {
|
|
403
|
+
initConfig(options.target, options)
|
|
404
|
+
return
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
throw new Error(`Unknown command: ${options.command}`)
|
|
408
|
+
} catch (error) {
|
|
409
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
410
|
+
console.error('')
|
|
411
|
+
console.error(HELP_TEXT)
|
|
412
|
+
process.exit(1)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
main()
|