Skip to content

feat: add cursor-scope prompt for project vs global install#60

Open
adamgomez-ux wants to merge 3 commits intojulianoczkowski:mainfrom
adamgomez-ux:main
Open

feat: add cursor-scope prompt for project vs global install#60
adamgomez-ux wants to merge 3 commits intojulianoczkowski:mainfrom
adamgomez-ux:main

Conversation

@adamgomez-ux
Copy link
Copy Markdown

Pull Request

📝 Description

Adds a new --cursor-scope option that lets users choose whether Cursor config (MCP, Rules, Skills) is installed at the project level or globally.

  • Project scope (default): .cursor/ is copied into the project folder — isolated, team-friendly, committable to source control
  • Global scope: .cursor/ is copied to ~/.cursor/ — available across all projects on the machine

🔗 Related Issue

N/A

🎯 Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🔧 Build/CI changes
  • ♻️ Code refactoring (no functional changes)
  • ⚡ Performance improvement
  • 🧪 Adding or updating tests
  • 🏗️ Infrastructure changes

🧪 Testing

All 54 existing tests pass. 4 new tests added to tests/utils/git.test.js covering:

  • Project scope keeps .cursor/ inside the project folder
  • Global scope omits .cursor/ from the project folder
  • Global scope writes .cursor/ to ~/.cursor/

Manual Testing Checklist

  • CLI runs without errors (npm run dev or node bin/create-trimble-app.js)
  • Interactive mode works (new cursor-scope prompt displays correctly after framework selection)
  • Command line arguments work (--cursor-scope project and --cursor-scope global)
  • All frameworks scaffold correctly (React, Angular)
  • Project naming works with various inputs
  • Error handling works for invalid inputs
  • Help and version commands work (--help, --version)

Framework Testing

Tested scaffolding for:

  • React template
  • Angular template
  • Vue template
  • HTML template

Environment Testing

Tested on:

  • Windows
  • macOS
  • Linux

Node.js Version Testing

  • Node.js 18.x
  • Node.js 20.x
  • Node.js 25.x (detected during testing)

📸 Screenshots

Before

Interactive prompt went straight from framework selection to project name.

After

A new step appears after framework selection:

◆ Where should Cursor config be installed? (MCP, Rules, Skills)
● Project (.cursor/ in project folder) — recommended, isolated to this project
○ Global (~/.cursor/) — available across all projects on this machine

Success message now includes:

Cursor config: project (.cursor/ in project folder)

🔧 CLI Functionality

  • Interactive prompts work correctly — new scope prompt appears as step 2
  • Command line arguments — new --cursor-scope <project|global> flag parsed properly
  • Error messages are clear for invalid scope values
  • Success messages confirm which scope was chosen
  • Template cloning works for all frameworks with both scope options
  • Project name validation handles edge cases
  • Dependency installation respects user choice

📋 Code Quality

  • Code follows JavaScript/Node.js best practices
  • Error handling is comprehensive
  • Logging provides appropriate feedback
  • No hardcoded values - scope passed through as parameter
  • Code is well-documented with comments where needed
  • Dependencies — no new dependencies added

🔄 Breaking Changes

  • This PR introduces breaking changes

No breaking changes. Project scope is the default, so existing behavior is preserved for all current users.

📚 Documentation

  • CLI help text updated — --cursor-scope flag appears in --help output
  • Code comments added for scope branching logic
  • README.md — suggest maintainer adds --cursor-scope to the CLI options table

💬 Additional Notes

This feature was motivated by teams wanting to commit .cursor/ config to source control so all team members get the same MCP, Rules, and Skills automatically when they clone the repo — without affecting their global Cursor setup on other projects.

The --cursor-scope flag also makes this scriptable for automated project setup workflows.

📝 Reviewer Instructions

  1. Pull and test locally:

    git checkout main
    npm install
    node bin/create-trimble-app.js
  2. Test the new cursor-scope prompt:

    • Interactive: node bin/create-trimble-app.js — select a framework, then choose scope
    • Project flag: node bin/create-trimble-app.js my-app --framework react --cursor-scope project --dry-run
    • Global flag: node bin/create-trimble-app.js my-app --framework react --cursor-scope global --dry-run
  3. Run tests:

    npm test

By submitting this PR, I confirm that:

  • I have read and followed the Contributing Guidelines
  • I have tested my changes thoroughly across multiple scenarios
  • I have considered the impact on existing users
  • I am willing to address feedback and make necessary changes

Copy link
Copy Markdown
Owner

@julianoczkowski julianoczkowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution, Adam! The idea is useful and the project-scope path is clean. I checked out the branch locally, ran the full test suite, and scaffolded all three templates (React, Angular, SolidJS) against both scopes. A few things to call out:

What works well

  • All 54 tests pass on the PR branch.
  • --help correctly shows the new --cursor-scope flag and invalid values are rejected with a clear error.
  • Dry-run output for all three frameworks includes the new Cursor config: line.
  • Project-scope (default) scaffolding is unchanged end-to-end for React / Angular / SolidJS including npm install + post-scaffold validation. No regression for existing users.
  • Global-scope scaffolding mechanically does the right thing: .cursor/ is omitted from the project folder and written to ~/.cursor/.

What needs to change before merge (two are destructive)

  1. Critical – the new test silently overwrites the developer's real ~/.cursor/mcp.json. I verified this firsthand: my personal ~/.cursor/mcp.json went from 826 bytes (my actual MCP servers) to 231 bytes (the React template's minimal file) just from running npm test. Any maintainer or CI runner using Cursor will lose their global MCP config. See the inline comment on tests/utils/git.test.js.
  2. Critical – the production global path has the same overwrite problem for real end users. copyDirectory uses fs.copyFile, which overwrites unconditionally. A user who picks "Global" in the prompt will lose any existing ~/.cursor/mcp.json and may have ~/.cursor/commands, ~/.cursor/rules, ~/.cursor/skills files clobbered on name collision. See the inline comment on src/utils/git.js.
  3. Design – running global scope for multiple frameworks pollutes ~/.cursor/rules/. Rule filenames are framework-specific (e.g. modus-react-master.mdc, modus-angular-master.mdc), so picking global scope for React and later for Angular leaves both sets active on every project on the machine. Worth documenting or namespacing.
  4. Correctness – Cursor's "Rules for AI" and slash-commands are primarily project-scoped. Copying rules/ and commands/ into ~/.cursor/ does not make Cursor apply them globally the way the prompt hint implies. Only ~/.cursor/mcp.json and (partially) ~/.cursor/skills/ are actually picked up globally. Prompt wording should be tightened.
  5. Nit – step-number comments in src/scaffold.js got desynchronized (jumps 2 → 4 → 5 → 6 → 7 → 8 → 9 → 9). Functional only; cosmetic fix.
  6. Docs – README CLI options table and the --help Examples footer don't mention --cursor-scope yet. You flagged the README one yourself in the PR body.
  7. Nit – .claude/settings.local.json is a personal dev-env artifact; broadening the bash allow-list here probably shouldn't be part of this PR (and arguably the file should be .gitignored with an example committed).

Happy to help with a follow-up if it's easier. Marking as Request Changes primarily for #1 and #2 — those will bite real users and contributors.

Comment thread tests/utils/git.test.js Outdated
await copyTemplate("react", projectDir, { cursorScope: "global" });

// ~/.cursor/ should now contain mcp.json from the template
expect(existsSync(join(globalCursorPath, "mcp.json"))).toBe(true);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical – this test overwrites the developer's real ~/.cursor/mcp.json.

I ran npm test on a clean checkout of this branch and my personal ~/.cursor/mcp.json went from 826 bytes (my actual MCP server config) to 231 bytes (the template's minimal 2-server file). Every maintainer, contributor, or CI job with a Cursor install will silently lose their global MCP config when running the test suite.

The root cause is that copyTemplate(..., { cursorScope: "global" }) resolves homedir() to the real home dir with no test isolation.

Suggested fix – inject an overridable global path and mock it in the test:

// in src/utils/git.js signature
export async function copyTemplate(
  templateName,
  targetPath,
  { cursorScope = "project", globalCursorPath } = {}
) { ... }

// in the global branch
const target = globalCursorPath ?? path.join((await import("os")).homedir(), ".cursor");
await copyDirectory(cursorSrc, target);

Then in this test, pass a temp dir:

const fakeHome = join(tmpdir(), "cta-fake-home-" + Date.now());
await copyTemplate("react", projectDir, {
  cursorScope: "global",
  globalCursorPath: join(fakeHome, ".cursor"),
});
expect(existsSync(join(fakeHome, ".cursor", "mcp.json"))).toBe(true);
// cleanup fakeHome in finally

Alternatively vi.spyOn(os, "homedir") to redirect it, but the injected-path approach is simpler and also useful for the real code path (e.g. future --cursor-global-path flag).

Comment thread src/utils/git.js Outdated
const { homedir } = await import("os");
const cursorSrc = path.join(bundledPath, ".cursor");
const globalCursorPath = path.join(homedir(), ".cursor");
await copyDirectory(cursorSrc, globalCursorPath);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical – this is destructive to the end user's existing Cursor config.

copyDirectory ultimately calls fs.copyFile, which overwrites unconditionally. A real user who picks the "Global" option in the interactive prompt will silently lose:

  • ~/.cursor/mcp.json – their personal MCP servers (confirmed on my own machine via the test).
  • Any files in ~/.cursor/commands/, ~/.cursor/rules/, ~/.cursor/skills/ that happen to share a filename with the template.

At minimum I'd suggest one or more of:

  1. Back up before overwrite: rename any existing ~/.cursor/mcp.json to mcp.json.bak-<ISO-timestamp> before writing.
  2. Merge strategy for mcp.json: read existing JSON, merge mcpServers keys (template wins on collision but existing keys are preserved), then write. This is what most users will actually want.
  3. Skip-if-exists for rules/, commands/, skills/: only copy files that don't already exist, unless the user passes --force.
  4. Confirmation prompt: if any of those targets already exist, show a second p.confirm with a list of files that will be affected before proceeding.

(1) + (3) is the minimum bar for "not destructive". (2) is the nicest UX.

Comment thread src/utils/git.js
await copyDirectory(bundledPath, targetPath);
if (cursorScope === "global") {
// Copy project files, skipping .cursor/ (it goes to ~/.cursor/ instead)
const projectSkipDirs = new Set([...SKIP_DIRECTORIES, ".cursor"]);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor – consider also skipping .cursor in the recursive pass if any template ever nests it (defensive; current templates don't). More importantly, a doc comment here clarifying why .cursor is excluded would help future readers — something like:

// Global scope: template's .cursor/ is redirected to ~/.cursor/ below,
// so we don't also want it inside the project folder.
const projectSkipDirs = new Set([...SKIP_DIRECTORIES, ".cursor"]);

Comment thread src/scaffold.js Outdated
{
label: "Global (~/.cursor/)",
value: "global",
hint: "Active across all your Cursor projects",
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hint text "Active across all your Cursor projects" is only accurate for ~/.cursor/mcp.json and (partially) ~/.cursor/skills/. Cursor does not apply ~/.cursor/rules/*.mdc as global rules — Rules for AI are project-scoped; the global equivalent is the user-settings "Rules" field. Similarly ~/.cursor/commands/ is not a supported global commands dir at the moment.

Two options:

  1. Narrow what gets copied in global mode to just mcp.json (and maybe skills/) and tighten the hint to "Installs global MCP config (~/.cursor/mcp.json)".
  2. Keep copying everything but soften the hint, e.g. "Installs Modus MCP globally; rules/commands become available to Cursor if/where it reads them".

Option 1 is probably the honest one, since Rules really are meant to travel with the project.

Comment thread src/scaffold.js Outdated
}
}

// 4. Installation Location Choice
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit – comment numbering got out of sync after inserting the new step. The current sequence reads // 2. (framework, implicit) → // 2. (this new scope prompt) → // 4. (here) → // 5.// 6.// 7.// 8.// 9. (appears twice, on L253 and L256). Purely cosmetic, but worth renumbering to 1..9 while the change is small.

Comment thread src/scaffold.js
// 8. Detailed next steps
logger.nextSteps(projectName, config.name, install, installInCurrentFolder);
// 9. Detailed next steps
logger.nextSteps(projectName, config.name, install, installInCurrentFolder, installScope);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate // 9. comment (also on line 253). Cosmetic.

Comment thread .claude/settings.local.json Outdated
"Bash(git push:*)",
"Bash(xargs cat *)",
"Bash(npm test *)",
"Bash(npx vitest *)",
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit – .claude/settings.local.json is a personal dev-env file and this broadens the bash allow-list (xargs cat *, npm test *, npx vitest *, npm install *). These shouldn't really be shipped as part of a feature PR.

Longer term, I'd suggest .gitignore-ing .claude/settings.local.json and committing a .claude/settings.example.json instead, so everyone's local tweaks stay local.

Comment thread src/cli.js
"--cursor-scope <scope>",
"Where to install Cursor config: project or global (default: prompt)",
validateCursorScope,
)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also add a --cursor-scope example to the Examples section of the help footer (the boxen block starting around line 66)? The flag is listed in Options but not demonstrated, which makes it easy to miss. Something like:

# Install Cursor config globally (for all projects on this machine)
npx @julianoczkowski/create-trimble-app@latest my-app -f react --cursor-scope global

- Fix global test to use injected globalCursorPath (fake home) instead of real ~/.cursor/
- Add globalCursorPath param to copyTemplate; global mode now only copies mcp.json and skills/ (non-destructive with backup)
- Add global-scope warning in scaffold.js about multi-framework side effects
- Fix global scope hint to "Installs global MCP config (~/.cursor/mcp.json)"
- Fix dry-run cursor config message for global scope
- Renumber scaffold.js step comments 1-9 with no duplicates
- Add --cursor-scope global example to CLI help
- Remove .claude/settings.local.json from git tracking; add to .gitignore; add settings.example.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@adamgomez-ux
Copy link
Copy Markdown
Author

Thanks for the thorough review Julian! I've pushed a follow-up commit addressing all 7 points:

Critical #1 — Fixed global test to use an injected temp directory instead of real /.cursor/
Critical #2 — Global copy is now non-destructive: backs up existing mcp.json with timestamp, skips existing files in rules/, commands/, skills/
Design #3 — Added warning after user picks global scope about multi-framework pollution
Correctness #4 — Global scope now only copies mcp.json and skills/; updated hint to "Installs global MCP config (
/.cursor/mcp.json)"
Nit #5 — Renumbered step comments in scaffold.js sequentially 1-9
Docs #6 — Added --cursor-scope global example to the help footer in cli.js
Nit #7 — Removed .claude/settings.local.json from git tracking, added to .gitignore, committed settings.example.json in its place

All 54 tests pass and all 6 dry-runs (React, Angular, SolidJS × project + global) complete cleanly.

@adamgomez-ux
Copy link
Copy Markdown
Author

Hi Julian, I just wanted to flag that the follow-up commit addressing all 7 review items (839027e) was pushed on Apr 21. The recent upstream merge may have affected how the commits display in the PR diff. All fixes are in place: injected fakeHome temp dir for test isolation, non-destructive global copy with timestamp backup, warning for multi-framework pollution, global scope now only copies mcp.json and skills/, step comments renumbered 1–9, --cursor-scope global example added to help footer, and settings.local.json removed from git with settings.example.json committed in its place. Happy to rebase or squash if that helps the review. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants