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 +41 -0
- package/README.md +25 -24
- package/bin/prjct.ts +13 -1
- package/core/agentic/template-executor.ts +14 -4
- package/core/cli/start.ts +14 -4
- package/core/commands/analysis.ts +6 -3
- package/core/commands/setup.ts +27 -17
- package/core/infrastructure/ai-provider.ts +114 -12
- package/core/infrastructure/command-installer.ts +70 -37
- package/core/infrastructure/path-manager.ts +18 -9
- package/core/infrastructure/setup.ts +132 -3
- package/core/types/provider.ts +44 -20
- package/package.json +1 -1
- package/templates/_bases/tracker-base.md +7 -5
- package/templates/commands/github.md +7 -5
- package/templates/commands/init.md +16 -0
- package/templates/commands/jira.md +8 -6
- package/templates/commands/linear.md +8 -6
- package/templates/commands/monday.md +8 -6
- package/templates/commands/sync.md +11 -1
- package/templates/cursor/p.md +29 -0
- package/templates/cursor/router.mdc +28 -0
- package/templates/global/CURSOR.mdc +233 -0
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.md)
|
|
8
8
|
[]()
|
|
9
|
+
[]()
|
|
9
10
|
[](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
|
-
│
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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:
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
226
|
+
console.log(`📝 Updated ${pathManager.getDisplayPath(globalConfigResult.path!)}`)
|
|
224
227
|
}
|
|
225
228
|
|
|
226
229
|
// Format output
|
package/core/commands/setup.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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(
|
|
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(
|
|
99
|
+
console.log(`✅ Created ${displayPath}`)
|
|
97
100
|
} else if (configResult.action === 'updated') {
|
|
98
|
-
console.log(
|
|
101
|
+
console.log(`✅ Updated ${displayPath}`)
|
|
99
102
|
} else if (configResult.action === 'appended') {
|
|
100
|
-
console.log(
|
|
103
|
+
console.log(`✅ Added prjct config to ${displayPath}`)
|
|
101
104
|
}
|
|
102
105
|
} else {
|
|
103
106
|
console.log(`⚠️ ${configResult.error}`)
|
|
104
107
|
}
|
|
105
108
|
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
console.log(
|
|
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
|
|
5
|
-
*
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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():
|
|
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
|
}
|