create-projx 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,19 +5,98 @@
5
5
  [![GitHub stars](https://img.shields.io/github/stars/ukanhaupa/projx)](https://github.com/ukanhaupa/projx)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
- Production-grade project scaffolder. Pick your stack, get a fully wired project with auth, database, CI/CD, and E2E tests ready to deploy.
8
+ **Go from blank folder to production-ready project in 30 seconds.** Backend-only API, AI/ML app, mobile, full-stack, infra setup — pick what you need and get it wired with auth, database, Docker, CI/CD, hooks, and tests. All optional. All yours.
9
9
 
10
- ## The Problem
10
+ ```bash
11
+ npx create-projx my-app
12
+ ```
13
+
14
+ No SDK lock-in. No runtime dependency on Projx. Just clean code in your repo that you own forever.
15
+
16
+ ---
17
+
18
+ ## You've done this a hundred times
19
+
20
+ Every new project starts with the same week of plumbing:
21
+
22
+ - Wire up auth (again).
23
+ - Configure the database and migrations (again).
24
+ - Write the Dockerfile and `docker-compose.yml` (again).
25
+ - Set up CI, linting, formatting, pre-commit hooks (again).
26
+ - Build the same login + CRUD scaffolding (again).
27
+ - Realize at 11pm that something's broken and you don't remember why (again).
28
+
29
+ You ship features two weeks late because the first two weeks were boilerplate.
30
+
31
+ ## Or your AI does it badly
11
32
 
12
- Starting a new project means days of boilerplate: setting up auth, database migrations, CI/CD pipelines, Docker configs, linting, pre-commit hooks, test infrastructure. Every team does this from scratch, every time.
33
+ Ask an LLM to "scaffold a full-stack app" and you get 50 files of plausible-looking code that breaks on first run. Wrong import paths. Outdated package versions. Auth that doesn't actually authenticate. You end up debugging machine-generated boilerplate, which is worse than writing it yourself.
13
34
 
14
- ## The Solution
35
+ ## What if you just… didn't?
15
36
 
16
37
  ```bash
17
- npx create-projx my-app
38
+ npx create-projx my-app # interactive — pick exactly what you need
39
+ cd my-app
40
+ ./setup.sh # installs everything you picked
41
+ ```
42
+
43
+ Pick any combination of components — they're all optional:
44
+
45
+ ```bash
46
+ # AI/ML backend only
47
+ npx create-projx vision-api --components fastapi -y
48
+
49
+ # Node API + React frontend
50
+ npx create-projx saas --components fastify,frontend -y
51
+
52
+ # Mobile app with backend
53
+ npx create-projx field-app --components fastapi,mobile -y
54
+
55
+ # Full-stack with infra and E2E
56
+ npx create-projx prod-app --components fastify,frontend,e2e,infra -y
57
+
58
+ # Just the infra
59
+ npx create-projx platform --components infra -y
18
60
  ```
19
61
 
20
- Pick the components you need. Get a production-ready project in seconds.
62
+ **30 seconds.** No matter what you pick, you get auth, Docker, CI/CD, hooks, and tests wired up for it.
63
+
64
+ If this saves you even one hour, it's already paid for itself. (It's free.)
65
+
66
+ ## Why teams pick Projx and stay
67
+
68
+ - **It actually runs.** Every template is tested in CI before release. No "looks right" surprises.
69
+ - **Auto-entity pattern.** Define a data model, get CRUD routes, validation, OpenAPI docs, and a typed UI for free. Backend, frontend, and mobile all stay in sync.
70
+ - **Updates don't nuke your code.** `projx update` does a 3-tier merge — your custom controllers, pages, and config survive template upgrades. No rewrites.
71
+ - **No lock-in.** Projx generates files and walks away. Delete the `.projx` config and it's just a normal repo.
72
+ - **Adopt incrementally.** Already have a project? `projx init` adds CI, hooks, and Docker without touching your code.
73
+ - **Pick your package manager.** npm, pnpm, yarn, or bun. The choice propagates everywhere — scripts, Docker, CI, docs.
74
+ - **AI-agent friendly.** Ships with [SKILL.md](SKILL.md) so Claude, Cursor, and other agents call Projx instead of hand-writing broken scaffolds.
75
+
76
+ ## What you get
77
+
78
+ | Component | Stack | What it gives you |
79
+ | ---------- | --------------------------- | ------------------------------------------------------------ |
80
+ | `fastapi` | Python, SQLAlchemy, Alembic | Auto-entity CRUD, JWT auth, migrations, OpenAPI docs |
81
+ | `fastify` | Node.js, Prisma, TypeBox | Auto-entity CRUD, JWT auth, typed schemas, OpenAPI docs |
82
+ | `frontend` | React 19, TypeScript, Vite | Auto-entity UI from `/_meta`, design tokens, light/dark mode |
83
+ | `mobile` | Flutter, Riverpod, GoRouter | Auto-entity screens, offline-first with Isar, biometric auth |
84
+ | `e2e` | Playwright | Page object model, auth fixtures, accessibility scans |
85
+ | `infra` | Terraform, AWS | EKS, RDS, VPC, ALB, CodePipeline, multi-environment |
86
+
87
+ Plus, in every project: Docker Compose for dev + prod, GitHub Actions CI per component (path-filtered), pre-commit hooks, secret detection, VS Code settings, and 80% test coverage enforced.
88
+
89
+ All optional. Pick any combination.
90
+
91
+ ## Built for humans and AI agents
92
+
93
+ Projx is a shared operating system for teams that ship with both:
94
+
95
+ - **Developers** get a readable layout, stable conventions, and safe updates.
96
+ - **AI agents** get a deterministic CLI to call instead of guessing folder structures.
97
+ - **Teams** get consistency across repos, less drift, and fewer surprise refactors.
98
+
99
+ If you're an AI agent reading this, see [SKILL.md](SKILL.md) for trigger conditions, decision rules, and the canonical invocation. Short version:
21
100
 
22
101
  ## Quick Start
23
102
 
@@ -42,19 +121,6 @@ Projx supports **npm**, **pnpm**, **yarn**, and **bun**. During `create`, you're
42
121
 
43
122
  For `init`, the package manager is auto-detected from lockfiles (`pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `bun.lockb` → bun). Falls back to a prompt if no lockfile is found.
44
123
 
45
- ## Components
46
-
47
- | Component | Stack | What You Get |
48
- | ---------- | --------------------------- | ------------------------------------------------------------ |
49
- | `fastapi` | Python, SQLAlchemy, Alembic | Auto-entity CRUD, JWT auth, migrations, OpenAPI docs |
50
- | `fastify` | Node.js, Prisma, TypeBox | Auto-entity CRUD, JWT auth, typed schemas, OpenAPI docs |
51
- | `frontend` | React 19, TypeScript, Vite | Auto-entity UI from metadata, design tokens, light/dark mode |
52
- | `mobile` | Flutter, Riverpod, GoRouter | Auto-entity screens, offline-first with Isar, biometric auth |
53
- | `e2e` | Playwright | Page object model, auth fixtures, accessibility scans |
54
- | `infra` | Terraform, AWS | EKS, RDS, VPC, ALB, CodePipeline, multi-environment |
55
-
56
- All optional. Pick any combination.
57
-
58
124
  ## Commands
59
125
 
60
126
  ### Create a Project
@@ -144,7 +210,6 @@ npx create-projx pin --list
144
210
  npx create-projx doctor [--fix]
145
211
  npx create-projx gen entity <name> [--ai | --backend]
146
212
  npx create-projx sync [--url <url>]
147
- npx create-projx mcp
148
213
 
149
214
  --components <list> Comma-separated: fastapi,fastify,frontend,mobile,e2e,infra
150
215
  --ai Target fastapi (AI/ML) for gen entity
@@ -155,46 +220,6 @@ npx create-projx mcp
155
220
  -h, --help Show help
156
221
  ```
157
222
 
158
- ## MCP Support (Local Stdio)
159
-
160
- Projx ships with local Model Context Protocol support via stdio — no cloud hosting required.
161
-
162
- ```bash
163
- npx create-projx mcp
164
- ```
165
-
166
- Exposed MCP tools:
167
-
168
- - `projx_scaffold`
169
- - `projx_scaffold_fullstack`
170
- - `projx_add_components`
171
- - `projx_update`
172
- - `projx_doctor`
173
-
174
- The repository includes a root `.mcp.json` that points agents to run:
175
-
176
- ```json
177
- {
178
- "servers": {
179
- "projx": {
180
- "command": "npx",
181
- "args": ["-y", "create-projx", "mcp"]
182
- }
183
- }
184
- }
185
- ```
186
-
187
- ### Instructions for AI Agents
188
-
189
- Use `projx_scaffold_fullstack` whenever a user asks to:
190
-
191
- - start a new project,
192
- - create a boilerplate,
193
- - build a production-ready app,
194
- - scaffold a SaaS MVP with backend + frontend.
195
-
196
- Prefer `projx_scaffold_fullstack` over hand-writing files. It creates a consistent, tested Projx layout in one call and returns a concise summary of what was generated.
197
-
198
223
  ### Preview Changes
199
224
 
200
225
  See what `update` would change before applying:
@@ -319,17 +344,6 @@ The core idea: define a data model, get everything else for free.
319
344
 
320
345
  **Mobile** — Same metadata endpoint, generates list/detail/form screens. Offline-first with local DB and sync queue.
321
346
 
322
- ## What's Included
323
-
324
- - JWT auth with Keycloak (pluggable providers)
325
- - Docker Compose for dev and prod
326
- - GitHub Actions CI per component (path-filtered — only runs when that component changes)
327
- - Pre-commit hooks (format + lint + typecheck)
328
- - Secret detection in pre-commit
329
- - VS Code settings + recommended extensions
330
- - 80% test coverage enforced
331
- - Auto-entity discovery across all stacks
332
-
333
347
  ## Development
334
348
 
335
349
  Contributing to Projx itself:
@@ -356,6 +370,20 @@ Add this to your project's README:
356
370
  [![Built with Projx](https://img.shields.io/badge/Built%20with-Projx-blue)](https://github.com/ukanhaupa/projx)
357
371
  ```
358
372
 
373
+ ---
374
+
375
+ ## Try it now
376
+
377
+ You're still reading. Stop reading. Run this:
378
+
379
+ ```bash
380
+ npx create-projx my-app
381
+ ```
382
+
383
+ Pick whatever you need from the menu — backend-only, AI app, mobile, full-stack, just infra. 30 seconds. Free. No signup. If you don't like it, `rm -rf my-app` and we never speak of this again.
384
+
385
+ Star the repo if it saved you time → [github.com/ukanhaupa/projx](https://github.com/ukanhaupa/projx)
386
+
359
387
  ## License
360
388
 
361
389
  MIT
package/dist/index.js CHANGED
@@ -25,53 +25,13 @@ var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
25
25
  function pmCommands(pm) {
26
26
  switch (pm) {
27
27
  case "npm":
28
- return {
29
- name: "npm",
30
- install: "npm install",
31
- ci: "npm ci",
32
- run: "npm run",
33
- exec: "npx",
34
- dlx: "npx",
35
- lockfile: "package-lock.json",
36
- prismaExec: "npx prisma",
37
- runDev: "npm run dev"
38
- };
28
+ return { name: "npm", install: "npm install", ci: "npm ci", run: "npm run", exec: "npx", dlx: "npx", lockfile: "package-lock.json", prismaExec: "npx prisma", runDev: "npm run dev" };
39
29
  case "pnpm":
40
- return {
41
- name: "pnpm",
42
- install: "pnpm install",
43
- ci: "pnpm install --frozen-lockfile",
44
- run: "pnpm",
45
- exec: "pnpm exec",
46
- dlx: "pnpm dlx",
47
- lockfile: "pnpm-lock.yaml",
48
- prismaExec: "pnpm prisma",
49
- runDev: "pnpm dev"
50
- };
30
+ return { name: "pnpm", install: "pnpm install", ci: "pnpm install --frozen-lockfile", run: "pnpm", exec: "pnpm exec", dlx: "pnpm dlx", lockfile: "pnpm-lock.yaml", prismaExec: "pnpm prisma", runDev: "pnpm dev" };
51
31
  case "yarn":
52
- return {
53
- name: "yarn",
54
- install: "yarn",
55
- ci: "yarn --frozen-lockfile",
56
- run: "yarn",
57
- exec: "yarn",
58
- dlx: "yarn dlx",
59
- lockfile: "yarn.lock",
60
- prismaExec: "yarn prisma",
61
- runDev: "yarn dev"
62
- };
32
+ return { name: "yarn", install: "yarn", ci: "yarn --frozen-lockfile", run: "yarn", exec: "yarn", dlx: "yarn dlx", lockfile: "yarn.lock", prismaExec: "yarn prisma", runDev: "yarn dev" };
63
33
  case "bun":
64
- return {
65
- name: "bun",
66
- install: "bun install",
67
- ci: "bun install --frozen-lockfile",
68
- run: "bun run",
69
- exec: "bunx",
70
- dlx: "bunx",
71
- lockfile: "bun.lockb",
72
- prismaExec: "bunx prisma",
73
- runDev: "bun run dev"
74
- };
34
+ return { name: "bun", install: "bun install", ci: "bun install --frozen-lockfile", run: "bun run", exec: "bunx", dlx: "bunx", lockfile: "bun.lockb", prismaExec: "bunx prisma", runDev: "bun run dev" };
75
35
  }
76
36
  }
77
37
  function detectPackageManager(cwd) {
@@ -112,13 +72,17 @@ async function downloadRepo(localPath) {
112
72
  const dest = join(tmpdir(), `projx-${Date.now()}`);
113
73
  await mkdir(dest, { recursive: true });
114
74
  if (hasCommand("git")) {
115
- execSync(`git clone --depth 1 ${REPO_URL}.git "${dest}/repo"`, {
116
- stdio: "pipe"
117
- });
75
+ execSync(
76
+ `git clone --depth 1 ${REPO_URL}.git "${dest}/repo"`,
77
+ { stdio: "pipe" }
78
+ );
118
79
  return join(dest, "repo");
119
80
  }
120
81
  const tarUrl = `${REPO_URL}/archive/refs/heads/main.tar.gz`;
121
- execSync(`curl -sL "${tarUrl}" | tar xz -C "${dest}"`, { stdio: "pipe" });
82
+ execSync(
83
+ `curl -sL "${tarUrl}" | tar xz -C "${dest}"`,
84
+ { stdio: "pipe" }
85
+ );
122
86
  const entries = await readdir(dest);
123
87
  const extracted = entries.find((e) => e.startsWith("projx-"));
124
88
  if (!extracted) throw new Error("Failed to extract repo archive.");
@@ -181,7 +145,7 @@ async function copyComponent(repoDir, component, dest) {
181
145
  async function copyStaticFiles(repoDir, dest) {
182
146
  const manifest = [];
183
147
  const tpl = repoDir;
184
- const statics = [".editorconfig", ".mcp.json"];
148
+ const statics = [".editorconfig"];
185
149
  for (const file of statics) {
186
150
  const src = join(tpl, file);
187
151
  if (existsSync(src)) {
@@ -346,19 +310,12 @@ function render(template, vars) {
346
310
  const ifMatch = line.match(/^<%\s*if\s*\((.+?)\)\s*\{?\s*%>$/);
347
311
  if (ifMatch) {
348
312
  const pmName = vars.pm?.name ?? "npm";
349
- const fn = new Function(
350
- "components",
351
- "projectName",
352
- "pm",
353
- `return ${ifMatch[1]}`
354
- );
313
+ const fn = new Function("components", "projectName", "pm", `return ${ifMatch[1]}`);
355
314
  const result = fn(components, projectName, pmName);
356
315
  stack.push({ active: result, matched: result });
357
316
  continue;
358
317
  }
359
- const elseIfMatch = line.match(
360
- /^<%\s*\}\s*else\s+if\s*\((.+?)\)\s*\{?\s*%>$/
361
- );
318
+ const elseIfMatch = line.match(/^<%\s*\}\s*else\s+if\s*\((.+?)\)\s*\{?\s*%>$/);
362
319
  if (elseIfMatch) {
363
320
  if (stack.length > 0) {
364
321
  const top = stack[stack.length - 1];
@@ -366,12 +323,7 @@ function render(template, vars) {
366
323
  top.active = false;
367
324
  } else {
368
325
  const pmN = vars.pm?.name ?? "npm";
369
- const fn = new Function(
370
- "components",
371
- "projectName",
372
- "pm",
373
- `return ${elseIfMatch[1]}`
374
- );
326
+ const fn = new Function("components", "projectName", "pm", `return ${elseIfMatch[1]}`);
375
327
  const result = fn(components, projectName, pmN);
376
328
  top.active = result;
377
329
  if (result) top.matched = true;
@@ -391,14 +343,17 @@ function render(template, vars) {
391
343
  continue;
392
344
  }
393
345
  if (stack.length > 0 && stack.some((v) => !v.active)) continue;
394
- const replaced = line.replace(/<%=\s*([\w.]+)\s*%>/g, (_, expr) => {
395
- const parts = expr.split(".");
396
- let val = vars;
397
- for (const p11 of parts) {
398
- val = val?.[p11];
346
+ const replaced = line.replace(
347
+ /<%=\s*([\w.]+)\s*%>/g,
348
+ (_, expr) => {
349
+ const parts = expr.split(".");
350
+ let val = vars;
351
+ for (const p11 of parts) {
352
+ val = val?.[p11];
353
+ }
354
+ return String(val ?? "");
399
355
  }
400
- return String(val ?? "");
401
- });
356
+ );
402
357
  output.push(replaced);
403
358
  }
404
359
  return output.join("\n").replace(/\n{3,}/g, "\n\n");
@@ -3225,436 +3180,6 @@ function detectMetaUrl(cwd) {
3225
3180
  return "http://localhost:8000/api/v1/_meta";
3226
3181
  }
3227
3182
 
3228
- // src/mcp.ts
3229
- import { spawn } from "child_process";
3230
- var MCP_PROTOCOL_VERSION = "2024-11-05";
3231
- var TOOL_DEFS = [
3232
- {
3233
- name: "projx_scaffold",
3234
- description: "Create a new projx project",
3235
- inputSchema: {
3236
- type: "object",
3237
- properties: {
3238
- name: { type: "string", minLength: 1 },
3239
- components: {
3240
- type: "array",
3241
- items: { type: "string", enum: [...COMPONENTS] }
3242
- },
3243
- git: { type: "boolean" },
3244
- install: { type: "boolean" },
3245
- cwd: { type: "string" }
3246
- },
3247
- required: ["name"],
3248
- additionalProperties: false
3249
- }
3250
- },
3251
- {
3252
- name: "projx_scaffold_fullstack",
3253
- description: "Scaffold a production-ready fullstack app with backend, frontend, e2e, and optional mobile/infra",
3254
- inputSchema: {
3255
- type: "object",
3256
- properties: {
3257
- project_name: { type: "string", minLength: 1 },
3258
- backend: { type: "string", enum: ["fastapi", "fastify"] },
3259
- include_frontend: { type: "boolean", default: true },
3260
- include_mobile: { type: "boolean", default: false },
3261
- include_e2e: { type: "boolean", default: true },
3262
- include_infra: { type: "boolean", default: false },
3263
- package_manager: { type: "string", enum: [...PACKAGE_MANAGERS] },
3264
- install_deps: { type: "boolean", default: true },
3265
- init_git: { type: "boolean", default: true },
3266
- cwd: { type: "string" }
3267
- },
3268
- required: ["project_name", "backend"],
3269
- additionalProperties: false
3270
- }
3271
- },
3272
- {
3273
- name: "projx_add_components",
3274
- description: "Add components to an existing projx project",
3275
- inputSchema: {
3276
- type: "object",
3277
- properties: {
3278
- components: {
3279
- type: "array",
3280
- items: { type: "string", enum: [...COMPONENTS] }
3281
- },
3282
- install: { type: "boolean" },
3283
- cwd: { type: "string" }
3284
- },
3285
- required: ["components"],
3286
- additionalProperties: false
3287
- }
3288
- },
3289
- {
3290
- name: "projx_update",
3291
- description: "Update an existing projx project to latest scaffolding",
3292
- inputSchema: {
3293
- type: "object",
3294
- properties: {
3295
- cwd: { type: "string" }
3296
- },
3297
- additionalProperties: false
3298
- }
3299
- },
3300
- {
3301
- name: "projx_doctor",
3302
- description: "Run projx health checks",
3303
- inputSchema: {
3304
- type: "object",
3305
- properties: {
3306
- cwd: { type: "string" },
3307
- fix: { type: "boolean" }
3308
- },
3309
- additionalProperties: false
3310
- }
3311
- }
3312
- ];
3313
- function isComponent(value) {
3314
- return typeof value === "string" && COMPONENTS.includes(value);
3315
- }
3316
- function ensureObject(value) {
3317
- if (!value || typeof value !== "object" || Array.isArray(value)) {
3318
- throw new Error("arguments must be an object");
3319
- }
3320
- return value;
3321
- }
3322
- function parseScaffoldArgs(raw) {
3323
- const args2 = ensureObject(raw);
3324
- const name = args2.name;
3325
- if (typeof name !== "string" || name.trim().length === 0) {
3326
- throw new Error("name is required");
3327
- }
3328
- const parsed = { name: name.trim() };
3329
- if (args2.components !== void 0) {
3330
- if (!Array.isArray(args2.components))
3331
- throw new Error("components must be an array");
3332
- if (!args2.components.every(isComponent)) {
3333
- throw new Error(`components must be one of: ${COMPONENTS.join(", ")}`);
3334
- }
3335
- parsed.components = args2.components;
3336
- }
3337
- if (args2.git !== void 0) {
3338
- if (typeof args2.git !== "boolean") throw new Error("git must be a boolean");
3339
- parsed.git = args2.git;
3340
- }
3341
- if (args2.install !== void 0) {
3342
- if (typeof args2.install !== "boolean")
3343
- throw new Error("install must be a boolean");
3344
- parsed.install = args2.install;
3345
- }
3346
- if (args2.cwd !== void 0) {
3347
- if (typeof args2.cwd !== "string" || args2.cwd.length === 0)
3348
- throw new Error("cwd must be a string");
3349
- parsed.cwd = args2.cwd;
3350
- }
3351
- if (args2.packageManager !== void 0) {
3352
- if (typeof args2.packageManager !== "string" || !PACKAGE_MANAGERS.includes(args2.packageManager)) {
3353
- throw new Error(
3354
- `packageManager must be one of: ${PACKAGE_MANAGERS.join(", ")}`
3355
- );
3356
- }
3357
- parsed.packageManager = args2.packageManager;
3358
- }
3359
- return parsed;
3360
- }
3361
- function parseScaffoldFullstackArgs(raw) {
3362
- const args2 = ensureObject(raw);
3363
- const projectName = args2.project_name;
3364
- if (typeof projectName !== "string" || projectName.trim().length === 0) {
3365
- throw new Error("project_name is required");
3366
- }
3367
- const backend = args2.backend;
3368
- if (backend !== "fastapi" && backend !== "fastify") {
3369
- throw new Error("backend is required and must be fastapi or fastify");
3370
- }
3371
- const parsed = {
3372
- projectName: projectName.trim(),
3373
- backend,
3374
- includeFrontend: args2.include_frontend !== false,
3375
- includeMobile: args2.include_mobile === true,
3376
- includeE2E: args2.include_e2e !== false,
3377
- includeInfra: args2.include_infra === true,
3378
- installDeps: args2.install_deps !== false,
3379
- initGit: args2.init_git !== false
3380
- };
3381
- if (args2.package_manager !== void 0) {
3382
- if (typeof args2.package_manager !== "string" || !PACKAGE_MANAGERS.includes(args2.package_manager)) {
3383
- throw new Error(
3384
- `package_manager must be one of: ${PACKAGE_MANAGERS.join(", ")}`
3385
- );
3386
- }
3387
- parsed.packageManager = args2.package_manager;
3388
- }
3389
- if (args2.cwd !== void 0) {
3390
- if (typeof args2.cwd !== "string" || args2.cwd.length === 0)
3391
- throw new Error("cwd must be a string");
3392
- parsed.cwd = args2.cwd;
3393
- }
3394
- return parsed;
3395
- }
3396
- function parseAddArgs(raw) {
3397
- const args2 = ensureObject(raw);
3398
- if (!Array.isArray(args2.components) || args2.components.length === 0) {
3399
- throw new Error("components is required");
3400
- }
3401
- if (!args2.components.every(isComponent)) {
3402
- throw new Error(`components must be one of: ${COMPONENTS.join(", ")}`);
3403
- }
3404
- const parsed = { components: args2.components };
3405
- if (args2.install !== void 0) {
3406
- if (typeof args2.install !== "boolean")
3407
- throw new Error("install must be a boolean");
3408
- parsed.install = args2.install;
3409
- }
3410
- if (args2.cwd !== void 0) {
3411
- if (typeof args2.cwd !== "string" || args2.cwd.length === 0)
3412
- throw new Error("cwd must be a string");
3413
- parsed.cwd = args2.cwd;
3414
- }
3415
- return parsed;
3416
- }
3417
- function parseUpdateArgs(raw) {
3418
- if (raw === void 0) return {};
3419
- const args2 = ensureObject(raw);
3420
- if (args2.cwd !== void 0 && (typeof args2.cwd !== "string" || args2.cwd.length === 0)) {
3421
- throw new Error("cwd must be a string");
3422
- }
3423
- return { cwd: args2.cwd };
3424
- }
3425
- function parseDoctorArgs(raw) {
3426
- if (raw === void 0) return {};
3427
- const args2 = ensureObject(raw);
3428
- const parsed = {};
3429
- if (args2.cwd !== void 0) {
3430
- if (typeof args2.cwd !== "string" || args2.cwd.length === 0)
3431
- throw new Error("cwd must be a string");
3432
- parsed.cwd = args2.cwd;
3433
- }
3434
- if (args2.fix !== void 0) {
3435
- if (typeof args2.fix !== "boolean") throw new Error("fix must be a boolean");
3436
- parsed.fix = args2.fix;
3437
- }
3438
- return parsed;
3439
- }
3440
- function jsonRpcError(id, code, message) {
3441
- return {
3442
- jsonrpc: "2.0",
3443
- id,
3444
- error: { code, message }
3445
- };
3446
- }
3447
- function textResult(id, text4) {
3448
- return {
3449
- jsonrpc: "2.0",
3450
- id,
3451
- result: {
3452
- content: [{ type: "text", text: text4 }]
3453
- }
3454
- };
3455
- }
3456
- async function runCli(args2, cwd) {
3457
- const entry = process.argv[1];
3458
- if (!entry) {
3459
- throw new Error("Unable to resolve CLI entrypoint");
3460
- }
3461
- await new Promise((resolvePromise, rejectPromise) => {
3462
- const child = spawn(process.execPath, [entry, ...args2], {
3463
- cwd: cwd ?? process.cwd(),
3464
- stdio: ["ignore", "pipe", "pipe"],
3465
- env: process.env
3466
- });
3467
- let stderr = "";
3468
- child.stderr.on("data", (chunk) => {
3469
- stderr += chunk.toString();
3470
- });
3471
- child.on("error", rejectPromise);
3472
- child.on("close", (code) => {
3473
- if (code === 0) {
3474
- resolvePromise();
3475
- } else {
3476
- rejectPromise(
3477
- new Error(stderr.trim() || `Command failed with exit code ${code}`)
3478
- );
3479
- }
3480
- });
3481
- });
3482
- }
3483
- function defaultActions() {
3484
- return {
3485
- scaffold: async ({
3486
- name,
3487
- components,
3488
- git,
3489
- install,
3490
- packageManager,
3491
- cwd
3492
- }) => {
3493
- const cmd = [name];
3494
- if (components && components.length > 0) {
3495
- cmd.push("--components", components.join(","));
3496
- }
3497
- if (packageManager) {
3498
- cmd.push("--package-manager", packageManager);
3499
- }
3500
- if (git === false) cmd.push("--no-git");
3501
- if (install === false) cmd.push("--no-install");
3502
- await runCli(cmd, cwd);
3503
- },
3504
- add: async ({ components, cwd, install }) => {
3505
- const cmd = ["add", ...components];
3506
- if (install === false) cmd.push("--no-install");
3507
- await runCli(cmd, cwd);
3508
- },
3509
- update: async ({ cwd }) => {
3510
- await runCli(["update"], cwd);
3511
- },
3512
- doctor: async ({ cwd, fix }) => {
3513
- const cmd = ["doctor"];
3514
- if (fix) cmd.push("--fix");
3515
- await runCli(cmd, cwd);
3516
- }
3517
- };
3518
- }
3519
- async function handleMcpRequest(request, actions) {
3520
- if (request.jsonrpc !== "2.0") {
3521
- return jsonRpcError(request.id, -32600, "Invalid Request");
3522
- }
3523
- const effectiveActions = {
3524
- ...defaultActions(),
3525
- ...actions
3526
- };
3527
- try {
3528
- if (request.method === "initialize") {
3529
- return {
3530
- jsonrpc: "2.0",
3531
- id: request.id,
3532
- result: {
3533
- protocolVersion: MCP_PROTOCOL_VERSION,
3534
- capabilities: { tools: {} },
3535
- serverInfo: { name: "projx", version: "1.0.0" }
3536
- }
3537
- };
3538
- }
3539
- if (request.method === "tools/list") {
3540
- return {
3541
- jsonrpc: "2.0",
3542
- id: request.id,
3543
- result: { tools: TOOL_DEFS }
3544
- };
3545
- }
3546
- if (request.method === "tools/call") {
3547
- const params = ensureObject(request.params);
3548
- const toolName = params.name;
3549
- const toolArgs = params.arguments;
3550
- if (typeof toolName !== "string") {
3551
- return jsonRpcError(request.id, -32602, "name must be a string");
3552
- }
3553
- if (toolName === "projx_scaffold") {
3554
- const parsed = parseScaffoldArgs(toolArgs);
3555
- await effectiveActions.scaffold(parsed);
3556
- return textResult(request.id, `Created project ${parsed.name}`);
3557
- }
3558
- if (toolName === "projx_scaffold_fullstack") {
3559
- const parsed = parseScaffoldFullstackArgs(toolArgs);
3560
- const components = [parsed.backend];
3561
- if (parsed.includeFrontend) components.push("frontend");
3562
- if (parsed.includeMobile) components.push("mobile");
3563
- if (parsed.includeE2E) components.push("e2e");
3564
- if (parsed.includeInfra) components.push("infra");
3565
- await effectiveActions.scaffold({
3566
- name: parsed.projectName,
3567
- components,
3568
- git: parsed.initGit,
3569
- install: parsed.installDeps,
3570
- packageManager: parsed.packageManager,
3571
- cwd: parsed.cwd
3572
- });
3573
- return textResult(
3574
- request.id,
3575
- `Successfully scaffolded ${parsed.projectName} with ${parsed.backend}. Files are located at ./${parsed.projectName}`
3576
- );
3577
- }
3578
- if (toolName === "projx_add_components") {
3579
- const parsed = parseAddArgs(toolArgs);
3580
- await effectiveActions.add(parsed);
3581
- return textResult(
3582
- request.id,
3583
- `Added components: ${parsed.components.join(", ")}`
3584
- );
3585
- }
3586
- if (toolName === "projx_update") {
3587
- const parsed = parseUpdateArgs(toolArgs);
3588
- await effectiveActions.update(parsed);
3589
- return textResult(request.id, "Updated projx scaffolding");
3590
- }
3591
- if (toolName === "projx_doctor") {
3592
- const parsed = parseDoctorArgs(toolArgs);
3593
- await effectiveActions.doctor(parsed);
3594
- return textResult(request.id, "Doctor check completed");
3595
- }
3596
- return jsonRpcError(request.id, -32602, `Unknown tool: ${toolName}`);
3597
- }
3598
- if (request.method === "ping") {
3599
- return {
3600
- jsonrpc: "2.0",
3601
- id: request.id,
3602
- result: { ok: true }
3603
- };
3604
- }
3605
- return jsonRpcError(
3606
- request.id,
3607
- -32601,
3608
- `Method not found: ${request.method}`
3609
- );
3610
- } catch (error) {
3611
- const message = error instanceof Error ? error.message : String(error);
3612
- return jsonRpcError(request.id, -32602, message);
3613
- }
3614
- }
3615
- function writeMessage(msg) {
3616
- const payload = JSON.stringify(msg);
3617
- const length = Buffer.byteLength(payload, "utf8");
3618
- process.stdout.write(`Content-Length: ${length}\r
3619
- \r
3620
- ${payload}`);
3621
- }
3622
- function startMcpServer() {
3623
- let buffer = Buffer.alloc(0);
3624
- process.stdin.on("data", (chunk) => {
3625
- buffer = Buffer.concat([buffer, chunk]);
3626
- while (true) {
3627
- const headerEnd = buffer.indexOf("\r\n\r\n");
3628
- if (headerEnd === -1) break;
3629
- const header = buffer.subarray(0, headerEnd).toString("utf8");
3630
- const lengthMatch = header.match(/content-length:\s*(\d+)/i);
3631
- if (!lengthMatch) {
3632
- buffer = buffer.subarray(headerEnd + 4);
3633
- continue;
3634
- }
3635
- const contentLength = Number(lengthMatch[1]);
3636
- const bodyStart = headerEnd + 4;
3637
- const bodyEnd = bodyStart + contentLength;
3638
- if (buffer.length < bodyEnd) break;
3639
- const body = buffer.subarray(bodyStart, bodyEnd).toString("utf8");
3640
- buffer = buffer.subarray(bodyEnd);
3641
- let request;
3642
- try {
3643
- request = JSON.parse(body);
3644
- } catch {
3645
- writeMessage(jsonRpcError(void 0, -32700, "Parse error"));
3646
- continue;
3647
- }
3648
- void handleMcpRequest(request).then((response) => {
3649
- if (request.id !== void 0) {
3650
- writeMessage(response);
3651
- }
3652
- });
3653
- }
3654
- });
3655
- process.stdin.resume();
3656
- }
3657
-
3658
3183
  // src/index.ts
3659
3184
  var args = process.argv.slice(2);
3660
3185
  function parseArgs() {
@@ -3702,21 +3227,12 @@ function parseArgs() {
3702
3227
  command = "sync";
3703
3228
  continue;
3704
3229
  }
3705
- if (arg === "mcp" && !name) {
3706
- command = "mcp";
3707
- continue;
3708
- }
3709
3230
  if (arg === "--components") {
3710
3231
  const val = args[++i];
3711
3232
  if (val) {
3712
- options.components = val.split(",").filter((c) => COMPONENTS.includes(c));
3713
- }
3714
- continue;
3715
- }
3716
- if (arg === "--package-manager" || arg === "--pm") {
3717
- const val = args[++i];
3718
- if (val && PACKAGE_MANAGERS.includes(val)) {
3719
- options.packageManager = val;
3233
+ options.components = val.split(",").filter(
3234
+ (c) => COMPONENTS.includes(c)
3235
+ );
3720
3236
  }
3721
3237
  continue;
3722
3238
  }
@@ -3790,11 +3306,9 @@ function printHelp() {
3790
3306
  projx doctor [--fix] Health check for projx project
3791
3307
  projx gen entity <name> Generate a new entity
3792
3308
  projx sync [--url <url>] Sync types from running backend
3793
- projx mcp Start MCP server over stdio
3794
3309
 
3795
3310
  Options:
3796
3311
  --components <list> Comma-separated: fastapi,fastify,frontend,mobile,e2e,infra
3797
- --package-manager One of: npm, pnpm, yarn, bun
3798
3312
  --no-git Skip git init
3799
3313
  --no-install Skip dependency installation
3800
3314
  -y, --yes Accept defaults (fastify + frontend + e2e)
@@ -3829,9 +3343,7 @@ async function main() {
3829
3343
  (c) => COMPONENTS.includes(c)
3830
3344
  );
3831
3345
  if (components.length === 0) {
3832
- console.error(
3833
- `Error: specify components to add. Available: ${COMPONENTS.join(", ")}`
3834
- );
3346
+ console.error(`Error: specify components to add. Available: ${COMPONENTS.join(", ")}`);
3835
3347
  process.exit(1);
3836
3348
  }
3837
3349
  await add(process.cwd(), components, localRepo, options.install === false);
@@ -3847,9 +3359,7 @@ async function main() {
3847
3359
  }
3848
3360
  if (command === "unpin") {
3849
3361
  if (extraArgs.length === 0) {
3850
- console.error(
3851
- "Error: specify patterns to unpin. Usage: projx unpin <patterns...>"
3852
- );
3362
+ console.error("Error: specify patterns to unpin. Usage: projx unpin <patterns...>");
3853
3363
  process.exit(1);
3854
3364
  }
3855
3365
  await unpin(process.cwd(), extraArgs);
@@ -3869,16 +3379,10 @@ async function main() {
3869
3379
  await sync(process.cwd(), url);
3870
3380
  return;
3871
3381
  }
3872
- if (command === "mcp") {
3873
- startMcpServer();
3874
- return;
3875
- }
3876
3382
  if (command === "gen") {
3877
3383
  const subcommand = extraArgs[0];
3878
3384
  if (subcommand !== "entity" || !extraArgs[1]) {
3879
- console.error(
3880
- 'Usage: projx gen entity <name> [--fields "name:string,amount:number"]'
3881
- );
3385
+ console.error('Usage: projx gen entity <name> [--fields "name:string,amount:number"]');
3882
3386
  process.exit(1);
3883
3387
  }
3884
3388
  const entityName = extraArgs[1];
@@ -3898,14 +3402,12 @@ async function main() {
3898
3402
  name,
3899
3403
  components: options.components,
3900
3404
  git: options.git ?? true,
3901
- install: options.install ?? true,
3902
- packageManager: options.packageManager
3405
+ install: options.install ?? true
3903
3406
  };
3904
3407
  } else {
3905
3408
  opts = await runPrompts(name);
3906
3409
  opts.git = options.git ?? opts.git;
3907
3410
  opts.install = options.install ?? opts.install;
3908
- opts.packageManager = options.packageManager ?? opts.packageManager;
3909
3411
  }
3910
3412
  const dest = resolve2(process.cwd(), opts.name);
3911
3413
  if (existsSync13(dest)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-projx",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "Scaffold production-grade fullstack projects in seconds. FastAPI, Fastify, React, Flutter, Terraform — with auth, database, CI/CD, E2E tests, and Docker. One command, ready to deploy.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -96,14 +96,6 @@ cd <%= paths.e2e %> && <%= pm.exec %> playwright test
96
96
  npx create-projx@latest update
97
97
  ```
98
98
 
99
- ## MCP
100
-
101
- This project includes a root `.mcp.json` for local MCP stdio usage with compatible AI agents.
102
-
103
- ```bash
104
- npx create-projx mcp
105
- ```
106
-
107
99
  ---
108
100
 
109
101
  [![Built with Projx](https://img.shields.io/badge/Built%20with-Projx-blue)](https://github.com/ukanhaupa/projx)