@actuate-media/cli 0.10.0 → 0.11.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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +290 -32
- package/CHANGELOG.md +8 -0
- package/dist/__tests__/mcp-init.test.d.ts +2 -0
- package/dist/__tests__/mcp-init.test.d.ts.map +1 -0
- package/dist/__tests__/mcp-init.test.js +100 -0
- package/dist/__tests__/mcp-init.test.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +126 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/mcp-init.test.ts +125 -0
- package/src/commands/mcp.ts +181 -0
- package/src/index.ts +2 -0
package/.turbo/turbo-build.log
CHANGED
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
|
|
2
|
-
> @actuate-media/cli@0.
|
|
2
|
+
> @actuate-media/cli@0.11.0 test /home/runner/work/actuatecms/actuatecms/packages/cli
|
|
3
3
|
> vitest run
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
[1m[30m[46m RUN [49m[39m[22m [36mv4.1.8 [39m[90m/home/runner/work/actuatecms/actuatecms/packages/cli[39m
|
|
7
7
|
|
|
8
|
-
[32m✓[39m dist/__tests__/db-sync.test.js [2m([22m[2m10 tests[22m[2m)[22m[33m
|
|
9
|
-
[32m✓[39m dist/__tests__/seed.test.js [2m([22m[2m10 tests[22m[2m)[22m[32m
|
|
10
|
-
[32m✓[39m src/__tests__/db-sync.test.ts [2m([22m[2m10 tests[22m[2m)[22m[
|
|
11
|
-
[32m✓[39m src/__tests__/seed.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m
|
|
12
|
-
[32m✓[39m dist/__tests__/deployment-diagnostics.test.js [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
13
|
-
[32m✓[39m src/__tests__/deployment-diagnostics.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
8
|
+
[32m✓[39m dist/__tests__/db-sync.test.js [2m([22m[2m10 tests[22m[2m)[22m[33m 462[2mms[22m[39m
|
|
9
|
+
[32m✓[39m dist/__tests__/seed.test.js [2m([22m[2m10 tests[22m[2m)[22m[32m 201[2mms[22m[39m
|
|
10
|
+
[32m✓[39m src/__tests__/db-sync.test.ts [2m([22m[2m10 tests[22m[2m)[22m[33m 468[2mms[22m[39m
|
|
11
|
+
[32m✓[39m src/__tests__/seed.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 194[2mms[22m[39m
|
|
12
|
+
[32m✓[39m dist/__tests__/deployment-diagnostics.test.js [2m([22m[2m8 tests[22m[2m)[22m[32m 52[2mms[22m[39m
|
|
13
|
+
[32m✓[39m src/__tests__/deployment-diagnostics.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 59[2mms[22m[39m
|
|
14
14
|
- Reading canonical Actuate schema...
|
|
15
|
+
✔ Actuate CMS models added to schema.
|
|
16
|
+
- Running prisma generate...
|
|
15
17
|
[90mstdout[2m | dist/__tests__/db-init.test.js[2m > [22m[2mdb:init (WS-D D5 — canonical schema, no stale fragment)[2m > [22m[2mcommand[2m > [22m[2minjects canonical models with @@map and no duplicate datasource/generator
|
|
16
18
|
[22m[39m✔ Prisma client generated.
|
|
17
19
|
ℹ Run `npx prisma migrate dev --name actuate-cms` to create the migration.
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
- Reading canonical Actuate schema...
|
|
22
|
+
✖ Could not locate @actuate-media/cms-core.
|
|
21
23
|
[90mstdout[2m | dist/__tests__/db-init.test.js[2m > [22m[2mdb:init (WS-D D5 — canonical schema, no stale fragment)[2m > [22m[2mcommand[2m > [22m[2mfails clearly when cms-core cannot be located
|
|
22
24
|
[22m[39mℹ Install it first: `npm install @actuate-media/cms-core`.
|
|
23
|
-
- Reading canonical Actuate schema...
|
|
24
25
|
|
|
25
|
-
✖ Could not locate @actuate-media/cms-core.
|
|
26
26
|
- Reading canonical Actuate schema...
|
|
27
|
+
✔ Actuate CMS models added to schema.
|
|
28
|
+
- Running prisma generate...
|
|
27
29
|
[90mstdout[2m | dist/__tests__/db-init.test.js[2m > [22m[2mdb:init (WS-D D5 — canonical schema, no stale fragment)[2m > [22m[2mcommand[2m > [22m[2mis idempotent without --force
|
|
28
30
|
[22m[39m✔ Prisma client generated.
|
|
29
31
|
ℹ Run `npx prisma migrate dev --name actuate-cms` to create the migration.
|
|
30
32
|
|
|
31
|
-
✔ Actuate CMS models added to schema.
|
|
32
|
-
- Running prisma generate...
|
|
33
33
|
- Reading canonical Actuate schema...
|
|
34
34
|
ℹ Actuate CMS models already present in schema. Use --force to overwrite.
|
|
35
|
-
[32m✓[39m dist/__tests__/db-init.test.js [2m([22m[2m4 tests[22m[2m)[22m[
|
|
35
|
+
[32m✓[39m dist/__tests__/db-init.test.js [2m([22m[2m4 tests[22m[2m)[22m[33m 365[2mms[22m[39m
|
|
36
36
|
- Reading canonical Actuate schema...
|
|
37
37
|
✔ Actuate CMS models added to schema.
|
|
38
38
|
- Running prisma generate...
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
ℹ Run `npx prisma migrate dev --name actuate-cms` to create the migration.
|
|
42
42
|
|
|
43
43
|
[90mstdout[2m | src/__tests__/db-init.test.ts[2m > [22m[2mdb:init (WS-D D5 — canonical schema, no stale fragment)[2m > [22m[2mcommand[2m > [22m[2mfails clearly when cms-core cannot be located
|
|
44
|
-
- Reading canonical Actuate schema...
|
|
45
44
|
[22m[39mℹ Install it first: `npm install @actuate-media/cms-core`.
|
|
45
|
+
- Reading canonical Actuate schema...
|
|
46
46
|
|
|
47
47
|
✖ Could not locate @actuate-media/cms-core.
|
|
48
48
|
- Reading canonical Actuate schema...
|
|
@@ -54,32 +54,290 @@
|
|
|
54
54
|
|
|
55
55
|
- Reading canonical Actuate schema...
|
|
56
56
|
ℹ Actuate CMS models already present in schema. Use --force to overwrite.
|
|
57
|
-
[32m✓[39m src/__tests__/db-init.test.ts [2m([22m[2m4 tests[22m[2m)[22m[
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
[32m✓[39m src/__tests__/db-init.test.ts [2m([22m[2m4 tests[22m[2m)[22m[33m 391[2mms[22m[39m
|
|
58
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mcreates both editor configs with placeholder key and git-ignores them
|
|
59
|
+
[22m[39m✔ Created .cursor/mcp.json
|
|
60
|
+
|
|
61
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mcreates both editor configs with placeholder key and git-ignores them
|
|
62
|
+
[22m[39m✔ Created .vscode/mcp.json
|
|
63
|
+
|
|
64
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mcreates both editor configs with placeholder key and git-ignores them
|
|
65
|
+
[22m[39m✔ Added to .gitignore: .cursor/mcp.json, .vscode/mcp.json
|
|
66
|
+
|
|
67
|
+
Next steps
|
|
68
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
69
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
70
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
71
|
+
Tip: pass --url https://your-site.com to target a deployed CMS instead.
|
|
72
|
+
|
|
73
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mapplies --url and --key to both configs
|
|
74
|
+
[22m[39m✔ Created .cursor/mcp.json
|
|
75
|
+
|
|
76
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mapplies --url and --key to both configs
|
|
77
|
+
[22m[39m✔ Created .vscode/mcp.json
|
|
78
|
+
|
|
79
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mapplies --url and --key to both configs
|
|
80
|
+
[22m[39m✔ Added to .gitignore: .cursor/mcp.json, .vscode/mcp.json
|
|
81
|
+
|
|
82
|
+
Next steps
|
|
83
|
+
1. Restart Cursor / VS Code so the MCP server is picked up
|
|
84
|
+
|
|
85
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mmerges into an existing config without clobbering other servers
|
|
86
|
+
[22m[39m✔ Updated .cursor/mcp.json
|
|
87
|
+
|
|
88
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mmerges into an existing config without clobbering other servers
|
|
89
|
+
[22m[39m✔ Created .vscode/mcp.json
|
|
90
|
+
|
|
91
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mmerges into an existing config without clobbering other servers
|
|
92
|
+
[22m[39m✔ Added to .gitignore: .cursor/mcp.json, .vscode/mcp.json
|
|
93
|
+
|
|
94
|
+
Next steps
|
|
95
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
96
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
97
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
98
|
+
Tip: pass --url https://your-site.com to target a deployed CMS instead.
|
|
99
|
+
|
|
100
|
+
[90mstderr[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mleaves an existing actuate entry alone unless --force is passed
|
|
101
|
+
[22m[39m⚠.cursor/mcp.json already has an "actuate" server — use --force to replace it.
|
|
102
|
+
|
|
103
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mleaves an existing actuate entry alone unless --force is passed
|
|
104
|
+
[22m[39m✔ Created .vscode/mcp.json
|
|
105
|
+
|
|
106
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mleaves an existing actuate entry alone unless --force is passed
|
|
107
|
+
[22m[39m✔ Added to .gitignore: .cursor/mcp.json, .vscode/mcp.json
|
|
108
|
+
|
|
109
|
+
Next steps
|
|
110
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
111
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
112
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
113
|
+
Tip: pass --url https://your-site.com to target a deployed CMS instead.
|
|
114
|
+
|
|
115
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mleaves an existing actuate entry alone unless --force is passed
|
|
116
|
+
[22m[39m✔ Updated .cursor/mcp.json
|
|
117
|
+
|
|
118
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mleaves an existing actuate entry alone unless --force is passed
|
|
119
|
+
[22m[39m✔ Updated .vscode/mcp.json
|
|
120
|
+
|
|
121
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mleaves an existing actuate entry alone unless --force is passed
|
|
122
|
+
[22m[39m
|
|
123
|
+
Next steps
|
|
124
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
125
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
126
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
127
|
+
|
|
128
|
+
[90mstderr[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mfails on invalid JSON without --force, overwrites with --force
|
|
129
|
+
[22m[39m✖ .cursor/mcp.json contains invalid JSON — fix it or re-run with --force.
|
|
130
|
+
|
|
131
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mfails on invalid JSON without --force, overwrites with --force
|
|
132
|
+
[22m[39m✔ Created .vscode/mcp.json
|
|
133
|
+
|
|
134
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mfails on invalid JSON without --force, overwrites with --force
|
|
135
|
+
[22m[39m✔ Added to .gitignore: .cursor/mcp.json, .vscode/mcp.json
|
|
136
|
+
|
|
137
|
+
Next steps
|
|
138
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
139
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
140
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
141
|
+
Tip: pass --url https://your-site.com to target a deployed CMS instead.
|
|
142
|
+
|
|
143
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mfails on invalid JSON without --force, overwrites with --force
|
|
144
|
+
[22m[39m✔ Updated .cursor/mcp.json
|
|
145
|
+
|
|
146
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mfails on invalid JSON without --force, overwrites with --force
|
|
147
|
+
[22m[39m✔ Updated .vscode/mcp.json
|
|
148
|
+
|
|
149
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mfails on invalid JSON without --force, overwrites with --force
|
|
150
|
+
[22m[39m
|
|
151
|
+
Next steps
|
|
152
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
153
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
154
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
155
|
+
Tip: pass --url https://your-site.com to target a deployed CMS instead.
|
|
156
|
+
|
|
157
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mdoes not duplicate gitignore entries on re-runs
|
|
158
|
+
[22m[39m✔ Created .cursor/mcp.json
|
|
159
|
+
|
|
160
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mdoes not duplicate gitignore entries on re-runs
|
|
161
|
+
[22m[39m✔ Created .vscode/mcp.json
|
|
162
|
+
|
|
163
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mdoes not duplicate gitignore entries on re-runs
|
|
164
|
+
[22m[39m✔ Added to .gitignore: .cursor/mcp.json, .vscode/mcp.json
|
|
165
|
+
|
|
166
|
+
Next steps
|
|
167
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
168
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
169
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
170
|
+
Tip: pass --url https://your-site.com to target a deployed CMS instead.
|
|
171
|
+
|
|
172
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mdoes not duplicate gitignore entries on re-runs
|
|
173
|
+
[22m[39m✔ Updated .cursor/mcp.json
|
|
174
|
+
|
|
175
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mdoes not duplicate gitignore entries on re-runs
|
|
176
|
+
[22m[39m✔ Updated .vscode/mcp.json
|
|
177
|
+
|
|
178
|
+
[90mstdout[2m | dist/__tests__/mcp-init.test.js[2m > [22m[2mmcp:init[2m > [22m[2mdoes not duplicate gitignore entries on re-runs
|
|
179
|
+
[22m[39m
|
|
180
|
+
Next steps
|
|
181
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
182
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
183
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
184
|
+
Tip: pass --url https://your-site.com to target a deployed CMS instead.
|
|
185
|
+
|
|
186
|
+
[32m✓[39m dist/__tests__/mcp-init.test.js [2m([22m[2m6 tests[22m[2m)[22m[33m 514[2mms[22m[39m
|
|
187
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mcreates both editor configs with placeholder key and git-ignores them
|
|
188
|
+
[22m[39m✔ Created .cursor/mcp.json
|
|
189
|
+
|
|
190
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mcreates both editor configs with placeholder key and git-ignores them
|
|
191
|
+
[22m[39m✔ Created .vscode/mcp.json
|
|
192
|
+
|
|
193
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mcreates both editor configs with placeholder key and git-ignores them
|
|
194
|
+
[22m[39m✔ Added to .gitignore: .cursor/mcp.json, .vscode/mcp.json
|
|
195
|
+
|
|
196
|
+
Next steps
|
|
197
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
198
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
199
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
200
|
+
Tip: pass --url https://your-site.com to target a deployed CMS instead.
|
|
201
|
+
|
|
202
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mapplies --url and --key to both configs
|
|
203
|
+
[22m[39m✔ Created .cursor/mcp.json
|
|
204
|
+
|
|
205
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mapplies --url and --key to both configs
|
|
206
|
+
[22m[39m✔ Created .vscode/mcp.json
|
|
207
|
+
|
|
208
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mapplies --url and --key to both configs
|
|
209
|
+
[22m[39m✔ Added to .gitignore: .cursor/mcp.json, .vscode/mcp.json
|
|
210
|
+
|
|
211
|
+
Next steps
|
|
212
|
+
1. Restart Cursor / VS Code so the MCP server is picked up
|
|
213
|
+
|
|
214
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mmerges into an existing config without clobbering other servers
|
|
215
|
+
[22m[39m✔ Updated .cursor/mcp.json
|
|
216
|
+
|
|
217
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mmerges into an existing config without clobbering other servers
|
|
218
|
+
[22m[39m✔ Created .vscode/mcp.json
|
|
219
|
+
|
|
220
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mmerges into an existing config without clobbering other servers
|
|
221
|
+
[22m[39m✔ Added to .gitignore: .cursor/mcp.json, .vscode/mcp.json
|
|
222
|
+
|
|
223
|
+
Next steps
|
|
224
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
225
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
226
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
227
|
+
Tip: pass --url https://your-site.com to target a deployed CMS instead.
|
|
228
|
+
|
|
229
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mleaves an existing actuate entry alone unless --force is passed
|
|
230
|
+
[22m[39m✔ Created .vscode/mcp.json
|
|
231
|
+
|
|
232
|
+
[90mstderr[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mleaves an existing actuate entry alone unless --force is passed
|
|
233
|
+
[22m[39m⚠.cursor/mcp.json already has an "actuate" server — use --force to replace it.
|
|
234
|
+
|
|
235
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mleaves an existing actuate entry alone unless --force is passed
|
|
236
|
+
[22m[39m✔ Added to .gitignore: .cursor/mcp.json, .vscode/mcp.json
|
|
237
|
+
|
|
238
|
+
Next steps
|
|
239
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
240
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
241
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
242
|
+
Tip: pass --url https://your-site.com to target a deployed CMS instead.
|
|
243
|
+
|
|
244
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mleaves an existing actuate entry alone unless --force is passed
|
|
245
|
+
[22m[39m✔ Updated .cursor/mcp.json
|
|
246
|
+
|
|
247
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mleaves an existing actuate entry alone unless --force is passed
|
|
248
|
+
[22m[39m✔ Updated .vscode/mcp.json
|
|
249
|
+
|
|
250
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mleaves an existing actuate entry alone unless --force is passed
|
|
251
|
+
[22m[39m
|
|
252
|
+
Next steps
|
|
253
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
254
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
255
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
256
|
+
|
|
257
|
+
[90mstderr[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mfails on invalid JSON without --force, overwrites with --force
|
|
258
|
+
[22m[39m✖ .cursor/mcp.json contains invalid JSON — fix it or re-run with --force.
|
|
259
|
+
|
|
260
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mfails on invalid JSON without --force, overwrites with --force
|
|
261
|
+
[22m[39m✔ Created .vscode/mcp.json
|
|
262
|
+
|
|
263
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mfails on invalid JSON without --force, overwrites with --force
|
|
264
|
+
[22m[39m✔ Added to .gitignore: .cursor/mcp.json, .vscode/mcp.json
|
|
265
|
+
|
|
266
|
+
Next steps
|
|
267
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
268
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
269
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
270
|
+
Tip: pass --url https://your-site.com to target a deployed CMS instead.
|
|
271
|
+
|
|
272
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mfails on invalid JSON without --force, overwrites with --force
|
|
273
|
+
[22m[39m✔ Updated .cursor/mcp.json
|
|
274
|
+
|
|
275
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mfails on invalid JSON without --force, overwrites with --force
|
|
276
|
+
[22m[39m✔ Updated .vscode/mcp.json
|
|
277
|
+
|
|
278
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mfails on invalid JSON without --force, overwrites with --force
|
|
279
|
+
[22m[39m
|
|
280
|
+
Next steps
|
|
281
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
282
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
283
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
284
|
+
Tip: pass --url https://your-site.com to target a deployed CMS instead.
|
|
285
|
+
|
|
286
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mdoes not duplicate gitignore entries on re-runs
|
|
287
|
+
[22m[39m✔ Created .cursor/mcp.json
|
|
288
|
+
|
|
289
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mdoes not duplicate gitignore entries on re-runs
|
|
290
|
+
[22m[39m✔ Created .vscode/mcp.json
|
|
291
|
+
|
|
292
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mdoes not duplicate gitignore entries on re-runs
|
|
293
|
+
[22m[39m✔ Added to .gitignore: .cursor/mcp.json, .vscode/mcp.json
|
|
294
|
+
|
|
295
|
+
Next steps
|
|
296
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
297
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
298
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
299
|
+
Tip: pass --url https://your-site.com to target a deployed CMS instead.
|
|
300
|
+
|
|
301
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mdoes not duplicate gitignore entries on re-runs
|
|
302
|
+
[22m[39m✔ Updated .cursor/mcp.json
|
|
303
|
+
|
|
304
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mdoes not duplicate gitignore entries on re-runs
|
|
305
|
+
[22m[39m✔ Updated .vscode/mcp.json
|
|
306
|
+
|
|
307
|
+
[90mstdout[2m | src/__tests__/mcp-init.test.ts[2m > [22m[2mmcp:init[2m > [22m[2mdoes not duplicate gitignore entries on re-runs
|
|
308
|
+
[22m[39m
|
|
309
|
+
Next steps
|
|
310
|
+
1. Mint an API key in Admin → Settings → API Keys (act_sk_...)
|
|
311
|
+
2. Replace act_sk_REPLACE_WITH_YOUR_KEY in both config files with the real key
|
|
312
|
+
3. Restart Cursor / VS Code so the MCP server is picked up
|
|
313
|
+
Tip: pass --url https://your-site.com to target a deployed CMS instead.
|
|
314
|
+
|
|
315
|
+
[32m✓[39m src/__tests__/mcp-init.test.ts [2m([22m[2m6 tests[22m[2m)[22m[33m 366[2mms[22m[39m
|
|
316
|
+
[32m✓[39m dist/__tests__/form-seed.test.js [2m([22m[2m10 tests[22m[2m)[22m[32m 67[2mms[22m[39m
|
|
317
|
+
[32m✓[39m src/__tests__/form-seed.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 61[2mms[22m[39m
|
|
318
|
+
[32m✓[39m dist/__tests__/vercel-env-matrix.test.js [2m([22m[2m5 tests[22m[2m)[22m[32m 49[2mms[22m[39m
|
|
319
|
+
[32m✓[39m src/__tests__/vercel-env-matrix.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 69[2mms[22m[39m
|
|
62
320
|
- Reading canonical Actuate schema...
|
|
63
321
|
✔ Actuate CMS models added to schema.
|
|
64
|
-
- Running prisma generate...
|
|
65
322
|
[90mstdout[2m | dist/__tests__/schema-fragment.test.js[2m > [22m[2mdb:init schema fragment[2m > [22m[2minjects all deploy-critical Actuate models
|
|
66
323
|
[22m[39m✔ Prisma client generated.
|
|
67
324
|
ℹ Run `npx prisma migrate dev --name actuate-cms` to create the migration.
|
|
68
325
|
|
|
69
|
-
|
|
326
|
+
- Running prisma generate...
|
|
327
|
+
[32m✓[39m dist/__tests__/schema-fragment.test.js [2m([22m[2m1 test[22m[2m)[22m[32m 193[2mms[22m[39m
|
|
70
328
|
- Reading canonical Actuate schema...
|
|
71
|
-
[90mstdout[2m | src/__tests__/schema-fragment.test.ts[2m > [22m[2mdb:init schema fragment[2m > [22m[2minjects all deploy-critical Actuate models
|
|
72
329
|
✔ Actuate CMS models added to schema.
|
|
330
|
+
- Running prisma generate...
|
|
331
|
+
[90mstdout[2m | src/__tests__/schema-fragment.test.ts[2m > [22m[2mdb:init schema fragment[2m > [22m[2minjects all deploy-critical Actuate models
|
|
73
332
|
[22m[39m✔ Prisma client generated.
|
|
74
333
|
ℹ Run `npx prisma migrate dev --name actuate-cms` to create the migration.
|
|
75
334
|
|
|
76
|
-
-
|
|
77
|
-
[32m✓[39m
|
|
78
|
-
[32m✓[39m
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
[2m
|
|
82
|
-
[2m
|
|
83
|
-
[2m
|
|
84
|
-
[2m Duration [22m 23.79s[2m (transform 2.27s, setup 0ms, import 6.86s, tests 2.20s, environment 2ms)[22m
|
|
335
|
+
[32m✓[39m src/__tests__/schema-fragment.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 172[2mms[22m[39m
|
|
336
|
+
[32m✓[39m dist/__tests__/init.test.js [2m([22m[2m2 tests[22m[2m)[22m[32m 45[2mms[22m[39m
|
|
337
|
+
[32m✓[39m src/__tests__/init.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 42[2mms[22m[39m
|
|
338
|
+
|
|
339
|
+
[2m Test Files [22m [1m[32m18 passed[39m[22m[90m (18)[39m
|
|
340
|
+
[2m Tests [22m [1m[32m112 passed[39m[22m[90m (112)[39m
|
|
341
|
+
[2m Start at [22m 03:11:38
|
|
342
|
+
[2m Duration [22m 30.42s[2m (transform 2.66s, setup 0ms, import 7.77s, tests 3.77s, environment 12ms)[22m
|
|
85
343
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @actuate-media/cli
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- eb998e5: New `actuate mcp:init` command — retrofit the Actuate MCP server onto existing projects.
|
|
8
|
+
|
|
9
|
+
Writes `.cursor/mcp.json` (Cursor / Claude Desktop shape) and `.vscode/mcp.json` (VS Code shape) wiring `@actuate-media/mcp-server`, with `--url` / `--key` options. Merges into existing configs without clobbering other MCP servers; an existing `actuate` entry is only replaced with `--force`. Both paths are appended to `.gitignore` so a real `act_sk_...` key is never committed. New scaffolds already get these files from `create-actuate-cms` — this closes the gap for projects created before that.
|
|
10
|
+
|
|
3
11
|
## 0.10.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-init.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/mcp-init.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
6
|
+
import { registerMcpCommand } from '../commands/mcp.js';
|
|
7
|
+
/**
|
|
8
|
+
* `actuate mcp:init` retrofits MCP editor configs onto existing projects.
|
|
9
|
+
* Pins the merge contract: other configured servers are never clobbered, an
|
|
10
|
+
* existing `actuate` entry needs `--force`, and both config paths end up
|
|
11
|
+
* git-ignored (they hold a real API key once configured).
|
|
12
|
+
*/
|
|
13
|
+
let projectDir;
|
|
14
|
+
let originalCwd;
|
|
15
|
+
async function run(args = []) {
|
|
16
|
+
const program = new Command();
|
|
17
|
+
program.exitOverride();
|
|
18
|
+
registerMcpCommand(program);
|
|
19
|
+
await program.parseAsync(['mcp:init', ...args], { from: 'user' });
|
|
20
|
+
}
|
|
21
|
+
async function readJson(relPath) {
|
|
22
|
+
return JSON.parse(await readFile(path.join(projectDir, relPath), 'utf8'));
|
|
23
|
+
}
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
originalCwd = process.cwd();
|
|
26
|
+
projectDir = await mkdtemp(path.join(tmpdir(), 'actuate-mcp-init-'));
|
|
27
|
+
process.chdir(projectDir);
|
|
28
|
+
});
|
|
29
|
+
afterEach(async () => {
|
|
30
|
+
process.chdir(originalCwd);
|
|
31
|
+
process.exitCode = 0;
|
|
32
|
+
await rm(projectDir, { recursive: true, force: true });
|
|
33
|
+
});
|
|
34
|
+
describe('mcp:init', () => {
|
|
35
|
+
it('creates both editor configs with placeholder key and git-ignores them', async () => {
|
|
36
|
+
await writeFile(path.join(projectDir, '.gitignore'), 'node_modules\n', 'utf8');
|
|
37
|
+
await run();
|
|
38
|
+
const cursor = await readJson('.cursor/mcp.json');
|
|
39
|
+
expect(cursor.mcpServers.actuate.command).toBe('npx');
|
|
40
|
+
expect(cursor.mcpServers.actuate.args).toEqual(['-y', '@actuate-media/mcp-server']);
|
|
41
|
+
expect(cursor.mcpServers.actuate.env.ACTUATE_BASE_URL).toBe('http://localhost:3000');
|
|
42
|
+
expect(cursor.mcpServers.actuate.env.ACTUATE_API_KEY).toBe('act_sk_REPLACE_WITH_YOUR_KEY');
|
|
43
|
+
const vscode = await readJson('.vscode/mcp.json');
|
|
44
|
+
expect(vscode.servers.actuate.type).toBe('stdio');
|
|
45
|
+
const gitignore = await readFile(path.join(projectDir, '.gitignore'), 'utf8');
|
|
46
|
+
expect(gitignore).toContain('node_modules');
|
|
47
|
+
expect(gitignore).toContain('.cursor/mcp.json');
|
|
48
|
+
expect(gitignore).toContain('.vscode/mcp.json');
|
|
49
|
+
});
|
|
50
|
+
it('applies --url and --key to both configs', async () => {
|
|
51
|
+
await run(['--url', 'https://example.com', '--key', 'act_sk_real123']);
|
|
52
|
+
const cursor = await readJson('.cursor/mcp.json');
|
|
53
|
+
expect(cursor.mcpServers.actuate.env).toEqual({
|
|
54
|
+
ACTUATE_BASE_URL: 'https://example.com',
|
|
55
|
+
ACTUATE_API_KEY: 'act_sk_real123',
|
|
56
|
+
});
|
|
57
|
+
const vscode = await readJson('.vscode/mcp.json');
|
|
58
|
+
expect(vscode.servers.actuate.env.ACTUATE_BASE_URL).toBe('https://example.com');
|
|
59
|
+
});
|
|
60
|
+
it('merges into an existing config without clobbering other servers', async () => {
|
|
61
|
+
await mkdir(path.join(projectDir, '.cursor'), { recursive: true });
|
|
62
|
+
await writeFile(path.join(projectDir, '.cursor', 'mcp.json'), JSON.stringify({ mcpServers: { sentry: { command: 'sentry-mcp' } } }), 'utf8');
|
|
63
|
+
await run();
|
|
64
|
+
const cursor = await readJson('.cursor/mcp.json');
|
|
65
|
+
expect(cursor.mcpServers.sentry).toEqual({ command: 'sentry-mcp' });
|
|
66
|
+
expect(cursor.mcpServers.actuate).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
it('leaves an existing actuate entry alone unless --force is passed', async () => {
|
|
69
|
+
await mkdir(path.join(projectDir, '.cursor'), { recursive: true });
|
|
70
|
+
const existing = {
|
|
71
|
+
mcpServers: { actuate: { command: 'custom', env: { ACTUATE_BASE_URL: 'https://keep.me' } } },
|
|
72
|
+
};
|
|
73
|
+
await writeFile(path.join(projectDir, '.cursor', 'mcp.json'), JSON.stringify(existing), 'utf8');
|
|
74
|
+
await run();
|
|
75
|
+
let cursor = await readJson('.cursor/mcp.json');
|
|
76
|
+
expect(cursor.mcpServers.actuate.command).toBe('custom');
|
|
77
|
+
await run(['--force', '--url', 'https://new.example.com']);
|
|
78
|
+
cursor = await readJson('.cursor/mcp.json');
|
|
79
|
+
expect(cursor.mcpServers.actuate.command).toBe('npx');
|
|
80
|
+
expect(cursor.mcpServers.actuate.env.ACTUATE_BASE_URL).toBe('https://new.example.com');
|
|
81
|
+
});
|
|
82
|
+
it('fails on invalid JSON without --force, overwrites with --force', async () => {
|
|
83
|
+
await mkdir(path.join(projectDir, '.cursor'), { recursive: true });
|
|
84
|
+
await writeFile(path.join(projectDir, '.cursor', 'mcp.json'), '{ not json', 'utf8');
|
|
85
|
+
await run();
|
|
86
|
+
expect(process.exitCode).toBe(1);
|
|
87
|
+
process.exitCode = 0;
|
|
88
|
+
await run(['--force']);
|
|
89
|
+
const cursor = await readJson('.cursor/mcp.json');
|
|
90
|
+
expect(cursor.mcpServers.actuate).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
it('does not duplicate gitignore entries on re-runs', async () => {
|
|
93
|
+
await run();
|
|
94
|
+
await run(['--force']);
|
|
95
|
+
const gitignore = await readFile(path.join(projectDir, '.gitignore'), 'utf8');
|
|
96
|
+
const count = gitignore.split('\n').filter((l) => l.trim() === '.cursor/mcp.json').length;
|
|
97
|
+
expect(count).toBe(1);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
//# sourceMappingURL=mcp-init.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-init.test.js","sourceRoot":"","sources":["../../src/__tests__/mcp-init.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC1E,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAEpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAEvD;;;;;GAKG;AAEH,IAAI,UAAkB,CAAA;AACtB,IAAI,WAAmB,CAAA;AAEvB,KAAK,UAAU,GAAG,CAAC,OAAiB,EAAE;IACpC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;IAC7B,OAAO,CAAC,YAAY,EAAE,CAAA;IACtB,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAC3B,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;AACnE,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,OAAe;IACrC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;AAC3E,CAAC;AAED,UAAU,CAAC,KAAK,IAAI,EAAE;IACpB,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAC3B,UAAU,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,mBAAmB,CAAC,CAAC,CAAA;IACpE,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;AAC3B,CAAC,CAAC,CAAA;AAEF,SAAS,CAAC,KAAK,IAAI,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;IAC1B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;IACpB,MAAM,EAAE,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AACxD,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;IACxB,EAAE,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;QACrF,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,gBAAgB,EAAE,MAAM,CAAC,CAAA;QAC9E,MAAM,GAAG,EAAE,CAAA;QAEX,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,CAAA;QACjD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACrD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,2BAA2B,CAAC,CAAC,CAAA;QACnF,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAA;QACpF,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAA;QAE1F,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,CAAA;QACjD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAEjD,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAA;QAC7E,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAA;QAC3C,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAA;QAC/C,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAA;IACjD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,qBAAqB,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAA;QAEtE,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,CAAA;QACjD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;YAC5C,gBAAgB,EAAE,qBAAqB;YACvC,eAAe,EAAE,gBAAgB;SAClC,CAAC,CAAA;QACF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,CAAA;QACjD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAA;IACjF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAClE,MAAM,SAAS,CACb,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,UAAU,CAAC,EAC5C,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,EACrE,MAAM,CACP,CAAA;QACD,MAAM,GAAG,EAAE,CAAA;QAEX,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,CAAA;QACjD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAA;QACnE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAA;IACjD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAClE,MAAM,QAAQ,GAAG;YACf,UAAU,EAAE,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,EAAE,EAAE;SAC7F,CAAA;QACD,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,UAAU,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAA;QAE/F,MAAM,GAAG,EAAE,CAAA;QACX,IAAI,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,CAAA;QAC/C,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAExD,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,yBAAyB,CAAC,CAAC,CAAA;QAC1D,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,CAAA;QAC3C,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACrD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAA;IACxF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC9E,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAClE,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,UAAU,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,CAAA;QAEnF,MAAM,GAAG,EAAE,CAAA;QACX,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAChC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;QAEpB,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;QACtB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,CAAA;QACjD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAA;IACjD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,GAAG,EAAE,CAAA;QACX,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;QACtB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAA;QAC7E,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,kBAAkB,CAAC,CAAC,MAAM,CAAA;QACzF,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACvB,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAuHnC,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA0DzD"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
const PLACEHOLDER_KEY = 'act_sk_REPLACE_WITH_YOUR_KEY';
|
|
7
|
+
/** Cursor / Claude Desktop shape: top-level `mcpServers`. */
|
|
8
|
+
function cursorEntry(opts) {
|
|
9
|
+
return {
|
|
10
|
+
command: 'npx',
|
|
11
|
+
args: ['-y', '@actuate-media/mcp-server'],
|
|
12
|
+
env: { ACTUATE_BASE_URL: opts.url, ACTUATE_API_KEY: opts.key },
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** VS Code shape: top-level `servers`, each entry declares `type: "stdio"`. */
|
|
16
|
+
function vscodeEntry(opts) {
|
|
17
|
+
return { type: 'stdio', ...cursorEntry(opts) };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create or merge one editor config. Existing files keep every other server
|
|
21
|
+
* entry; an existing `actuate` entry is only replaced with `--force`.
|
|
22
|
+
*/
|
|
23
|
+
async function mergeConfigFile(root, target, force) {
|
|
24
|
+
const filePath = path.join(root, target.relPath);
|
|
25
|
+
let config = {};
|
|
26
|
+
let existed = false;
|
|
27
|
+
if (existsSync(filePath)) {
|
|
28
|
+
existed = true;
|
|
29
|
+
const raw = await readFile(filePath, 'utf8');
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(raw);
|
|
32
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
33
|
+
config = parsed;
|
|
34
|
+
}
|
|
35
|
+
else if (!force) {
|
|
36
|
+
logger.error(`${target.relPath} is not a JSON object — fix it or re-run with --force.`);
|
|
37
|
+
return 'error';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
if (!force) {
|
|
42
|
+
logger.error(`${target.relPath} contains invalid JSON — fix it or re-run with --force.`);
|
|
43
|
+
return 'error';
|
|
44
|
+
}
|
|
45
|
+
config = {};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const serversRaw = config[target.rootKey];
|
|
49
|
+
const servers = serversRaw && typeof serversRaw === 'object' && !Array.isArray(serversRaw)
|
|
50
|
+
? serversRaw
|
|
51
|
+
: {};
|
|
52
|
+
if (servers.actuate && !force) {
|
|
53
|
+
logger.warn(`${target.relPath} already has an "actuate" server — use --force to replace it.`);
|
|
54
|
+
return 'skipped';
|
|
55
|
+
}
|
|
56
|
+
config[target.rootKey] = { ...servers, actuate: target.entry };
|
|
57
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
58
|
+
await writeFile(filePath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
59
|
+
return existed ? 'updated' : 'created';
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Make sure both MCP config paths are git-ignored. Appends only the missing
|
|
63
|
+
* entries; creates `.gitignore` if the project somehow has none.
|
|
64
|
+
*/
|
|
65
|
+
async function ensureGitignored(root, relPaths) {
|
|
66
|
+
const gitignorePath = path.join(root, '.gitignore');
|
|
67
|
+
const existing = existsSync(gitignorePath) ? await readFile(gitignorePath, 'utf8') : '';
|
|
68
|
+
const lines = new Set(existing.split(/\r?\n/).map((l) => l.trim()));
|
|
69
|
+
const missing = relPaths.filter((p) => !lines.has(p));
|
|
70
|
+
if (missing.length === 0)
|
|
71
|
+
return [];
|
|
72
|
+
const block = (existing && !existing.endsWith('\n') ? '\n' : '') +
|
|
73
|
+
'\n# MCP configs hold a real API key once configured — never commit them\n' +
|
|
74
|
+
missing.join('\n') +
|
|
75
|
+
'\n';
|
|
76
|
+
await writeFile(gitignorePath, existing + block, 'utf8');
|
|
77
|
+
return missing;
|
|
78
|
+
}
|
|
79
|
+
export function registerMcpCommand(program) {
|
|
80
|
+
program
|
|
81
|
+
.command('mcp:init')
|
|
82
|
+
.description('Write .cursor/mcp.json + .vscode/mcp.json wiring the Actuate MCP server')
|
|
83
|
+
.option('--url <baseUrl>', 'Base URL of the running CMS', 'http://localhost:3000')
|
|
84
|
+
.option('--key <apiKey>', 'Actuate API key (act_sk_...)', PLACEHOLDER_KEY)
|
|
85
|
+
.option('--force', 'Replace an existing "actuate" server entry (or unparseable config)')
|
|
86
|
+
.action(async (opts) => {
|
|
87
|
+
const root = process.cwd();
|
|
88
|
+
if (opts.key !== PLACEHOLDER_KEY && !opts.key.startsWith('act_sk_')) {
|
|
89
|
+
logger.warn('The provided --key does not look like an Actuate API key (expected act_sk_ prefix).');
|
|
90
|
+
}
|
|
91
|
+
const targets = [
|
|
92
|
+
{ relPath: '.cursor/mcp.json', rootKey: 'mcpServers', entry: cursorEntry(opts) },
|
|
93
|
+
{ relPath: '.vscode/mcp.json', rootKey: 'servers', entry: vscodeEntry(opts) },
|
|
94
|
+
];
|
|
95
|
+
let failed = false;
|
|
96
|
+
for (const target of targets) {
|
|
97
|
+
const result = await mergeConfigFile(root, target, Boolean(opts.force));
|
|
98
|
+
if (result === 'created')
|
|
99
|
+
logger.success(`Created ${target.relPath}`);
|
|
100
|
+
else if (result === 'updated')
|
|
101
|
+
logger.success(`Updated ${target.relPath}`);
|
|
102
|
+
else if (result === 'error')
|
|
103
|
+
failed = true;
|
|
104
|
+
}
|
|
105
|
+
const ignored = await ensureGitignored(root, targets.map((t) => t.relPath));
|
|
106
|
+
if (ignored.length > 0) {
|
|
107
|
+
logger.success(`Added to .gitignore: ${ignored.join(', ')}`);
|
|
108
|
+
}
|
|
109
|
+
console.log();
|
|
110
|
+
console.log(chalk.bold('Next steps'));
|
|
111
|
+
if (opts.key === PLACEHOLDER_KEY) {
|
|
112
|
+
console.log(` 1. Mint an API key in ${chalk.cyan('Admin → Settings → API Keys')} (act_sk_...)`);
|
|
113
|
+
console.log(` 2. Replace ${chalk.cyan(PLACEHOLDER_KEY)} in both config files with the real key`);
|
|
114
|
+
console.log(' 3. Restart Cursor / VS Code so the MCP server is picked up');
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.log(' 1. Restart Cursor / VS Code so the MCP server is picked up');
|
|
118
|
+
}
|
|
119
|
+
if (opts.url === 'http://localhost:3000') {
|
|
120
|
+
console.log(chalk.dim(' Tip: pass --url https://your-site.com to target a deployed CMS instead.'));
|
|
121
|
+
}
|
|
122
|
+
if (failed)
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { registerDbStatusCommand } from './commands/db-status.js';
|
|
|
14
14
|
import { registerInitCommand } from './commands/init.js';
|
|
15
15
|
import { registerDeployCheckCommand, registerDoctorCommand, registerVerifyCommand, } from './commands/doctor.js';
|
|
16
16
|
import { registerVercelBlobLinkCommand } from './commands/vercel-blob-link.js';
|
|
17
|
+
import { registerMcpCommand } from './commands/mcp.js';
|
|
17
18
|
const program = new Command();
|
|
18
19
|
program
|
|
19
20
|
.name('actuate')
|
|
@@ -35,5 +36,6 @@ registerDoctorCommand(program);
|
|
|
35
36
|
registerDeployCheckCommand(program);
|
|
36
37
|
registerVerifyCommand(program);
|
|
37
38
|
registerVercelBlobLinkCommand(program);
|
|
39
|
+
registerMcpCommand(program);
|
|
38
40
|
program.parse();
|
|
39
41
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAA;AAC/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAA;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAA;AAC/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAA;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAA;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEtD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,6DAA6D,CAAC;KAC1E,OAAO,CAAC,OAAO,CAAC,CAAA;AAEnB,sBAAsB,CAAC,OAAO,CAAC,CAAA;AAC/B,8BAA8B,CAAC,OAAO,CAAC,CAAA;AACvC,uBAAuB,CAAC,OAAO,CAAC,CAAA;AAChC,mBAAmB,CAAC,OAAO,CAAC,CAAA;AAC5B,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,sBAAsB,CAAC,OAAO,CAAC,CAAA;AAC/B,0BAA0B,CAAC,OAAO,CAAC,CAAA;AACnC,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,uBAAuB,CAAC,OAAO,CAAC,CAAA;AAChC,mBAAmB,CAAC,OAAO,CAAC,CAAA;AAC5B,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,0BAA0B,CAAC,OAAO,CAAC,CAAA;AACnC,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC9B,6BAA6B,CAAC,OAAO,CAAC,CAAA;AACtC,kBAAkB,CAAC,OAAO,CAAC,CAAA;AAE3B,OAAO,CAAC,KAAK,EAAE,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actuate-media/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "CLI for Actuate CMS — migrations, codegen, imports, exports, and upgrades",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"typescript": "^5.7.0",
|
|
30
30
|
"vitest": "^4.1.0",
|
|
31
|
-
"@actuate-media/cms-core": "0.
|
|
31
|
+
"@actuate-media/cms-core": "0.63.0"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "tsc",
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { Command } from 'commander'
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { registerMcpCommand } from '../commands/mcp.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `actuate mcp:init` retrofits MCP editor configs onto existing projects.
|
|
11
|
+
* Pins the merge contract: other configured servers are never clobbered, an
|
|
12
|
+
* existing `actuate` entry needs `--force`, and both config paths end up
|
|
13
|
+
* git-ignored (they hold a real API key once configured).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
let projectDir: string
|
|
17
|
+
let originalCwd: string
|
|
18
|
+
|
|
19
|
+
async function run(args: string[] = []): Promise<void> {
|
|
20
|
+
const program = new Command()
|
|
21
|
+
program.exitOverride()
|
|
22
|
+
registerMcpCommand(program)
|
|
23
|
+
await program.parseAsync(['mcp:init', ...args], { from: 'user' })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function readJson(relPath: string): Promise<any> {
|
|
27
|
+
return JSON.parse(await readFile(path.join(projectDir, relPath), 'utf8'))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
originalCwd = process.cwd()
|
|
32
|
+
projectDir = await mkdtemp(path.join(tmpdir(), 'actuate-mcp-init-'))
|
|
33
|
+
process.chdir(projectDir)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
afterEach(async () => {
|
|
37
|
+
process.chdir(originalCwd)
|
|
38
|
+
process.exitCode = 0
|
|
39
|
+
await rm(projectDir, { recursive: true, force: true })
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('mcp:init', () => {
|
|
43
|
+
it('creates both editor configs with placeholder key and git-ignores them', async () => {
|
|
44
|
+
await writeFile(path.join(projectDir, '.gitignore'), 'node_modules\n', 'utf8')
|
|
45
|
+
await run()
|
|
46
|
+
|
|
47
|
+
const cursor = await readJson('.cursor/mcp.json')
|
|
48
|
+
expect(cursor.mcpServers.actuate.command).toBe('npx')
|
|
49
|
+
expect(cursor.mcpServers.actuate.args).toEqual(['-y', '@actuate-media/mcp-server'])
|
|
50
|
+
expect(cursor.mcpServers.actuate.env.ACTUATE_BASE_URL).toBe('http://localhost:3000')
|
|
51
|
+
expect(cursor.mcpServers.actuate.env.ACTUATE_API_KEY).toBe('act_sk_REPLACE_WITH_YOUR_KEY')
|
|
52
|
+
|
|
53
|
+
const vscode = await readJson('.vscode/mcp.json')
|
|
54
|
+
expect(vscode.servers.actuate.type).toBe('stdio')
|
|
55
|
+
|
|
56
|
+
const gitignore = await readFile(path.join(projectDir, '.gitignore'), 'utf8')
|
|
57
|
+
expect(gitignore).toContain('node_modules')
|
|
58
|
+
expect(gitignore).toContain('.cursor/mcp.json')
|
|
59
|
+
expect(gitignore).toContain('.vscode/mcp.json')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('applies --url and --key to both configs', async () => {
|
|
63
|
+
await run(['--url', 'https://example.com', '--key', 'act_sk_real123'])
|
|
64
|
+
|
|
65
|
+
const cursor = await readJson('.cursor/mcp.json')
|
|
66
|
+
expect(cursor.mcpServers.actuate.env).toEqual({
|
|
67
|
+
ACTUATE_BASE_URL: 'https://example.com',
|
|
68
|
+
ACTUATE_API_KEY: 'act_sk_real123',
|
|
69
|
+
})
|
|
70
|
+
const vscode = await readJson('.vscode/mcp.json')
|
|
71
|
+
expect(vscode.servers.actuate.env.ACTUATE_BASE_URL).toBe('https://example.com')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('merges into an existing config without clobbering other servers', async () => {
|
|
75
|
+
await mkdir(path.join(projectDir, '.cursor'), { recursive: true })
|
|
76
|
+
await writeFile(
|
|
77
|
+
path.join(projectDir, '.cursor', 'mcp.json'),
|
|
78
|
+
JSON.stringify({ mcpServers: { sentry: { command: 'sentry-mcp' } } }),
|
|
79
|
+
'utf8',
|
|
80
|
+
)
|
|
81
|
+
await run()
|
|
82
|
+
|
|
83
|
+
const cursor = await readJson('.cursor/mcp.json')
|
|
84
|
+
expect(cursor.mcpServers.sentry).toEqual({ command: 'sentry-mcp' })
|
|
85
|
+
expect(cursor.mcpServers.actuate).toBeDefined()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('leaves an existing actuate entry alone unless --force is passed', async () => {
|
|
89
|
+
await mkdir(path.join(projectDir, '.cursor'), { recursive: true })
|
|
90
|
+
const existing = {
|
|
91
|
+
mcpServers: { actuate: { command: 'custom', env: { ACTUATE_BASE_URL: 'https://keep.me' } } },
|
|
92
|
+
}
|
|
93
|
+
await writeFile(path.join(projectDir, '.cursor', 'mcp.json'), JSON.stringify(existing), 'utf8')
|
|
94
|
+
|
|
95
|
+
await run()
|
|
96
|
+
let cursor = await readJson('.cursor/mcp.json')
|
|
97
|
+
expect(cursor.mcpServers.actuate.command).toBe('custom')
|
|
98
|
+
|
|
99
|
+
await run(['--force', '--url', 'https://new.example.com'])
|
|
100
|
+
cursor = await readJson('.cursor/mcp.json')
|
|
101
|
+
expect(cursor.mcpServers.actuate.command).toBe('npx')
|
|
102
|
+
expect(cursor.mcpServers.actuate.env.ACTUATE_BASE_URL).toBe('https://new.example.com')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('fails on invalid JSON without --force, overwrites with --force', async () => {
|
|
106
|
+
await mkdir(path.join(projectDir, '.cursor'), { recursive: true })
|
|
107
|
+
await writeFile(path.join(projectDir, '.cursor', 'mcp.json'), '{ not json', 'utf8')
|
|
108
|
+
|
|
109
|
+
await run()
|
|
110
|
+
expect(process.exitCode).toBe(1)
|
|
111
|
+
process.exitCode = 0
|
|
112
|
+
|
|
113
|
+
await run(['--force'])
|
|
114
|
+
const cursor = await readJson('.cursor/mcp.json')
|
|
115
|
+
expect(cursor.mcpServers.actuate).toBeDefined()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('does not duplicate gitignore entries on re-runs', async () => {
|
|
119
|
+
await run()
|
|
120
|
+
await run(['--force'])
|
|
121
|
+
const gitignore = await readFile(path.join(projectDir, '.gitignore'), 'utf8')
|
|
122
|
+
const count = gitignore.split('\n').filter((l) => l.trim() === '.cursor/mcp.json').length
|
|
123
|
+
expect(count).toBe(1)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { Command } from 'commander'
|
|
5
|
+
import chalk from 'chalk'
|
|
6
|
+
import { logger } from '../utils/logger.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `actuate mcp:init` — retrofit the Actuate MCP server config onto an
|
|
10
|
+
* existing project.
|
|
11
|
+
*
|
|
12
|
+
* New scaffolds (`npm create actuate-cms`) emit `.cursor/mcp.json` and
|
|
13
|
+
* `.vscode/mcp.json` automatically; projects created before that feature (or
|
|
14
|
+
* set up by hand) have no easy path. This command writes both editor configs,
|
|
15
|
+
* merging into existing files so other MCP servers are preserved, and ensures
|
|
16
|
+
* both paths are git-ignored (a real `act_sk_...` key pasted into them must
|
|
17
|
+
* never be committed).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
interface McpInitOptions {
|
|
21
|
+
url: string
|
|
22
|
+
key: string
|
|
23
|
+
force?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const PLACEHOLDER_KEY = 'act_sk_REPLACE_WITH_YOUR_KEY'
|
|
27
|
+
|
|
28
|
+
/** Cursor / Claude Desktop shape: top-level `mcpServers`. */
|
|
29
|
+
function cursorEntry(opts: McpInitOptions): Record<string, unknown> {
|
|
30
|
+
return {
|
|
31
|
+
command: 'npx',
|
|
32
|
+
args: ['-y', '@actuate-media/mcp-server'],
|
|
33
|
+
env: { ACTUATE_BASE_URL: opts.url, ACTUATE_API_KEY: opts.key },
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** VS Code shape: top-level `servers`, each entry declares `type: "stdio"`. */
|
|
38
|
+
function vscodeEntry(opts: McpInitOptions): Record<string, unknown> {
|
|
39
|
+
return { type: 'stdio', ...cursorEntry(opts) }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface MergeTarget {
|
|
43
|
+
/** Path relative to the project root, also used in log lines. */
|
|
44
|
+
relPath: string
|
|
45
|
+
/** Top-level key the editor expects (`mcpServers` vs `servers`). */
|
|
46
|
+
rootKey: 'mcpServers' | 'servers'
|
|
47
|
+
entry: Record<string, unknown>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type MergeResult = 'created' | 'updated' | 'skipped' | 'error'
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create or merge one editor config. Existing files keep every other server
|
|
54
|
+
* entry; an existing `actuate` entry is only replaced with `--force`.
|
|
55
|
+
*/
|
|
56
|
+
async function mergeConfigFile(
|
|
57
|
+
root: string,
|
|
58
|
+
target: MergeTarget,
|
|
59
|
+
force: boolean,
|
|
60
|
+
): Promise<MergeResult> {
|
|
61
|
+
const filePath = path.join(root, target.relPath)
|
|
62
|
+
|
|
63
|
+
let config: Record<string, unknown> = {}
|
|
64
|
+
let existed = false
|
|
65
|
+
if (existsSync(filePath)) {
|
|
66
|
+
existed = true
|
|
67
|
+
const raw = await readFile(filePath, 'utf8')
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(raw) as unknown
|
|
70
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
71
|
+
config = parsed as Record<string, unknown>
|
|
72
|
+
} else if (!force) {
|
|
73
|
+
logger.error(`${target.relPath} is not a JSON object — fix it or re-run with --force.`)
|
|
74
|
+
return 'error'
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
if (!force) {
|
|
78
|
+
logger.error(`${target.relPath} contains invalid JSON — fix it or re-run with --force.`)
|
|
79
|
+
return 'error'
|
|
80
|
+
}
|
|
81
|
+
config = {}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const serversRaw = config[target.rootKey]
|
|
86
|
+
const servers =
|
|
87
|
+
serversRaw && typeof serversRaw === 'object' && !Array.isArray(serversRaw)
|
|
88
|
+
? (serversRaw as Record<string, unknown>)
|
|
89
|
+
: {}
|
|
90
|
+
|
|
91
|
+
if (servers.actuate && !force) {
|
|
92
|
+
logger.warn(`${target.relPath} already has an "actuate" server — use --force to replace it.`)
|
|
93
|
+
return 'skipped'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
config[target.rootKey] = { ...servers, actuate: target.entry }
|
|
97
|
+
|
|
98
|
+
await mkdir(path.dirname(filePath), { recursive: true })
|
|
99
|
+
await writeFile(filePath, JSON.stringify(config, null, 2) + '\n', 'utf8')
|
|
100
|
+
return existed ? 'updated' : 'created'
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Make sure both MCP config paths are git-ignored. Appends only the missing
|
|
105
|
+
* entries; creates `.gitignore` if the project somehow has none.
|
|
106
|
+
*/
|
|
107
|
+
async function ensureGitignored(root: string, relPaths: string[]): Promise<string[]> {
|
|
108
|
+
const gitignorePath = path.join(root, '.gitignore')
|
|
109
|
+
const existing = existsSync(gitignorePath) ? await readFile(gitignorePath, 'utf8') : ''
|
|
110
|
+
const lines = new Set(existing.split(/\r?\n/).map((l) => l.trim()))
|
|
111
|
+
const missing = relPaths.filter((p) => !lines.has(p))
|
|
112
|
+
if (missing.length === 0) return []
|
|
113
|
+
|
|
114
|
+
const block =
|
|
115
|
+
(existing && !existing.endsWith('\n') ? '\n' : '') +
|
|
116
|
+
'\n# MCP configs hold a real API key once configured — never commit them\n' +
|
|
117
|
+
missing.join('\n') +
|
|
118
|
+
'\n'
|
|
119
|
+
await writeFile(gitignorePath, existing + block, 'utf8')
|
|
120
|
+
return missing
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function registerMcpCommand(program: Command): void {
|
|
124
|
+
program
|
|
125
|
+
.command('mcp:init')
|
|
126
|
+
.description('Write .cursor/mcp.json + .vscode/mcp.json wiring the Actuate MCP server')
|
|
127
|
+
.option('--url <baseUrl>', 'Base URL of the running CMS', 'http://localhost:3000')
|
|
128
|
+
.option('--key <apiKey>', 'Actuate API key (act_sk_...)', PLACEHOLDER_KEY)
|
|
129
|
+
.option('--force', 'Replace an existing "actuate" server entry (or unparseable config)')
|
|
130
|
+
.action(async (opts: McpInitOptions) => {
|
|
131
|
+
const root = process.cwd()
|
|
132
|
+
|
|
133
|
+
if (opts.key !== PLACEHOLDER_KEY && !opts.key.startsWith('act_sk_')) {
|
|
134
|
+
logger.warn(
|
|
135
|
+
'The provided --key does not look like an Actuate API key (expected act_sk_ prefix).',
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const targets: MergeTarget[] = [
|
|
140
|
+
{ relPath: '.cursor/mcp.json', rootKey: 'mcpServers', entry: cursorEntry(opts) },
|
|
141
|
+
{ relPath: '.vscode/mcp.json', rootKey: 'servers', entry: vscodeEntry(opts) },
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
let failed = false
|
|
145
|
+
for (const target of targets) {
|
|
146
|
+
const result = await mergeConfigFile(root, target, Boolean(opts.force))
|
|
147
|
+
if (result === 'created') logger.success(`Created ${target.relPath}`)
|
|
148
|
+
else if (result === 'updated') logger.success(`Updated ${target.relPath}`)
|
|
149
|
+
else if (result === 'error') failed = true
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const ignored = await ensureGitignored(
|
|
153
|
+
root,
|
|
154
|
+
targets.map((t) => t.relPath),
|
|
155
|
+
)
|
|
156
|
+
if (ignored.length > 0) {
|
|
157
|
+
logger.success(`Added to .gitignore: ${ignored.join(', ')}`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log()
|
|
161
|
+
console.log(chalk.bold('Next steps'))
|
|
162
|
+
if (opts.key === PLACEHOLDER_KEY) {
|
|
163
|
+
console.log(
|
|
164
|
+
` 1. Mint an API key in ${chalk.cyan('Admin → Settings → API Keys')} (act_sk_...)`,
|
|
165
|
+
)
|
|
166
|
+
console.log(
|
|
167
|
+
` 2. Replace ${chalk.cyan(PLACEHOLDER_KEY)} in both config files with the real key`,
|
|
168
|
+
)
|
|
169
|
+
console.log(' 3. Restart Cursor / VS Code so the MCP server is picked up')
|
|
170
|
+
} else {
|
|
171
|
+
console.log(' 1. Restart Cursor / VS Code so the MCP server is picked up')
|
|
172
|
+
}
|
|
173
|
+
if (opts.url === 'http://localhost:3000') {
|
|
174
|
+
console.log(
|
|
175
|
+
chalk.dim(' Tip: pass --url https://your-site.com to target a deployed CMS instead.'),
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (failed) process.exitCode = 1
|
|
180
|
+
})
|
|
181
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
registerVerifyCommand,
|
|
19
19
|
} from './commands/doctor.js'
|
|
20
20
|
import { registerVercelBlobLinkCommand } from './commands/vercel-blob-link.js'
|
|
21
|
+
import { registerMcpCommand } from './commands/mcp.js'
|
|
21
22
|
|
|
22
23
|
const program = new Command()
|
|
23
24
|
|
|
@@ -42,5 +43,6 @@ registerDoctorCommand(program)
|
|
|
42
43
|
registerDeployCheckCommand(program)
|
|
43
44
|
registerVerifyCommand(program)
|
|
44
45
|
registerVercelBlobLinkCommand(program)
|
|
46
|
+
registerMcpCommand(program)
|
|
45
47
|
|
|
46
48
|
program.parse()
|