@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.
- package/CHANGELOG.md +12 -0
- package/README.md +67 -10
- package/bin/mcp.mjs +2 -0
- package/dist/mcp-server.js +253 -0
- package/dist/steps/git.js +4 -4
- package/dist/steps/install.js +2 -2
- package/dist/utils/process.js +2 -2
- package/package.json +17 -10
- package/skills/clubedge-starter/SKILL.md +56 -0
- package/template/.github/workflows/preview.yml +3 -1
- package/template/apps/api/package.json +1 -1
- package/template/apps/api-fastapi/package.json +1 -1
- package/template/apps/api-fastapi/pyproject.toml +1 -1
- package/template/apps/api-fastapi/uv.lock +4 -4
- package/template/apps/start/package.json +1 -1
- package/template/apps/vite/package.json +1 -1
- package/template/apps/web/package.json +1 -1
- package/template/package.json +1 -1
- package/template/packages/auth/package.json +1 -1
- package/template/packages/auth-appwrite/package.json +1 -1
- package/template/packages/auth-better-auth/package.json +1 -1
- package/template/packages/auth-firebase/package.json +1 -1
- package/template/packages/auth-supabase/package.json +1 -1
- package/template/packages/cache/package.json +1 -1
- package/template/packages/cache-redis/package.json +1 -1
- package/template/packages/contract/package.json +1 -1
- package/template/packages/core/package.json +1 -1
- package/template/packages/db/package.json +1 -1
- package/template/packages/email/package.json +1 -1
- package/template/packages/email-resend/package.json +1 -1
- package/template/packages/email-smtp/package.json +1 -1
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/jobs-bullmq/package.json +1 -1
- package/template/packages/jobs-inngest/package.json +1 -1
- package/template/packages/jobs-pg-boss/package.json +1 -1
- package/template/packages/local-db/package.json +1 -1
- package/template/packages/observability/package.json +1 -1
- package/template/packages/observability-otel/package.json +1 -1
- package/template/packages/observability-sentry/package.json +1 -1
- package/template/packages/payments/package.json +1 -1
- package/template/packages/payments-stripe/package.json +1 -1
- package/template/packages/storage/package.json +1 -1
- package/template/packages/storage-s3/package.json +1 -1
- package/template/packages/storage-supabase/package.json +1 -1
- package/template/packages/ui/package.json +1 -1
- 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.
|
|
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.
|
|
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.
|
|
340
|
+
create-clubedge-app v0.37.0
|
|
286
341
|
|
|
|
287
342
|
v
|
|
288
|
-
Clubedge Starter v0.24.
|
|
289
|
-
commit
|
|
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.
|
|
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.
|
|
301
|
-
`
|
|
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.
|
|
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.
|
|
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,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
|
}
|
package/dist/steps/install.js
CHANGED
|
@@ -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
|
}
|
package/dist/utils/process.js
CHANGED
|
@@ -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
|
|
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.
|
|
4
|
-
"description": "Create
|
|
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.
|
|
26
|
-
"starterCommit": "
|
|
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": "^
|
|
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": "^
|
|
68
|
+
"@types/node": "^26.6.5",
|
|
62
69
|
"@types/ssh2": "^1.15.6",
|
|
63
70
|
"typescript": "^5.9.3",
|
|
64
|
-
"vitest": "^5.0.
|
|
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
|
-
#
|
|
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:
|
|
@@ -103,11 +103,11 @@ wheels = [
|
|
|
103
103
|
|
|
104
104
|
[[package]]
|
|
105
105
|
name = "certifi"
|
|
106
|
-
version = "2026.10.
|
|
106
|
+
version = "2026.10.19"
|
|
107
107
|
source = { registry = "https://pypi.org/simple" }
|
|
108
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
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/
|
|
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.
|
|
124
|
+
version = "0.24.1"
|
|
125
125
|
source = { editable = "." }
|
|
126
126
|
dependencies = [
|
|
127
127
|
{ name = "fastapi" },
|
package/template/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clubedge/observability",
|
|
3
|
-
"version": "0.24.
|
|
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",
|
package/template.lock.json
CHANGED