prjct-cli 0.36.1 → 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 CHANGED
@@ -1,5 +1,46 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.37.0] - 2026-01-24
4
+
5
+ ### Feature: Cursor IDE Support (PRJ-63)
6
+
7
+ prjct now works with **Cursor IDE** in addition to Claude Code and Gemini CLI. Use any AI model Cursor supports (GPT-4, Claude, Gemini, DeepSeek, etc.).
8
+
9
+ **Key Insight:** Cursor has NO global config directory. Unlike Claude/Gemini which use `~/.claude/` and `~/.gemini/`, Cursor uses project-level config in `.cursor/`.
10
+
11
+ **Solution: Minimal Router Pattern**
12
+ - Router files are minimal (~15 lines), point to npm package for real instructions
13
+ - If deleted, `p. sync` regenerates them automatically
14
+ - Added to `.gitignore` - each developer regenerates their own
15
+
16
+ **New Files:**
17
+ - `templates/global/CURSOR.mdc` - Full prjct instructions for Cursor
18
+ - `templates/cursor/router.mdc` - Minimal router (installed in projects)
19
+ - `templates/cursor/p.md` - Command router for `p. <command>`
20
+
21
+ **Modified:**
22
+ - `core/types/provider.ts` - Added 'cursor' to AIProviderName
23
+ - `core/infrastructure/ai-provider.ts` - Added CursorProvider, detectCursorProject()
24
+ - `core/infrastructure/setup.ts` - Added installCursorProject(), hasCursorProject()
25
+ - `templates/commands/init.md` - Cursor detection and setup
26
+ - `templates/commands/sync.md` - Cursor router regeneration
27
+ - `bin/prjct.ts` - Cursor status in --version output
28
+ - `README.md` - Added Cursor to supported platforms
29
+
30
+ **Architecture:**
31
+ ```
32
+ Claude/Gemini (CLI) Cursor (GUI)
33
+ ~/.claude/CLAUDE.md .cursor/rules/prjct.mdc (router)
34
+ ~/.gemini/GEMINI.md ↓
35
+ ↓ npm/prjct-cli/templates/global/CURSOR.mdc
36
+ Global config ↓
37
+ └──────────────────────────┘
38
+
39
+ ~/.prjct-cli/projects/{id}/ (shared storage)
40
+ ```
41
+
42
+ ---
43
+
3
44
  ## [0.36.1] - 2026-01-23
4
45
 
5
46
  ### Docs: Minimal README
package/README.md CHANGED
@@ -2,10 +2,11 @@
2
2
 
3
3
  **Context layer for AI coding agents.**
4
4
 
5
- Works with Claude Code, Gemini CLI, and more.
5
+ Works with Claude Code, Gemini CLI, Cursor IDE, and more.
6
6
 
7
7
  [![Claude Code](https://img.shields.io/badge/Claude%20Code-Ready-6366f1)](CLAUDE.md)
8
8
  [![Gemini CLI](https://img.shields.io/badge/Gemini%20CLI-Ready-4285F4)]()
9
+ [![Cursor IDE](https://img.shields.io/badge/Cursor%20IDE-Ready-00D4AA)]()
9
10
  [![npm](https://img.shields.io/npm/v/prjct-cli)](https://www.npmjs.com/package/prjct-cli)
10
11
 
11
12
  ## What is prjct?
@@ -13,18 +14,18 @@ Works with Claude Code, Gemini CLI, and more.
13
14
  prjct gives AI coding agents the context they need about your project. It maintains state between sessions, tracks progress, and ensures agents understand your codebase.
14
15
 
15
16
  ```
16
- Your AI Agent (Claude/Gemini) prjct
17
-
18
- │ "What am I working on?"
19
- ───────────────────────►
20
- │ Reads project context
21
- │ Task: "Add user auth"
22
- │ Branch: feature/auth
23
- │ Subtask 2/5: API routes
24
- ◄───────────────────────
25
-
26
-
27
- Writes code with full context
17
+ Your AI Agent (Claude/Gemini/Cursor) prjct
18
+
19
+ │ "What am I working on?"
20
+ ───────────────────────────────►
21
+ │ Reads project context
22
+ │ Task: "Add user auth"
23
+ │ Branch: feature/auth
24
+ │ Subtask 2/5: API routes
25
+ ◄───────────────────────────────
26
+
27
+
28
+ Writes code with full context
28
29
  ```
29
30
 
30
31
  ## Install
@@ -36,7 +37,7 @@ prjct start
36
37
 
37
38
  ## Usage
38
39
 
39
- Inside Claude Code or Gemini CLI, use the `p.` prefix:
40
+ Inside Claude Code, Gemini CLI, or Cursor IDE, use the `p.` prefix:
40
41
 
41
42
  ```
42
43
  p. sync # Analyze project, generate agents
@@ -53,13 +54,14 @@ p. sync → p. task "..." → [code] → p. done → p. ship
53
54
 
54
55
  ## How It Works
55
56
 
56
- | Component | Claude Code | Gemini CLI |
57
- |-----------|-------------|------------|
58
- | Router | `~/.claude/commands/p.md` | `~/.gemini/commands/p.toml` |
59
- | Config | `~/.claude/CLAUDE.md` | `~/.gemini/GEMINI.md` |
60
- | Storage | `~/.prjct-cli/projects/` | `~/.prjct-cli/projects/` |
57
+ | Component | Claude Code | Gemini CLI | Cursor IDE |
58
+ |-----------|-------------|------------|------------|
59
+ | Router | `~/.claude/commands/p.md` | `~/.gemini/commands/p.toml` | `.cursor/commands/p.md` |
60
+ | Config | `~/.claude/CLAUDE.md` | `~/.gemini/GEMINI.md` | `.cursor/rules/prjct.mdc` |
61
+ | Storage | `~/.prjct-cli/projects/` | `~/.prjct-cli/projects/` | `~/.prjct-cli/projects/` |
62
+ | Scope | Global | Global | Per-project |
61
63
 
62
- Both agents share the same project storage, so you can switch between them freely.
64
+ All agents share the same project storage, so you can switch between them freely.
63
65
 
64
66
  ## Commands
65
67
 
@@ -77,17 +79,16 @@ Both agents share the same project storage, so you can switch between them freel
77
79
  ## CLI Commands
78
80
 
79
81
  ```bash
80
- prjct start # First-time setup
82
+ prjct start # First-time setup (Claude/Gemini)
83
+ prjct init # Initialize project (+ Cursor setup)
81
84
  prjct --version # Show version + provider status
82
85
  prjct --help # Show help
83
- prjct init # Initialize project
84
- prjct sync # Sync project state
85
86
  ```
86
87
 
87
88
  ## Requirements
88
89
 
89
90
  - Node.js 18+ or Bun 1.0+
90
- - Claude Code and/or Gemini CLI
91
+ - One of: Claude Code, Gemini CLI, or Cursor IDE
91
92
 
92
93
  ## Links
93
94
 
package/bin/prjct.ts CHANGED
@@ -89,8 +89,11 @@ if (args[0] === 'start' || args[0] === 'setup') {
89
89
  // Show version with provider status
90
90
  const detection = detectAllProviders()
91
91
  const home = os.homedir()
92
+ const cwd = process.cwd()
92
93
  const claudeConfigured = fs.existsSync(path.join(home, '.claude', 'commands', 'p.md'))
93
94
  const geminiConfigured = fs.existsSync(path.join(home, '.gemini', 'commands', 'p.toml'))
95
+ const cursorDetected = fs.existsSync(path.join(cwd, '.cursor'))
96
+ const cursorConfigured = fs.existsSync(path.join(cwd, '.cursor', 'rules', 'prjct.mdc'))
94
97
 
95
98
  const GREEN = '\x1b[32m'
96
99
 
@@ -118,8 +121,17 @@ ${DIM}Providers:${RESET}`)
118
121
  console.log(` Gemini CLI ${DIM}○ not installed${RESET}`)
119
122
  }
120
123
 
124
+ // Cursor status (project-level)
125
+ if (cursorDetected) {
126
+ const status = cursorConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● detected${RESET}`
127
+ console.log(` Cursor IDE ${status}${DIM} (project)${RESET}`)
128
+ } else {
129
+ console.log(` Cursor IDE ${DIM}○ not detected${RESET}`)
130
+ }
131
+
121
132
  console.log(`
122
- ${DIM}Run 'prjct start' to configure${RESET}
133
+ ${DIM}Run 'prjct start' to configure (CLI providers)${RESET}
134
+ ${DIM}Run 'prjct init' to configure (Cursor IDE)${RESET}
123
135
  ${CYAN}https://prjct.app${RESET}
124
136
  `)
125
137
  } else {
@@ -27,7 +27,11 @@ export interface TemplateExecutionContext {
27
27
  command: string
28
28
  args: string
29
29
 
30
- // Paths for Claude (not content - Claude reads what it needs)
30
+ // Agent information
31
+ agentName: string
32
+ agentSettingsPath: string
33
+
34
+ // Paths for agent (not content)
31
35
  paths: {
32
36
  orchestrator: string
33
37
  agentRouting: string
@@ -85,6 +89,8 @@ export class TemplateExecutor {
85
89
  ): Promise<TemplateExecutionContext> {
86
90
  const projectId = await this.getProjectId(projectPath)
87
91
  const globalPath = pathManager.getGlobalProjectPath(projectId)
92
+ const aiProvider = require('../infrastructure/ai-provider')
93
+ const activeProvider = aiProvider.getActiveProvider()
88
94
 
89
95
  // Get templates directory - use local path during development
90
96
  let templatesDir: string
@@ -102,6 +108,8 @@ export class TemplateExecutor {
102
108
  globalPath,
103
109
  command,
104
110
  args,
111
+ agentName: activeProvider.displayName,
112
+ agentSettingsPath: pathManager.getAgentSettingsPath(),
105
113
  paths: {
106
114
  orchestrator: path.join(templatesDir, 'agentic', 'orchestrator.md'),
107
115
  agentRouting: path.join(templatesDir, 'agentic', 'agent-routing.md'),
@@ -109,7 +117,7 @@ export class TemplateExecutor {
109
117
  commandTemplate: path.join(templatesDir, 'commands', `${command}.md`),
110
118
  repoAnalysis: path.join(globalPath, 'analysis', 'repo-analysis.json'),
111
119
  agentsDir: path.join(globalPath, 'agents'),
112
- skillsDir: path.join(os.homedir(), '.claude', 'skills'),
120
+ skillsDir: activeProvider.skillsDir,
113
121
  stateJson: path.join(globalPath, 'storage', 'state.json'),
114
122
  }
115
123
  }
@@ -157,7 +165,7 @@ export class TemplateExecutor {
157
165
  }
158
166
 
159
167
  /**
160
- * Build prompt that tells Claude to execute templates agentically
168
+ * Build prompt that tells agent to execute templates agentically
161
169
  */
162
170
  buildAgenticPrompt(context: TemplateExecutionContext): AgenticPromptInfo {
163
171
  const requiresOrchestration = this.requiresOrchestration(context.command)
@@ -165,9 +173,11 @@ export class TemplateExecutor {
165
173
  const prompt = `
166
174
  ## Agentic Execution Mode
167
175
 
168
- You are executing a prjct command. Follow the template-first approach.
176
+ You are executing a prjct command as ${context.agentName}. Follow the template-first approach.
169
177
 
170
178
  ### Context
179
+ - Agent: ${context.agentName}
180
+ - Settings: ${context.agentSettingsPath}
171
181
  - Command: ${context.command}
172
182
  - Args: ${context.args}
173
183
  - Project: ${context.projectPath}
package/core/cli/start.ts CHANGED
@@ -198,12 +198,17 @@ async function selectProviders(): Promise<AIProviderName[]> {
198
198
  }
199
199
 
200
200
  /**
201
- * Install router for a provider
201
+ * Install router for a CLI-based provider (Claude/Gemini)
202
+ * Note: Cursor uses project-level config, not global
202
203
  */
203
204
  async function installRouter(provider: AIProviderName): Promise<boolean> {
204
- const home = os.homedir()
205
205
  const config = Providers[provider]
206
206
 
207
+ // Skip project-level providers (Cursor)
208
+ if (!config.configDir) {
209
+ return false
210
+ }
211
+
207
212
  try {
208
213
  // Create commands directory
209
214
  const commandsDir = path.join(config.configDir, 'commands')
@@ -231,12 +236,17 @@ async function installRouter(provider: AIProviderName): Promise<boolean> {
231
236
  }
232
237
 
233
238
  /**
234
- * Install global config for a provider
239
+ * Install global config for a CLI-based provider (Claude/Gemini)
240
+ * Note: Cursor uses project-level config, not global
235
241
  */
236
242
  async function installGlobalConfig(provider: AIProviderName): Promise<boolean> {
237
- const home = os.homedir()
238
243
  const config = Providers[provider]
239
244
 
245
+ // Skip project-level providers (Cursor)
246
+ if (!config.configDir) {
247
+ return false
248
+ }
249
+
240
250
  try {
241
251
  // Ensure config directory exists
242
252
  fs.mkdirSync(config.configDir, { recursive: true })
@@ -74,14 +74,17 @@ export class AnalysisCommands extends PrjctCommandsBase {
74
74
 
75
75
  await generateContext(projectId!, projectPath)
76
76
 
77
+ const aiProvider = require('../infrastructure/ai-provider')
78
+ const activeProvider = aiProvider.getActiveProvider()
79
+
77
80
  const globalConfigResult = await commandInstaller.installGlobalConfig()
78
81
  if (globalConfigResult.success) {
79
- console.log('📝 Updated ~/.claude/CLAUDE.md')
82
+ console.log(`📝 Updated ${pathManager.getDisplayPath(globalConfigResult.path!)}`)
80
83
  }
81
84
 
82
85
  console.log('✅ Analysis complete!\n')
83
86
  console.log('📄 Full report: analysis/repo-summary.md')
84
- console.log('📝 Context: ~/.prjct-cli/projects/' + projectId + '/CLAUDE.md\n')
87
+ console.log(`📝 Context: ~/.prjct-cli/projects/${projectId}/${activeProvider.contextFile}\n`)
85
88
  console.log('Next steps:')
86
89
  console.log('• /p:sync → Generate agents based on stack')
87
90
  console.log('• /p:feature → Add a new feature')
@@ -220,7 +223,7 @@ export class AnalysisCommands extends PrjctCommandsBase {
220
223
  // Update global config
221
224
  const globalConfigResult = await commandInstaller.installGlobalConfig()
222
225
  if (globalConfigResult.success) {
223
- console.log('📝 Updated ~/.claude/CLAUDE.md')
226
+ console.log(`📝 Updated ${pathManager.getDisplayPath(globalConfigResult.path!)}`)
224
227
  }
225
228
 
226
229
  // Format output
@@ -17,17 +17,19 @@ export class SetupCommands extends PrjctCommandsBase {
17
17
  * First-time setup - Install commands to editors
18
18
  */
19
19
  async start(): Promise<CommandResult> {
20
- console.log('🚀 Setting up prjct for Claude...\n')
20
+ const aiProvider = require('../infrastructure/ai-provider')
21
+ const activeProvider = aiProvider.getActiveProvider()
22
+
23
+ console.log(`🚀 Setting up prjct for ${activeProvider.displayName}...\n`)
21
24
 
22
25
  const status = await commandInstaller.checkInstallation()
23
26
 
24
- if (!status.claudeDetected) {
27
+ if (!status.claudeDetected) { // Note: variable name is legacy, checks active provider
25
28
  return {
26
29
  success: false,
27
30
  message:
28
- '❌ Claude not detected.\n\nPlease install Claude Code or Claude Desktop first:\n' +
29
- ' - Claude Code: https://claude.com/code\n' +
30
- ' - Claude Desktop: https://claude.com/desktop',
31
+ `❌ ${activeProvider.displayName} not detected.\n\nPlease install it first:\n` +
32
+ ` - ${activeProvider.displayName}: ${activeProvider.docsUrl}`,
31
33
  }
32
34
  }
33
35
 
@@ -41,7 +43,7 @@ export class SetupCommands extends PrjctCommandsBase {
41
43
  }
42
44
  }
43
45
 
44
- console.log(`\n✅ Installed ${result.installed?.length ?? 0} commands to:\n ${result.path}`)
46
+ console.log(`\n✅ Installed ${result.installed?.length ?? 0} commands to:\n ${pathManager.getDisplayPath(result.path || '')}`)
45
47
 
46
48
  if ((result.errors?.length ?? 0) > 0) {
47
49
  console.log(`\n⚠️ ${result.errors?.length ?? 0} errors:`)
@@ -50,9 +52,9 @@ export class SetupCommands extends PrjctCommandsBase {
50
52
 
51
53
  console.log('\n🎉 Setup complete!')
52
54
  console.log('\nNext steps:')
53
- console.log(' 1. Open Claude Code or Claude Desktop')
55
+ console.log(` 1. Open ${activeProvider.displayName}`)
54
56
  console.log(' 2. Navigate to your project')
55
- console.log(' 3. Run: /p:init')
57
+ console.log(' 3. Run: /p:init') // This might need adjustment for Gemini (p init) but /p:init is likely fine for now or we can make it dynamic
56
58
 
57
59
  return {
58
60
  success: true,
@@ -90,25 +92,32 @@ export class SetupCommands extends PrjctCommandsBase {
90
92
 
91
93
  console.log('\n📝 Installing global configuration...')
92
94
  const configResult = await commandInstaller.installGlobalConfig()
95
+ const displayPath = configResult.path ? pathManager.getDisplayPath(configResult.path) : 'global config'
93
96
 
94
97
  if (configResult.success) {
95
98
  if (configResult.action === 'created') {
96
- console.log('✅ Created ~/.claude/CLAUDE.md')
99
+ console.log(`✅ Created ${displayPath}`)
97
100
  } else if (configResult.action === 'updated') {
98
- console.log('✅ Updated ~/.claude/CLAUDE.md')
101
+ console.log(`✅ Updated ${displayPath}`)
99
102
  } else if (configResult.action === 'appended') {
100
- console.log('✅ Added prjct config to ~/.claude/CLAUDE.md')
103
+ console.log(`✅ Added prjct config to ${displayPath}`)
101
104
  }
102
105
  } else {
103
106
  console.log(`⚠️ ${configResult.error}`)
104
107
  }
105
108
 
106
- console.log('\n⚡ Installing status line...')
107
- const statusLineResult = await this.installStatusLine()
108
- if (statusLineResult.success) {
109
- console.log('✅ Status line configured')
110
- } else {
111
- console.log(`⚠️ ${statusLineResult.error}`)
109
+ const aiProvider = require('../infrastructure/ai-provider')
110
+ const activeProvider = aiProvider.getActiveProvider()
111
+
112
+ // Status line is currently Claude-only
113
+ if (activeProvider.name === 'claude') {
114
+ console.log('\n⚡ Installing status line...')
115
+ const statusLineResult = await this.installStatusLine()
116
+ if (statusLineResult.success) {
117
+ console.log('✅ Status line configured')
118
+ } else {
119
+ console.log(`⚠️ ${statusLineResult.error}`)
120
+ }
112
121
  }
113
122
 
114
123
  console.log('\n🎉 Setup complete!\n')
@@ -126,6 +135,7 @@ export class SetupCommands extends PrjctCommandsBase {
126
135
  */
127
136
  async installStatusLine(): Promise<{ success: boolean; error?: string }> {
128
137
  try {
138
+ // Note: This method is currently Claude-specific
129
139
  const claudeDir = pathManager.getClaudeDir()
130
140
  const settingsPath = pathManager.getClaudeSettingsPath()
131
141
  const statusLinePath = path.join(claudeDir, 'prjct-statusline.sh')
@@ -1,15 +1,18 @@
1
1
  /**
2
2
  * AI Provider - Multi-agent support for prjct-cli
3
3
  *
4
- * Supports both Claude Code and Gemini CLI with a unified abstraction layer.
5
- * Both agents share similar architectures:
6
- * - Context files: CLAUDE.md / GEMINI.md
7
- * - Skills: Both use SKILL.md format (identical!)
8
- * - Commands: .md (Claude) / .toml (Gemini)
9
- * - MCP: Both support Model Context Protocol
4
+ * Supports multiple AI coding agents with a unified abstraction layer:
5
+ * - Claude Code (CLI): ~/.claude/, CLAUDE.md, .md commands
6
+ * - Gemini CLI (CLI): ~/.gemini/, GEMINI.md, .toml commands
7
+ * - Cursor IDE (GUI): .cursor/ (project-level), .mdc rules
8
+ *
9
+ * Key differences:
10
+ * - CLI providers (Claude/Gemini) have global config directories
11
+ * - Cursor has project-level config only (no ~/.cursor/)
10
12
  *
11
13
  * @see https://geminicli.com/docs/cli/gemini-md/
12
14
  * @see https://geminicli.com/docs/cli/skills/
15
+ * @see https://cursor.com/docs/context/rules
13
16
  */
14
17
 
15
18
  import { execSync } from 'child_process'
@@ -22,6 +25,7 @@ import type {
22
25
  ProviderDetectionResult,
23
26
  ProviderSelectionResult,
24
27
  ProviderBranding,
28
+ CursorProjectDetection,
25
29
  } from '../types/provider'
26
30
 
27
31
  // =============================================================================
@@ -66,12 +70,42 @@ export const GeminiProvider: AIProviderConfig = {
66
70
  docsUrl: 'https://geminicli.com/docs',
67
71
  }
68
72
 
73
+ /**
74
+ * Cursor IDE provider configuration
75
+ *
76
+ * Key differences from Claude/Gemini:
77
+ * - NOT a CLI (GUI app, VS Code fork)
78
+ * - No global config directory (~/.cursor/ doesn't exist)
79
+ * - Project-level config only (.cursor/rules/, .cursor/commands/)
80
+ * - User can select any model (GPT, Claude, Gemini, DeepSeek, etc.)
81
+ *
82
+ * @see https://cursor.com/docs/context/rules
83
+ */
84
+ export const CursorProvider: AIProviderConfig = {
85
+ name: 'cursor',
86
+ displayName: 'Cursor IDE',
87
+ cliCommand: null, // Not a CLI - GUI app
88
+ configDir: null, // No global config directory
89
+ contextFile: 'prjct.mdc', // Uses .mdc format with frontmatter
90
+ skillsDir: null, // No skills directory
91
+ commandsDir: '.cursor/commands',
92
+ rulesDir: '.cursor/rules', // Cursor-specific: rules directory
93
+ commandFormat: 'md',
94
+ settingsFile: null,
95
+ projectSettingsFile: null,
96
+ ignoreFile: '.cursorignore',
97
+ isProjectLevel: true, // Config is project-level only
98
+ websiteUrl: 'https://cursor.com',
99
+ docsUrl: 'https://cursor.com/docs',
100
+ }
101
+
69
102
  /**
70
103
  * All available providers
71
104
  */
72
105
  export const Providers: Record<AIProviderName, AIProviderConfig> = {
73
106
  claude: ClaudeProvider,
74
107
  gemini: GeminiProvider,
108
+ cursor: CursorProvider,
75
109
  }
76
110
 
77
111
  // =============================================================================
@@ -105,10 +139,17 @@ function getCliVersion(command: string): string | null {
105
139
  }
106
140
 
107
141
  /**
108
- * Detect if a specific provider is installed
142
+ * Detect if a specific CLI-based provider is installed
143
+ * Note: Cursor is NOT a CLI, use detectCursorProject() instead
109
144
  */
110
145
  export function detectProvider(provider: AIProviderName): ProviderDetectionResult {
111
146
  const config = Providers[provider]
147
+
148
+ // Cursor is not a CLI - return not installed for CLI detection
149
+ if (!config.cliCommand) {
150
+ return { installed: false }
151
+ }
152
+
112
153
  const cliPath = whichCommand(config.cliCommand)
113
154
 
114
155
  if (!cliPath) {
@@ -125,9 +166,10 @@ export function detectProvider(provider: AIProviderName): ProviderDetectionResul
125
166
  }
126
167
 
127
168
  /**
128
- * Detect all available providers
169
+ * Detect all available CLI-based providers
170
+ * Note: Cursor detection is project-level, use detectCursorProject() separately
129
171
  */
130
- export function detectAllProviders(): Record<AIProviderName, ProviderDetectionResult> {
172
+ export function detectAllProviders(): { claude: ProviderDetectionResult; gemini: ProviderDetectionResult } {
131
173
  return {
132
174
  claude: detectProvider('claude'),
133
175
  gemini: detectProvider('gemini'),
@@ -165,9 +207,13 @@ export function getActiveProvider(projectProvider?: AIProviderName): AIProviderC
165
207
 
166
208
  /**
167
209
  * Check if config directory exists for a provider
210
+ * Returns false for project-level providers (Cursor)
168
211
  */
169
212
  export function hasProviderConfig(provider: AIProviderName): boolean {
170
213
  const config = Providers[provider]
214
+ if (!config.configDir) {
215
+ return false // Cursor has no global config directory
216
+ }
171
217
  return fs.existsSync(config.configDir)
172
218
  }
173
219
 
@@ -189,6 +235,14 @@ Designed for [Gemini](${config.websiteUrl})`,
189
235
  }
190
236
  }
191
237
 
238
+ if (provider === 'cursor') {
239
+ return {
240
+ commitFooter: `🤖 Generated with [p/](https://www.prjct.app/)
241
+ Built with [Cursor](${config.websiteUrl})`,
242
+ signature: '⚡ prjct + Cursor',
243
+ }
244
+ }
245
+
192
246
  // Default: Claude
193
247
  return {
194
248
  commitFooter: `🤖 Generated with [p/](https://www.prjct.app/)
@@ -197,30 +251,75 @@ Designed for [Claude](${config.websiteUrl})`,
197
251
  }
198
252
  }
199
253
 
254
+ // =============================================================================
255
+ // Cursor Project Detection
256
+ // =============================================================================
257
+
258
+ /**
259
+ * Detect if a project is configured for Cursor IDE
260
+ *
261
+ * Cursor has NO global config (~/.cursor/ doesn't exist).
262
+ * Detection is based on project-level .cursor/ directory.
263
+ */
264
+ export function detectCursorProject(projectRoot: string): CursorProjectDetection {
265
+ const cursorDir = path.join(projectRoot, '.cursor')
266
+ const rulesDir = path.join(cursorDir, 'rules')
267
+ const routerPath = path.join(rulesDir, 'prjct.mdc')
268
+
269
+ const detected = fs.existsSync(cursorDir)
270
+ const routerInstalled = fs.existsSync(routerPath)
271
+
272
+ return {
273
+ detected,
274
+ routerInstalled,
275
+ projectRoot: detected ? projectRoot : undefined,
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Check if Cursor routers need to be regenerated
281
+ */
282
+ export function needsCursorRouterRegeneration(projectRoot: string): boolean {
283
+ const detection = detectCursorProject(projectRoot)
284
+
285
+ // Only check if .cursor/ exists (project uses Cursor)
286
+ // and prjct router is missing
287
+ return detection.detected && !detection.routerInstalled
288
+ }
289
+
200
290
  // =============================================================================
201
291
  // Provider Paths
202
292
  // =============================================================================
203
293
 
204
294
  /**
205
295
  * Get full path to global context file
296
+ * Returns null for project-level providers (Cursor)
206
297
  */
207
- export function getGlobalContextPath(provider: AIProviderName): string {
298
+ export function getGlobalContextPath(provider: AIProviderName): string | null {
208
299
  const config = Providers[provider]
300
+ if (!config.configDir) {
301
+ return null // Cursor has no global config
302
+ }
209
303
  return path.join(config.configDir, config.contextFile)
210
304
  }
211
305
 
212
306
  /**
213
307
  * Get full path to global settings file
308
+ * Returns null for project-level providers (Cursor)
214
309
  */
215
- export function getGlobalSettingsPath(provider: AIProviderName): string {
310
+ export function getGlobalSettingsPath(provider: AIProviderName): string | null {
216
311
  const config = Providers[provider]
312
+ if (!config.configDir || !config.settingsFile) {
313
+ return null // Cursor has no global settings
314
+ }
217
315
  return path.join(config.configDir, config.settingsFile)
218
316
  }
219
317
 
220
318
  /**
221
319
  * Get full path to skills directory
320
+ * Returns null for providers without skill support (Cursor)
222
321
  */
223
- export function getSkillsPath(provider: AIProviderName): string {
322
+ export function getSkillsPath(provider: AIProviderName): string | null {
224
323
  return Providers[provider].skillsDir
225
324
  }
226
325
 
@@ -298,6 +397,7 @@ export default {
298
397
  Providers,
299
398
  ClaudeProvider,
300
399
  GeminiProvider,
400
+ CursorProvider,
301
401
  detectProvider,
302
402
  detectAllProviders,
303
403
  getActiveProvider,
@@ -309,4 +409,6 @@ export default {
309
409
  getCommandsDir,
310
410
  getProjectCommandsPath,
311
411
  selectProvider,
412
+ detectCursorProject,
413
+ needsCursorRouterRegeneration,
312
414
  }