@clubedge/create-clubedge-app 0.35.0 → 0.37.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 (46) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +67 -10
  3. package/bin/mcp.mjs +2 -0
  4. package/dist/mcp-server.js +253 -0
  5. package/dist/steps/git.js +4 -4
  6. package/dist/steps/install.js +2 -2
  7. package/dist/utils/process.js +2 -2
  8. package/package.json +17 -10
  9. package/skills/clubedge-starter/SKILL.md +56 -0
  10. package/template/.github/workflows/preview.yml +3 -1
  11. package/template/apps/api/package.json +1 -1
  12. package/template/apps/api-fastapi/package.json +1 -1
  13. package/template/apps/api-fastapi/pyproject.toml +1 -1
  14. package/template/apps/api-fastapi/uv.lock +4 -4
  15. package/template/apps/start/package.json +1 -1
  16. package/template/apps/vite/package.json +1 -1
  17. package/template/apps/web/package.json +1 -1
  18. package/template/package.json +1 -1
  19. package/template/packages/auth/package.json +1 -1
  20. package/template/packages/auth-appwrite/package.json +1 -1
  21. package/template/packages/auth-better-auth/package.json +1 -1
  22. package/template/packages/auth-firebase/package.json +1 -1
  23. package/template/packages/auth-supabase/package.json +1 -1
  24. package/template/packages/cache/package.json +1 -1
  25. package/template/packages/cache-redis/package.json +1 -1
  26. package/template/packages/contract/package.json +1 -1
  27. package/template/packages/core/package.json +1 -1
  28. package/template/packages/db/package.json +1 -1
  29. package/template/packages/email/package.json +1 -1
  30. package/template/packages/email-resend/package.json +1 -1
  31. package/template/packages/email-smtp/package.json +1 -1
  32. package/template/packages/jobs/package.json +1 -1
  33. package/template/packages/jobs-bullmq/package.json +1 -1
  34. package/template/packages/jobs-inngest/package.json +1 -1
  35. package/template/packages/jobs-pg-boss/package.json +1 -1
  36. package/template/packages/local-db/package.json +1 -1
  37. package/template/packages/observability/package.json +1 -1
  38. package/template/packages/observability-otel/package.json +1 -1
  39. package/template/packages/observability-sentry/package.json +1 -1
  40. package/template/packages/payments/package.json +1 -1
  41. package/template/packages/payments-stripe/package.json +1 -1
  42. package/template/packages/storage/package.json +1 -1
  43. package/template/packages/storage-s3/package.json +1 -1
  44. package/template/packages/storage-supabase/package.json +1 -1
  45. package/template/packages/ui/package.json +1 -1
  46. package/template.lock.json +2 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to `create-clubedge-app` are documented here.
4
4
 
5
+ ## 0.37.0
6
+
7
+ - Add MCP server and agent skill (#110).
8
+ - Still on Starter `v0.24.1`.
9
+
10
+ ## 0.36.0
11
+
12
+ - Bump vitest from 5.0.3 to 5.0.5 in the minor-and-patch group (#104).
13
+ - Bump giget from 2.0.0 to 3.3.1 (#69).
14
+ - Bump @types/node from 22.20.5 to 26.6.5 (#68).
15
+ - Default to Starter `v0.24.1` (#107).
16
+
5
17
  ## 0.35.0
6
18
 
7
19
  - **Deploy a frontend and its separate API** (#103):
package/README.md CHANGED
@@ -27,6 +27,61 @@ Add, remove, or upgrade them later without losing your own changes.
27
27
  pnpm dlx @clubedge/create-clubedge-app my-app
28
28
  ```
29
29
 
30
+ ## MCP server
31
+
32
+ Coding agents can connect to a local MCP server to inspect the capabilities of this CLI's pinned Starter release, validate stack and module choices, preview a complete scaffold plan, and generate a project in the server's working directory. The server exposes `get_starter_capabilities`, `validate_project_config`, `plan_project`, and `create_project`. Planning and validation do not write files. Project creation does not initialize Git or install dependencies unless those options are requested.
33
+
34
+ After publishing a release that includes the MCP server, run it from the workspace where projects should be created:
35
+
36
+ ```sh
37
+ npx -y --package @clubedge/create-clubedge-app create-clubedge-app-mcp
38
+ ```
39
+
40
+ Configure an MCP client to launch `npx -y --package @clubedge/create-clubedge-app create-clubedge-app-mcp` with its working directory set to the workspace. In Codex, add this to `~/.codex/config.toml` and replace the example with the absolute path of the workspace where projects should be created:
41
+
42
+ ```toml
43
+ [mcp_servers.clubedge-starter]
44
+ command = "npx"
45
+ args = ["--yes", "--package", "@clubedge/create-clubedge-app", "create-clubedge-app-mcp"]
46
+ cwd = "D:/path/to/workspace"
47
+ ```
48
+
49
+ The MCP server is included in the same package and uses the CLI's bundled Starter and compatibility rules. Its create tool only writes beneath the configured working directory and refuses a non-empty destination.
50
+
51
+ ### Agent skill
52
+
53
+ The npm package includes the Agent Skill at [`skills/clubedge-starter/SKILL.md`](skills/clubedge-starter/SKILL.md). After the release is published, download and extract the package in PowerShell:
54
+
55
+ ```powershell
56
+ $skillTarball = npm pack @clubedge/create-clubedge-app@latest
57
+ tar -xzf $skillTarball
58
+ ```
59
+
60
+ Then install it for the current user:
61
+
62
+ ```powershell
63
+ # Codex
64
+ New-Item -ItemType Directory -Force "$HOME/.agents/skills/clubedge-starter" | Out-Null
65
+ Copy-Item .\package\skills\clubedge-starter\SKILL.md "$HOME/.agents/skills/clubedge-starter/SKILL.md"
66
+
67
+ # Claude Code
68
+ New-Item -ItemType Directory -Force "$HOME/.claude/skills/clubedge-starter" | Out-Null
69
+ Copy-Item .\package\skills\clubedge-starter\SKILL.md "$HOME/.claude/skills/clubedge-starter/SKILL.md"
70
+ ```
71
+
72
+ These commands create `package/` in the current directory. Remove that extracted folder and tarball when the copy is done. Before publication, the same copy commands work from the repository root by replacing `package/skills/...` with `skills/...`.
73
+
74
+ For a repository-only install, use `.agents/skills/clubedge-starter/` for Codex or `.claude/skills/clubedge-starter/` for Claude Code instead of the `$HOME` paths. Restart the agent session after installing. In Codex, ask it to `Use the clubedge-starter skill to inspect the available stacks and modules. Do not create a project.` In Claude Code, invoke `/clubedge-starter` and give it the same request. The skill uses live MCP capabilities when available, so its choices follow the Starter release installed with the server.
75
+
76
+ To connect the MCP server in Claude Code, run this from the workspace where projects should be created:
77
+
78
+ ```sh
79
+ claude mcp add --scope local --transport stdio clubedge-starter -- npx -y --package @clubedge/create-clubedge-app create-clubedge-app-mcp
80
+ claude mcp get clubedge-starter
81
+ ```
82
+
83
+ Then open Claude Code in that workspace, invoke `/clubedge-starter`, and ask it to inspect the available stacks and modules without creating a project. In Codex, use the `cwd` in `~/.codex/config.toml` above, restart Codex, and ask the same prompt. To exercise project creation, ask the agent to create a named project in a new child folder with Git and dependency installation disabled; verify the folder and report before enabling those optional actions.
84
+
30
85
  ## Why create-clubedge-app
31
86
 
32
87
  Copying a starter is easy; living with it is not. You delete the services you don't use by hand, and once you've changed the code, every later improvement to the starter has to be copied over by hand too.
@@ -152,7 +207,7 @@ pnpm dlx @clubedge/create-clubedge-app my-app --yes --no-git --framework tanstac
152
207
  pnpm dlx @clubedge/create-clubedge-app my-app --ref main
153
208
 
154
209
  # Reproduce a specific Starter release or commit
155
- pnpm dlx @clubedge/create-clubedge-app my-app --ref v0.24.0
210
+ pnpm dlx @clubedge/create-clubedge-app my-app --ref v0.24.1
156
211
  pnpm dlx @clubedge/create-clubedge-app my-app --ref <commit-sha>
157
212
 
158
213
  # Test local Starter changes before releasing them
@@ -171,7 +226,7 @@ pnpm clubedge add email resend payments stripe # several modules as
171
226
  pnpm clubedge remove storage payments
172
227
  pnpm dlx @clubedge/create-clubedge-app add infra local --dry-run
173
228
  pnpm dlx @clubedge/create-clubedge-app@latest upgrade # to the newest CLI's Starter release
174
- pnpm dlx @clubedge/create-clubedge-app upgrade --ref v0.24.0 # or a specific tag, branch, or commit
229
+ pnpm dlx @clubedge/create-clubedge-app upgrade --ref v0.24.1 # or a specific tag, branch, or commit
175
230
  ```
176
231
 
177
232
  The CLI renders the project twice, from the Starter commit and modules recorded in your `package.json` and from the new state, and merges the difference into your project. The new state has other modules for `add` and `remove`, and the newer Starter with the same modules for `upgrade`. Modules a newer Starter introduces start as `none` where possible, so an upgrade never adds a service on its own.
@@ -282,27 +337,27 @@ Projects created with `--infra neon` also have `.github/workflows/preview.yml`,
282
337
  Each CLI release is tested against one specific Starter release:
283
338
 
284
339
  ```text
285
- create-clubedge-app v0.35.0
340
+ create-clubedge-app v0.37.0
286
341
  |
287
342
  v
288
- Clubedge Starter v0.24.0
289
- commit 3aaca476bf5ff1db9b5fc4080f7a8a8006436998
343
+ Clubedge Starter v0.24.1
344
+ commit 3e8b5ba8910767f27336cf253fbfe92a9bb42875
290
345
  ```
291
346
 
292
347
  The current relationship is:
293
348
 
294
349
  | CLI release | Default Starter release | Meaning |
295
350
  | --- | --- | --- |
296
- | `create-clubedge-app@0.35.0` | `clubedge-starter@v0.24.0` | The CLI's required, tested default |
351
+ | `create-clubedge-app@0.37.0` | `clubedge-starter@v0.24.1` | The CLI's required, tested default |
297
352
 
298
353
  This means the same CLI version always scaffolds the same Starter revision by default. The CLI does not silently follow Starter `main`.
299
354
 
300
- The `v0.24.0` tag is expected to resolve to commit
301
- `3aaca476bf5ff1db9b5fc4080f7a8a8006436998`. The CLI verifies this before
355
+ The `v0.24.1` tag is expected to resolve to commit
356
+ `3e8b5ba8910767f27336cf253fbfe92a9bb42875`. The CLI verifies this before
302
357
  scaffolding with the default reference and fails rather than silently using a
303
358
  moved tag.
304
359
 
305
- When the Starter changes, its maintainers first publish a new versioned release, such as `v0.24.0`. The CLI is then updated to use that release, all generated-project checks must pass, and a new CLI version is published. This keeps Starter and CLI releases independently versioned while preserving a tested compatibility relationship.
360
+ When the Starter changes, its maintainers first publish a new versioned release, such as `v0.24.1`. The CLI is then updated to use that release, all generated-project checks must pass, and a new CLI version is published. This keeps Starter and CLI releases independently versioned while preserving a tested compatibility relationship.
306
361
 
307
362
  Every generated project records its CLI version, Starter repository, Starter ref, Starter commit, framework, and modules in `package.json` and `README.md`.
308
363
 
@@ -357,7 +412,7 @@ CI packs the CLI with `npm pack`, scaffolds clean projects from the packed tarba
357
412
  1. Make and validate changes in `clubedge-starter`.
358
413
  2. Run lint, typecheck, unit tests, browser tests, and production build.
359
414
  3. Update the Starter package versions.
360
- 4. Commit `main`, create a release tag such as `v0.24.0`, and publish the Starter release.
415
+ 4. Commit `main`, create a release tag such as `v0.24.1`, and publish the Starter release.
361
416
 
362
417
  ### CLI maintainers
363
418
 
@@ -403,6 +458,8 @@ Each phase ends with a Starter release and a CLI release pinned to it. The full
403
458
  | 0.33.0 | v0.22.0 | Starter v0.22.0: stack packs (template schema 4), and a React + Vite frontend with a separate Hono API (`--framework vite --api hono`) |
404
459
  | 0.34.0 | v0.23.0 | Starter v0.23.0: a FastAPI API in Python (`--framework vite --api fastapi`), generated with no CLI change |
405
460
  | 0.35.0 | v0.24.0 | Deploy frontend and API stack packs; Default to Starter v0.24.0 |
461
+ | 0.36.0 | v0.24.1 | Bump vitest from 5.0.3 to 5.0.5 in the minor-and-patch group; Bump giget from 2.0.0 to 3.3.1; Bump @types/node from 22.20.5 to 26.6.5; Default to Starter v0.24.1 |
462
+ | 0.37.0 | v0.24.1 | Add MCP server and agent skill |
406
463
  | next | | Show the Starter's version of a conflicted file next to yours |
407
464
 
408
465
  ## Community
package/bin/mcp.mjs ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../dist/mcp-server.js";
@@ -0,0 +1,253 @@
1
+ import { realpath } from "node:fs/promises";
2
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
3
+ import process from "node:process";
4
+ import { McpServer } from "@modelcontextprotocol/server";
5
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
6
+ import { z } from "zod/v4";
7
+ import { executePlan } from "./core/execute.js";
8
+ import { readTemplateManifest } from "./core/manifest.js";
9
+ import { createPlan, describePlan } from "./core/plan.js";
10
+ import { resolveTemplateSource } from "./core/source.js";
11
+ import { cliVersion, cliUrl, siteUrl } from "./package-info.js";
12
+ import { initializeGit } from "./steps/git.js";
13
+ import { installDependencies, resolvePnpm } from "./steps/install.js";
14
+ import { commandSucceeds, run } from "./utils/process.js";
15
+ import { displayNameFromPackageName, packageNameFromDirectory } from "./utils/names.js";
16
+ // Keep subprocess output off the MCP JSON-RPC stdout stream, and don't let children read MCP input.
17
+ const mcpStdio = ["ignore", process.stderr, process.stderr];
18
+ const selectionSchema = {
19
+ stacks: z.record(z.string(), z.string()).optional().describe("Stack id by slot, such as { framework: 'next', api: 'hono' }."),
20
+ modules: z.record(z.string(), z.string()).optional().describe("Module option by module id, such as { auth: 'supabase', storage: 's3' }."),
21
+ };
22
+ function isWithin(directory, candidate) {
23
+ const path = relative(directory, candidate);
24
+ return path === "" || (!isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`));
25
+ }
26
+ async function workspaceDestination(directory) {
27
+ if (!directory.trim())
28
+ throw new Error("Choose a project directory inside the MCP server's working directory.");
29
+ if (isAbsolute(directory))
30
+ throw new Error("Use a relative project directory beneath the MCP server's working directory.");
31
+ const workspace = await realpath(process.cwd());
32
+ const target = resolve(workspace, directory);
33
+ if (!isWithin(workspace, target) || target === workspace) {
34
+ throw new Error("The project directory must be a child of the MCP server's working directory.");
35
+ }
36
+ // Resolve the nearest existing ancestor so a symlink inside the workspace cannot redirect
37
+ // project writes to an unrelated directory.
38
+ let ancestor = dirname(target);
39
+ let resolvedAncestor;
40
+ while (resolvedAncestor === undefined) {
41
+ try {
42
+ resolvedAncestor = await realpath(ancestor);
43
+ }
44
+ catch (error) {
45
+ if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT"))
46
+ throw error;
47
+ const parent = dirname(ancestor);
48
+ if (parent === ancestor)
49
+ throw error;
50
+ ancestor = parent;
51
+ }
52
+ }
53
+ if (resolvedAncestor === undefined || !isWithin(workspace, resolvedAncestor)) {
54
+ throw new Error("The project directory resolves outside the MCP server's working directory.");
55
+ }
56
+ try {
57
+ const resolvedTarget = await realpath(target);
58
+ if (!isWithin(workspace, resolvedTarget)) {
59
+ throw new Error("The project directory resolves outside the MCP server's working directory.");
60
+ }
61
+ }
62
+ catch (error) {
63
+ if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT"))
64
+ throw error;
65
+ }
66
+ return target;
67
+ }
68
+ async function withTemplate(fn) {
69
+ const source = await resolveTemplateSource({});
70
+ try {
71
+ return await fn(source);
72
+ }
73
+ finally {
74
+ await source.cleanup();
75
+ }
76
+ }
77
+ async function planProject(source, options, directory, sideEffects = {}) {
78
+ const manifest = await readTemplateManifest(source.root);
79
+ return createPlan({
80
+ targetDirectory: directory,
81
+ identity: {
82
+ packageName: packageNameFromDirectory(directory),
83
+ displayName: displayNameFromPackageName(packageNameFromDirectory(directory)),
84
+ },
85
+ source,
86
+ manifest,
87
+ stacks: options.stacks,
88
+ modules: options.modules,
89
+ cliVersion,
90
+ git: sideEffects.git ?? false,
91
+ install: sideEffects.install ?? false,
92
+ });
93
+ }
94
+ function capabilities(manifest, source) {
95
+ return {
96
+ cliVersion,
97
+ starter: {
98
+ repository: source.repository,
99
+ ref: source.ref,
100
+ commit: source.commit,
101
+ docs: cliUrl,
102
+ website: siteUrl,
103
+ },
104
+ slots: Object.entries(manifest.slots).map(([id, slot]) => ({
105
+ id,
106
+ name: slot.name,
107
+ default: slot.default ?? "none",
108
+ choices: Object.entries(manifest.stacks)
109
+ .filter(([, stack]) => stack.slot === id)
110
+ .map(([stackId, stack]) => ({ id: stackId, name: stack.name, role: stack.role, modules: stack.modules })),
111
+ })),
112
+ modules: Object.entries(manifest.modules).map(([id, module]) => ({
113
+ id,
114
+ name: module.name,
115
+ scope: module.scope,
116
+ default: module.default,
117
+ options: Object.entries(module.options).map(([optionId, option]) => ({
118
+ id: optionId,
119
+ name: option.name,
120
+ requires: option.requires,
121
+ })),
122
+ })),
123
+ };
124
+ }
125
+ function result(value) {
126
+ return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
127
+ }
128
+ function createServer() {
129
+ const server = new McpServer({ name: "clubedge-starter", version: cliVersion });
130
+ server.registerTool("get_starter_capabilities", {
131
+ description: "Discover the frameworks, API stacks, modules, defaults, and compatibility choices supported by this CLI's pinned Clubedge Starter release.",
132
+ inputSchema: z.object({}),
133
+ annotations: { readOnlyHint: true },
134
+ }, async () => withTemplate(async (source) => result(capabilities(await readTemplateManifest(source.root), source))));
135
+ server.registerTool("validate_project_config", {
136
+ description: "Check requested stack and module choices against the pinned Starter. This does not write files.",
137
+ inputSchema: z.object(selectionSchema),
138
+ annotations: { readOnlyHint: true },
139
+ }, async (options) => withTemplate(async (source) => {
140
+ try {
141
+ const plan = await planProject(source, options, resolve(process.cwd(), ".clubedge-validation"));
142
+ return result({
143
+ valid: true,
144
+ stacks: plan.apps.map(({ slot, id, name }) => ({ slot, id, name })),
145
+ modules: plan.modules.map(({ id, option, optionName }) => ({ id, option, name: optionName })),
146
+ });
147
+ }
148
+ catch (error) {
149
+ return result({ valid: false, error: error instanceof Error ? error.message : String(error) });
150
+ }
151
+ }));
152
+ server.registerTool("plan_project", {
153
+ description: "Preview a complete scaffold plan using the pinned Starter. Validates stack and module compatibility and lists planned files and setup actions without writing to disk.",
154
+ inputSchema: z.object({
155
+ directory: z.string().min(1).describe("Destination path relative to the MCP server's working directory."),
156
+ ...selectionSchema,
157
+ initialize_git: z.boolean().default(false).describe("Include Git initialization in the plan."),
158
+ install_dependencies: z.boolean().default(false).describe("Include dependency installation in the plan."),
159
+ }),
160
+ annotations: { readOnlyHint: true },
161
+ }, async ({ directory, initialize_git, install_dependencies, ...options }) => withTemplate(async (source) => {
162
+ try {
163
+ const target = await workspaceDestination(directory);
164
+ const plan = await planProject(source, options, target, {
165
+ git: initialize_git,
166
+ install: install_dependencies,
167
+ });
168
+ return result({
169
+ valid: true,
170
+ directory: target,
171
+ destinationMustBeEmpty: true,
172
+ starterRef: plan.provenance.starterRef,
173
+ starterCommit: plan.provenance.starterCommit,
174
+ stacks: plan.apps.map(({ slot, id, name }) => ({ slot, id, name })),
175
+ modules: plan.modules.map(({ id, option, optionName }) => ({ id, option, name: optionName })),
176
+ fileCount: plan.files.length,
177
+ editCount: plan.edits.length,
178
+ environmentFiles: plan.envFiles.map(({ from, to }) => ({ source: from, destination: to })),
179
+ toolchains: plan.toolchains.map(({ directory: appDirectory, app, toolchain }) => ({
180
+ directory: appDirectory,
181
+ app,
182
+ name: toolchain.name,
183
+ install: toolchain.install,
184
+ })),
185
+ actions: describePlan(plan),
186
+ });
187
+ }
188
+ catch (error) {
189
+ return result({ valid: false, error: error instanceof Error ? error.message : String(error) });
190
+ }
191
+ }));
192
+ server.registerTool("create_project", {
193
+ description: "Generate a Clubedge Starter project under the MCP server's working directory. The destination must be empty or absent. Git initialization and dependency installation are opt-in.",
194
+ inputSchema: z.object({
195
+ directory: z.string().min(1).describe("Destination path relative to the MCP server's working directory."),
196
+ ...selectionSchema,
197
+ initialize_git: z.boolean().default(false).describe("Initialize a Git repository after generating files."),
198
+ install_dependencies: z.boolean().default(false).describe("Run pnpm install and declared secondary toolchain installs after generating files."),
199
+ }),
200
+ }, async ({ directory, initialize_git, install_dependencies, ...options }) => withTemplate(async (source) => {
201
+ const target = await workspaceDestination(directory);
202
+ const plan = await planProject(source, options, target, { git: initialize_git, install: install_dependencies });
203
+ await executePlan(plan, source);
204
+ const warnings = [];
205
+ if (initialize_git) {
206
+ try {
207
+ await initializeGit(target, mcpStdio);
208
+ }
209
+ catch (error) {
210
+ warnings.push(`Project created, but Git initialization failed: ${error instanceof Error ? error.message : String(error)}`);
211
+ }
212
+ }
213
+ if (install_dependencies) {
214
+ const pnpm = await resolvePnpm();
215
+ if (!pnpm) {
216
+ warnings.push("Project created, but pnpm was not found. Install pnpm and run it in the project directory.");
217
+ }
218
+ else {
219
+ try {
220
+ await installDependencies(target, pnpm, [], mcpStdio);
221
+ for (const { directory: appDirectory, app, toolchain } of plan.toolchains) {
222
+ const [check, ...checkArgs] = toolchain.check;
223
+ if (!(await commandSucceeds(check, checkArgs))) {
224
+ warnings.push(`${toolchain.name} is required to install ${app} dependencies: ${toolchain.url}`);
225
+ continue;
226
+ }
227
+ const [command, ...args] = toolchain.install;
228
+ try {
229
+ await run(command, args, resolve(target, appDirectory), mcpStdio);
230
+ }
231
+ catch (error) {
232
+ warnings.push(`${app} dependencies did not install: ${error instanceof Error ? error.message : String(error)}`);
233
+ }
234
+ }
235
+ }
236
+ catch (error) {
237
+ warnings.push(`Project created, but dependency installation did not complete: ${error instanceof Error ? error.message : String(error)}`);
238
+ }
239
+ }
240
+ }
241
+ return result({
242
+ created: true,
243
+ directory: target,
244
+ starterRef: plan.provenance.starterRef,
245
+ stacks: plan.apps.map(({ slot, id, name }) => ({ slot, id, name })),
246
+ modules: plan.modules.map(({ id, option, optionName }) => ({ id, option, name: optionName })),
247
+ actions: describePlan(plan),
248
+ warnings,
249
+ });
250
+ }));
251
+ return server;
252
+ }
253
+ serveStdio(createServer);
package/dist/steps/git.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { run } from "../utils/process.js";
2
- export async function initializeGit(directory) {
2
+ export async function initializeGit(directory, stdio) {
3
3
  try {
4
- await run("git", ["init", "-b", "main"], directory);
4
+ await run("git", ["init", "-b", "main"], directory, stdio);
5
5
  }
6
6
  catch {
7
7
  // Git before 2.28 has no -b option.
8
- await run("git", ["init"], directory);
9
- await run("git", ["branch", "-M", "main"], directory);
8
+ await run("git", ["init"], directory, stdio);
9
+ await run("git", ["branch", "-M", "main"], directory, stdio);
10
10
  }
11
11
  }
@@ -13,7 +13,7 @@ export function detectLauncher(userAgent = process.env.npm_config_user_agent) {
13
13
  const name = userAgent?.split("/")[0];
14
14
  return name && ["npm", "pnpm", "yarn", "bun"].includes(name) ? name : null;
15
15
  }
16
- export async function installDependencies(directory, pnpm, args = []) {
16
+ export async function installDependencies(directory, pnpm, args = [], stdio) {
17
17
  const [command, ...prefix] = pnpm;
18
- await run(command, [...prefix, "install", ...args], directory);
18
+ await run(command, [...prefix, "install", ...args], directory, stdio);
19
19
  }
@@ -16,9 +16,9 @@ export function spawnCommand(command, args, options, shell = useShell) {
16
16
  return spawn([command, ...args].map(quoteForCmd).join(" "), { ...options, shell: true });
17
17
  }
18
18
  /** Runs a command with inherited output. Rejects on a non-zero exit. */
19
- export async function run(command, args, cwd) {
19
+ export async function run(command, args, cwd, stdio = "inherit") {
20
20
  await new Promise((resolve, reject) => {
21
- const child = spawnCommand(command, args, { cwd, stdio: "inherit" });
21
+ const child = spawnCommand(command, args, { cwd, stdio });
22
22
  child.once("error", reject);
23
23
  child.once("exit", (code, signal) => {
24
24
  if (code === 0)
package/package.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "@clubedge/create-clubedge-app",
3
- "version": "0.35.0",
4
- "description": "Create a React app with Next.js, TanStack Start, or Vite and a separate API from the Clubedge Starter.",
3
+ "version": "0.37.0",
4
+ "description": "Create React apps from Clubedge Starter with a CLI or local MCP server.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.9.0",
7
7
  "bin": {
8
- "create-clubedge-app": "./bin/create.mjs"
8
+ "create-clubedge-app": "./bin/create.mjs",
9
+ "create-clubedge-app-mcp": "./bin/mcp.mjs"
9
10
  },
10
11
  "files": [
11
12
  "bin/",
12
13
  "dist/",
13
14
  "template/",
14
15
  "template.lock.json",
16
+ "skills/",
15
17
  "README.md",
16
18
  "CHANGELOG.md",
17
19
  "LICENSE",
@@ -22,8 +24,8 @@
22
24
  },
23
25
  "clubedge": {
24
26
  "starterRepository": "Clubedge/clubedge-starter",
25
- "starterRef": "v0.24.0",
26
- "starterCommit": "3aaca476bf5ff1db9b5fc4080f7a8a8006436998"
27
+ "starterRef": "v0.24.1",
28
+ "starterCommit": "3e8b5ba8910767f27336cf253fbfe92a9bb42875"
27
29
  },
28
30
  "license": "Apache-2.0",
29
31
  "repository": {
@@ -40,7 +42,10 @@
40
42
  "nextjs",
41
43
  "tanstack-start",
42
44
  "starter",
43
- "scaffolding"
45
+ "scaffolding",
46
+ "mcp",
47
+ "model-context-protocol",
48
+ "ai-agent"
44
49
  ],
45
50
  "scripts": {
46
51
  "build": "tsc -p tsconfig.build.json",
@@ -52,15 +57,17 @@
52
57
  "release": "node scripts/release.mjs"
53
58
  },
54
59
  "dependencies": {
60
+ "@modelcontextprotocol/server": "^2.0.0",
55
61
  "@clack/prompts": "^1.8.1",
56
- "giget": "^2.0.0",
62
+ "giget": "^3.3.1",
57
63
  "node-diff3": "^3.2.1",
58
- "ssh2": "^1.17.0"
64
+ "ssh2": "^1.17.0",
65
+ "zod": "^4.2.0"
59
66
  },
60
67
  "devDependencies": {
61
- "@types/node": "^22.20.5",
68
+ "@types/node": "^26.6.5",
62
69
  "@types/ssh2": "^1.15.6",
63
70
  "typescript": "^5.9.3",
64
- "vitest": "^5.0.3"
71
+ "vitest": "^5.0.5"
65
72
  }
66
73
  }
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: clubedge-starter
3
+ description: Create and evolve applications based on Clubedge Starter with the create-clubedge-app CLI or MCP server. Use when asked to scaffold a Clubedge app, choose its framework or modules, validate a configuration, or change Starter modules in an existing generated project.
4
+ compatibility: Requires Node.js 22.12+ and pnpm 10.9+ to create projects. The MCP workflow requires the Clubedge Starter MCP server to be configured.
5
+ metadata:
6
+ author: Clubedge
7
+ repository: https://github.com/Clubedge/create-clubedge-app
8
+ ---
9
+
10
+ # Clubedge Starter
11
+
12
+ Create and manage Clubedge Starter projects through the supported CLI or MCP tools. Follow the user's requested stack and services; do not add unrequested modules.
13
+
14
+ ## Choose the interface
15
+
16
+ - If the `clubedge-starter` MCP server is available, call `get_starter_capabilities` first. Treat its response as authoritative for framework/API choices, module options, defaults, and requirements.
17
+ - Before scaffolding through MCP, call `plan_project` with the destination `directory` and requested `stacks` and `modules`. It validates compatibility and previews the files and setup actions without writing to disk. Resolve invalid combinations and any open choices before creating the project. Use `validate_project_config` when only a compatibility check is needed and no destination plan is useful.
18
+ - Use `create_project` to scaffold through MCP. Give `directory` as a relative path beneath the MCP server's working directory. Set `initialize_git` and `install_dependencies` to `true` only when appropriate for the user's request; both default to `false`.
19
+ - If MCP is unavailable, use the CLI. Prefer the installed project-local executable when present; otherwise use `pnpm dlx @clubedge/create-clubedge-app <directory> --yes` and provide the requested options as CLI flags.
20
+ - Use the CLI's `add`, `remove`, or `upgrade` commands for changes to an existing generated project. Do not reproduce those changes by manually copying Starter files.
21
+
22
+ ## Scaffold a project
23
+
24
+ For a new app:
25
+
26
+ 1. Identify the requested framework and API shape. Full-stack choices such as Next.js and TanStack Start do not use a separate API; Vite can be paired with Hono or FastAPI.
27
+ 2. Choose only the requested modules. Check the live capabilities or CLI `--help` rather than relying on a hard-coded list, since each CLI release is pinned to a specific Starter release.
28
+ 3. Validate that module requirements are met. For example, module compatibility can depend on the selected framework or another module.
29
+ 4. Preview with the CLI's `--dry-run` when the requested changes or destination are unclear. For MCP, describe the exact target directory and options before creating if the user has not made them clear.
30
+ 5. Create only in a new or empty directory. The MCP tool only accepts paths under its configured working directory. The CLI also refuses to overwrite an existing project.
31
+ 6. Report the target path, selected stacks and modules, and any actions that still need to be run, such as dependency installation or starting local infrastructure.
32
+
33
+ CLI example:
34
+
35
+ ```sh
36
+ pnpm dlx @clubedge/create-clubedge-app my-app --yes --framework next --auth better-auth --infra local
37
+ ```
38
+
39
+ ## Change an existing project
40
+
41
+ Run the CLI from the generated project directory:
42
+
43
+ ```sh
44
+ pnpm clubedge add payments stripe
45
+ pnpm clubedge remove storage
46
+ pnpm clubedge upgrade
47
+ ```
48
+
49
+ Check the project's Git state first. These commands preserve user edits and can report merge conflicts that need review. After a change, follow the CLI's instructions and run relevant checks, migrations, or service setup for the modules involved.
50
+
51
+ ## Project guidance
52
+
53
+ - Keep secrets in the project's environment file and never print or commit secret values.
54
+ - Do not claim a project was created, installed, migrated, or deployed until the corresponding action succeeded.
55
+ - If an install or provider setup fails, report the exact failed step and the next command needed to resume; do not silently present a partially configured project as ready.
56
+ - Consult the generated `SETUP.md` and the official docs at https://starter.clubedge.live/docs for provider-specific configuration.
@@ -21,9 +21,10 @@ env:
21
21
  # clubedge:end
22
22
  jobs:
23
23
  preview:
24
- # Pull requests from forks get no secrets, so they get no preview.
24
+ # Forks and Dependabot PRs get no secrets, so they cannot provision a database preview.
25
25
  if: >-
26
26
  github.event.action != 'closed' && vars.NEON_PROJECT_ID != '' &&
27
+ github.event.pull_request.user.login != 'dependabot[bot]' &&
27
28
  github.event.pull_request.head.repo.full_name == github.repository
28
29
  runs-on: ubuntu-24.04
29
30
  env:
@@ -181,6 +182,7 @@ jobs:
181
182
  cleanup:
182
183
  if: >-
183
184
  github.event.action == 'closed' && vars.NEON_PROJECT_ID != '' &&
185
+ github.event.pull_request.user.login != 'dependabot[bot]' &&
184
186
  github.event.pull_request.head.repo.full_name == github.repository
185
187
  runs-on: ubuntu-24.04
186
188
  steps:
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/api",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/api-fastapi",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "clubedge-api"
3
- version = "0.24.0"
3
+ version = "0.24.1"
4
4
  description = "The project's API, implementing packages/contract/openapi.yaml."
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
@@ -103,11 +103,11 @@ wheels = [
103
103
 
104
104
  [[package]]
105
105
  name = "certifi"
106
- version = "2026.10.18"
106
+ version = "2026.10.19"
107
107
  source = { registry = "https://pypi.org/simple" }
108
- sdist = { url = "https://files.pythonhosted.org/packages/7e/eb/a2aa1ac3ea30dd3f30ca57f40232138b6b0040719316ecb85daf61167771/certifi-2026.10.18.tar.gz", hash = "sha256:b9995d97b64d0431a5af837d8ca8cd6238104d1f319f04bd93ea43124d6c861e", size = 135227, upload-time = "2026-10-18T16:21:07.655Z" }
108
+ sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" }
109
109
  wheels = [
110
- { url = "https://files.pythonhosted.org/packages/08/ba/95dd616042484f7c83a06974388b37b837732740ee70bc5dc715b5fcd55a/certifi-2026.10.18-py3-none-any.whl", hash = "sha256:73191c47aaa79da394e8fee2dbc2ec827432cab56311122c9e47abf79e390f62", size = 134093, upload-time = "2026-10-18T16:21:06.276Z" },
110
+ { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" },
111
111
  ]
112
112
 
113
113
  [[package]]
@@ -121,7 +121,7 @@ wheels = [
121
121
 
122
122
  [[package]]
123
123
  name = "clubedge-api"
124
- version = "0.24.0"
124
+ version = "0.24.1"
125
125
  source = { editable = "." }
126
126
  dependencies = [
127
127
  { name = "fastapi" },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/start",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/vite",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/web",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clubedge-starter-v1",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "packageManager": "pnpm@10.9.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/auth",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/auth-appwrite",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Appwrite provider for @clubedge/auth, run from the server with an API key and session cookies.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/auth-better-auth",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Better Auth adapter for @clubedge/auth, storing users and sessions in your own PostgreSQL.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/auth-firebase",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Firebase Authentication provider for @clubedge/auth, run from the server with session cookies.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/auth-supabase",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Supabase Auth adapter for @clubedge/auth.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/cache",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/cache-redis",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Redis rate limiter and cache for @clubedge/cache.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/contract",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/core",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/db",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/email",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Email interface, console and in-memory providers, and message templates.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/email-resend",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Resend provider for @clubedge/email, through Resend's HTTP API.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/email-smtp",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "SMTP provider for @clubedge/email, for any SMTP server.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/jobs",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Background jobs interface: job definitions, a queue contract, and an in-memory queue.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/jobs-bullmq",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "BullMQ provider for @clubedge/jobs: a job queue stored in Redis.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/jobs-inngest",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Inngest provider for @clubedge/jobs: jobs run by Inngest, which calls the app for each one.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/jobs-pg-boss",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "pg-boss provider for @clubedge/jobs: a job queue stored in the project's PostgreSQL.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/local-db",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "PostgreSQL for development without Docker: PGlite served over the PostgreSQL protocol.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/observability",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Observability interface: error reports and spans for @clubedge/core's telemetry hooks, and an in-memory provider for tests.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/observability-otel",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "OpenTelemetry provider for @clubedge/observability: traces and error logs exported over OTLP to any collector.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/observability-sentry",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Sentry provider for @clubedge/observability: server and browser errors, traces, and source map uploads.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/payments",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Provider-neutral payments: the PaymentProvider interface, billing logic, and subscription tables.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/payments-stripe",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Stripe provider for @clubedge/payments: Checkout, the customer portal, and verified webhooks.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/storage",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/storage-s3",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "S3-compatible adapter for @clubedge/storage.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/storage-supabase",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Supabase Storage adapter for @clubedge/storage.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/ui",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "repository": "Clubedge/clubedge-starter",
3
- "ref": "v0.24.0",
4
- "commit": "3aaca476bf5ff1db9b5fc4080f7a8a8006436998",
3
+ "ref": "v0.24.1",
4
+ "commit": "3e8b5ba8910767f27336cf253fbfe92a9bb42875",
5
5
  "files": [
6
6
  ".dockerignore",
7
7
  ".env.example",