prjct-cli 0.35.4 → 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 +84 -0
- package/README.md +64 -618
- package/bin/prjct.ts +128 -17
- package/core/agentic/template-executor.ts +14 -4
- package/core/cli/start.ts +397 -0
- package/core/commands/analysis.ts +6 -3
- package/core/commands/setup.ts +27 -17
- package/core/index.ts +101 -35
- package/core/infrastructure/ai-provider.ts +414 -0
- package/core/infrastructure/command-installer.ts +119 -42
- package/core/infrastructure/editors-config.ts +20 -6
- package/core/infrastructure/path-manager.ts +18 -9
- package/core/infrastructure/setup.ts +356 -62
- package/core/services/skill-service.ts +52 -16
- package/core/types/index.ts +12 -0
- package/core/types/provider.ts +134 -0
- package/core/utils/branding.ts +20 -3
- package/dist/bin/prjct.mjs +1482 -2763
- package/dist/core/infrastructure/command-installer.js +33 -2
- package/dist/core/infrastructure/editors-config.js +13 -3
- package/dist/core/infrastructure/setup.js +293 -73
- package/package.json +9 -9
- package/scripts/postinstall.js +17 -119
- package/templates/_bases/tracker-base.md +7 -5
- package/templates/agents/AGENTS.md +9 -1
- 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/p.md +1 -1
- package/templates/commands/p.toml +37 -0
- package/templates/commands/sync.md +11 -1
- package/templates/cursor/p.md +29 -0
- package/templates/cursor/router.mdc +28 -0
- package/templates/global/CLAUDE.md +33 -1
- package/templates/global/CURSOR.mdc +233 -0
- package/templates/global/GEMINI.md +265 -0
- package/templates/global/STORAGE-SPEC.md +256 -0
- package/templates/global/docs/agents.md +88 -0
- package/templates/global/docs/architecture.md +103 -0
- package/templates/global/docs/commands.md +96 -0
- package/templates/global/docs/validation.md +95 -0
package/bin/prjct.ts
CHANGED
|
@@ -3,17 +3,67 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Auto-setup on first use (like Astro, Vite, etc.)
|
|
5
5
|
* Supports both Bun and Node.js runtimes.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: postinstall.js often doesn't run, so we detect and
|
|
8
|
+
* auto-install on first CLI use. This is the reliable path.
|
|
6
9
|
*/
|
|
7
10
|
|
|
11
|
+
import fs from 'fs'
|
|
12
|
+
import path from 'path'
|
|
13
|
+
import os from 'os'
|
|
8
14
|
import { VERSION } from '../core/utils/version'
|
|
9
15
|
import editorsConfig from '../core/infrastructure/editors-config'
|
|
10
16
|
import { startServer, DEFAULT_PORT } from '../core/server/server'
|
|
11
17
|
import configManager from '../core/infrastructure/config-manager'
|
|
18
|
+
import { detectAllProviders } from '../core/infrastructure/ai-provider'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if routers are installed for detected providers
|
|
22
|
+
* Returns true if at least one provider has its router installed
|
|
23
|
+
*/
|
|
24
|
+
function checkRoutersInstalled(): boolean {
|
|
25
|
+
const home = os.homedir()
|
|
26
|
+
const detection = detectAllProviders()
|
|
27
|
+
|
|
28
|
+
// Check Claude router
|
|
29
|
+
if (detection.claude.installed) {
|
|
30
|
+
const claudeRouter = path.join(home, '.claude', 'commands', 'p.md')
|
|
31
|
+
if (!fs.existsSync(claudeRouter)) {
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check Gemini router
|
|
37
|
+
if (detection.gemini.installed) {
|
|
38
|
+
const geminiRouter = path.join(home, '.gemini', 'commands', 'p.toml')
|
|
39
|
+
if (!fs.existsSync(geminiRouter)) {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// If no providers detected, consider it "installed" (setup will handle)
|
|
45
|
+
if (!detection.claude.installed && !detection.gemini.installed) {
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
12
51
|
|
|
13
52
|
// Check for special subcommands that bypass normal CLI
|
|
14
53
|
const args = process.argv.slice(2)
|
|
15
54
|
|
|
16
|
-
|
|
55
|
+
// Colors for output
|
|
56
|
+
const CYAN = '\x1b[36m'
|
|
57
|
+
const YELLOW = '\x1b[33m'
|
|
58
|
+
const DIM = '\x1b[2m'
|
|
59
|
+
const BOLD = '\x1b[1m'
|
|
60
|
+
const RESET = '\x1b[0m'
|
|
61
|
+
|
|
62
|
+
if (args[0] === 'start' || args[0] === 'setup') {
|
|
63
|
+
// Interactive setup with beautiful UI
|
|
64
|
+
const { runStart } = await import('../core/cli/start')
|
|
65
|
+
await runStart()
|
|
66
|
+
} else if (args[0] === 'dev') {
|
|
17
67
|
// Dev mode - placeholder for future development server
|
|
18
68
|
console.log('Dev mode is not yet implemented.')
|
|
19
69
|
console.log('Use "prjct serve" to start the web server.')
|
|
@@ -35,26 +85,87 @@ if (args[0] === 'dev') {
|
|
|
35
85
|
console.error('Server error:', (error as Error).message)
|
|
36
86
|
process.exitCode = 1
|
|
37
87
|
}
|
|
88
|
+
} else if (args[0] === 'version' || args[0] === '-v' || args[0] === '--version') {
|
|
89
|
+
// Show version with provider status
|
|
90
|
+
const detection = detectAllProviders()
|
|
91
|
+
const home = os.homedir()
|
|
92
|
+
const cwd = process.cwd()
|
|
93
|
+
const claudeConfigured = fs.existsSync(path.join(home, '.claude', 'commands', 'p.md'))
|
|
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'))
|
|
97
|
+
|
|
98
|
+
const GREEN = '\x1b[32m'
|
|
99
|
+
|
|
100
|
+
console.log(`
|
|
101
|
+
${CYAN}p/${RESET} prjct v${VERSION}
|
|
102
|
+
${DIM}Context layer for AI coding agents${RESET}
|
|
103
|
+
|
|
104
|
+
${DIM}Providers:${RESET}`)
|
|
105
|
+
|
|
106
|
+
// Claude status
|
|
107
|
+
if (detection.claude.installed) {
|
|
108
|
+
const status = claudeConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● installed${RESET}`
|
|
109
|
+
const ver = detection.claude.version ? ` (v${detection.claude.version})` : ''
|
|
110
|
+
console.log(` Claude Code ${status}${DIM}${ver}${RESET}`)
|
|
111
|
+
} else {
|
|
112
|
+
console.log(` Claude Code ${DIM}○ not installed${RESET}`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Gemini status
|
|
116
|
+
if (detection.gemini.installed) {
|
|
117
|
+
const status = geminiConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● installed${RESET}`
|
|
118
|
+
const ver = detection.gemini.version ? ` (v${detection.gemini.version})` : ''
|
|
119
|
+
console.log(` Gemini CLI ${status}${DIM}${ver}${RESET}`)
|
|
120
|
+
} else {
|
|
121
|
+
console.log(` Gemini CLI ${DIM}○ not installed${RESET}`)
|
|
122
|
+
}
|
|
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
|
+
|
|
132
|
+
console.log(`
|
|
133
|
+
${DIM}Run 'prjct start' to configure (CLI providers)${RESET}
|
|
134
|
+
${DIM}Run 'prjct init' to configure (Cursor IDE)${RESET}
|
|
135
|
+
${CYAN}https://prjct.app${RESET}
|
|
136
|
+
`)
|
|
38
137
|
} else {
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
138
|
+
// Check if setup has been done
|
|
139
|
+
const configPath = path.join(os.homedir(), '.prjct-cli', 'config', 'installed-editors.json')
|
|
140
|
+
const routersInstalled = checkRoutersInstalled()
|
|
141
|
+
|
|
142
|
+
if (!fs.existsSync(configPath) || !routersInstalled) {
|
|
143
|
+
// First time - prompt to run start
|
|
144
|
+
console.log(`
|
|
145
|
+
${CYAN}${BOLD} Welcome to prjct!${RESET}
|
|
146
|
+
|
|
147
|
+
Run ${BOLD}prjct start${RESET} to configure your AI providers.
|
|
42
148
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
149
|
+
${DIM}This is a one-time setup that lets you choose between
|
|
150
|
+
Claude Code, Gemini CLI, or both.${RESET}
|
|
151
|
+
`)
|
|
152
|
+
process.exitCode = 0
|
|
153
|
+
} else {
|
|
154
|
+
// Check version and auto-update if needed
|
|
155
|
+
try {
|
|
156
|
+
const lastVersion = await editorsConfig.getLastVersion()
|
|
47
157
|
|
|
48
|
-
|
|
49
|
-
|
|
158
|
+
if (lastVersion && lastVersion !== VERSION) {
|
|
159
|
+
console.log(`\n${YELLOW}ℹ${RESET} Updating prjct v${lastVersion} → v${VERSION}...\n`)
|
|
50
160
|
|
|
51
|
-
|
|
161
|
+
const { default: setup } = await import('../core/infrastructure/setup')
|
|
162
|
+
await setup.run()
|
|
163
|
+
}
|
|
164
|
+
} catch (error) {
|
|
165
|
+
// Silent fail on version check
|
|
52
166
|
}
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.error('\n⚠️ Setup warning:', (error as Error).message)
|
|
55
|
-
console.error('You can run setup manually: prjct setup\n')
|
|
56
|
-
}
|
|
57
167
|
|
|
58
|
-
|
|
59
|
-
|
|
168
|
+
// Continue to main CLI logic
|
|
169
|
+
await import('../core/index')
|
|
170
|
+
}
|
|
60
171
|
}
|
|
@@ -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}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prjct start - Global initialization with beautiful UI
|
|
3
|
+
*
|
|
4
|
+
* First-time setup command that:
|
|
5
|
+
* 1. Shows beautiful ASCII banner
|
|
6
|
+
* 2. Detects available AI providers
|
|
7
|
+
* 3. Lets user select which to configure
|
|
8
|
+
* 4. Installs routers and global config
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs'
|
|
12
|
+
import path from 'path'
|
|
13
|
+
import os from 'os'
|
|
14
|
+
import readline from 'readline'
|
|
15
|
+
import { VERSION } from '../utils/version'
|
|
16
|
+
import { detectAllProviders, Providers } from '../infrastructure/ai-provider'
|
|
17
|
+
import type { AIProviderName } from '../types/provider'
|
|
18
|
+
|
|
19
|
+
// Colors
|
|
20
|
+
const RESET = '\x1b[0m'
|
|
21
|
+
const BOLD = '\x1b[1m'
|
|
22
|
+
const DIM = '\x1b[2m'
|
|
23
|
+
const GREEN = '\x1b[32m'
|
|
24
|
+
const YELLOW = '\x1b[33m'
|
|
25
|
+
const BLUE = '\x1b[34m'
|
|
26
|
+
const MAGENTA = '\x1b[35m'
|
|
27
|
+
const CYAN = '\x1b[36m'
|
|
28
|
+
const WHITE = '\x1b[37m'
|
|
29
|
+
const BG_BLUE = '\x1b[44m'
|
|
30
|
+
|
|
31
|
+
// True color gradient (cyan -> blue -> purple -> pink)
|
|
32
|
+
const G1 = '\x1b[38;2;0;255;255m' // Cyan
|
|
33
|
+
const G2 = '\x1b[38;2;80;180;255m' // Sky blue
|
|
34
|
+
const G3 = '\x1b[38;2;140;120;255m' // Blue-purple
|
|
35
|
+
const G4 = '\x1b[38;2;200;80;220m' // Purple
|
|
36
|
+
const G5 = '\x1b[38;2;255;80;180m' // Pink
|
|
37
|
+
|
|
38
|
+
// Large block letters - PRJCT (7 lines tall)
|
|
39
|
+
const BANNER = `
|
|
40
|
+
|
|
41
|
+
${G1} ██████╗ ${G2} ██████╗ ${G3} ██╗${G4} ██████╗${G5}████████╗${RESET}
|
|
42
|
+
${G1} ██╔══██╗${G2} ██╔══██╗${G3} ██║${G4}██╔════╝${G5}╚══██╔══╝${RESET}
|
|
43
|
+
${G1} ██████╔╝${G2} ██████╔╝${G3} ██║${G4}██║ ${G5} ██║ ${RESET}
|
|
44
|
+
${G1} ██╔═══╝ ${G2} ██╔══██╗${G3}██ ██║${G4}██║ ${G5} ██║ ${RESET}
|
|
45
|
+
${G1} ██║ ${G2} ██║ ██║${G3}╚█████╔╝${G4}╚██████╗${G5} ██║ ${RESET}
|
|
46
|
+
${G1} ╚═╝ ${G2} ╚═╝ ╚═╝${G3} ╚════╝ ${G4} ╚═════╝${G5} ╚═╝ ${RESET}
|
|
47
|
+
|
|
48
|
+
`
|
|
49
|
+
|
|
50
|
+
const WELCOME_BOX = ` ${WHITE}Context Layer for AI Agents${RESET} ${DIM}v${VERSION}${RESET}
|
|
51
|
+
|
|
52
|
+
${DIM}Project context layer for AI coding agents.
|
|
53
|
+
Works with Claude Code, Gemini CLI, and more.${RESET}
|
|
54
|
+
${CYAN}https://prjct.app${RESET}
|
|
55
|
+
`
|
|
56
|
+
|
|
57
|
+
interface ProviderOption {
|
|
58
|
+
name: AIProviderName
|
|
59
|
+
displayName: string
|
|
60
|
+
installed: boolean
|
|
61
|
+
selected: boolean
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Create readline interface for user input
|
|
66
|
+
*/
|
|
67
|
+
function createReadline(): readline.Interface {
|
|
68
|
+
return readline.createInterface({
|
|
69
|
+
input: process.stdin,
|
|
70
|
+
output: process.stdout,
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Clear screen and show banner
|
|
76
|
+
*/
|
|
77
|
+
function showBanner(): void {
|
|
78
|
+
// Clear screen
|
|
79
|
+
console.clear()
|
|
80
|
+
console.log(BANNER)
|
|
81
|
+
console.log(WELCOME_BOX)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Show provider selection UI
|
|
86
|
+
*/
|
|
87
|
+
function showProviderSelection(options: ProviderOption[], currentIndex: number): void {
|
|
88
|
+
console.log(`\n${BOLD} Select AI providers to configure:${RESET}\n`)
|
|
89
|
+
console.log(` ${DIM}(Use arrow keys to navigate, space to toggle, enter to confirm)${RESET}\n`)
|
|
90
|
+
|
|
91
|
+
options.forEach((option, index) => {
|
|
92
|
+
const cursor = index === currentIndex ? `${CYAN}❯${RESET}` : ' '
|
|
93
|
+
const checkbox = option.selected ? `${GREEN}[✓]${RESET}` : `${DIM}[ ]${RESET}`
|
|
94
|
+
const status = option.installed
|
|
95
|
+
? `${GREEN}(installed)${RESET}`
|
|
96
|
+
: `${YELLOW}(will install)${RESET}`
|
|
97
|
+
const name = index === currentIndex
|
|
98
|
+
? `${BOLD}${option.displayName}${RESET}`
|
|
99
|
+
: option.displayName
|
|
100
|
+
|
|
101
|
+
console.log(` ${cursor} ${checkbox} ${name} ${status}`)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
console.log('')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Interactive provider selection (with fallback for non-TTY)
|
|
109
|
+
*/
|
|
110
|
+
async function selectProviders(): Promise<AIProviderName[]> {
|
|
111
|
+
const detection = detectAllProviders()
|
|
112
|
+
|
|
113
|
+
const options: ProviderOption[] = [
|
|
114
|
+
{
|
|
115
|
+
name: 'claude',
|
|
116
|
+
displayName: 'Claude Code',
|
|
117
|
+
installed: detection.claude.installed,
|
|
118
|
+
selected: detection.claude.installed,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'gemini',
|
|
122
|
+
displayName: 'Gemini CLI',
|
|
123
|
+
installed: detection.gemini.installed,
|
|
124
|
+
selected: detection.gemini.installed,
|
|
125
|
+
},
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
// If neither installed, select Claude by default
|
|
129
|
+
if (!options.some(o => o.selected)) {
|
|
130
|
+
options[0].selected = true
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Non-interactive mode: auto-select detected providers
|
|
134
|
+
if (!process.stdin.isTTY) {
|
|
135
|
+
console.log(`\n${BOLD} Detected providers:${RESET}\n`)
|
|
136
|
+
options.forEach(option => {
|
|
137
|
+
if (option.installed) {
|
|
138
|
+
console.log(` ${GREEN}✓${RESET} ${option.displayName}`)
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
console.log('')
|
|
142
|
+
return options.filter(o => o.selected).map(o => o.name)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return new Promise((resolve) => {
|
|
146
|
+
let currentIndex = 0
|
|
147
|
+
|
|
148
|
+
const render = () => {
|
|
149
|
+
process.stdout.write('\x1b[8A')
|
|
150
|
+
process.stdout.write('\x1b[0J')
|
|
151
|
+
showProviderSelection(options, currentIndex)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
showProviderSelection(options, currentIndex)
|
|
155
|
+
|
|
156
|
+
process.stdin.setRawMode(true)
|
|
157
|
+
process.stdin.resume()
|
|
158
|
+
process.stdin.setEncoding('utf8')
|
|
159
|
+
|
|
160
|
+
const cleanup = () => {
|
|
161
|
+
process.stdin.setRawMode(false)
|
|
162
|
+
process.stdin.removeListener('data', handleKey)
|
|
163
|
+
process.stdin.pause()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const handleKey = (key: string) => {
|
|
167
|
+
if (key === '\u0003') {
|
|
168
|
+
cleanup()
|
|
169
|
+
console.log('\n Cancelled.\n')
|
|
170
|
+
process.exit(0)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (key === '\r' || key === '\n') {
|
|
174
|
+
cleanup()
|
|
175
|
+
const selected = options.filter(o => o.selected).map(o => o.name)
|
|
176
|
+
resolve(selected.length > 0 ? selected : ['claude'])
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (key === '\x1b[A') {
|
|
181
|
+
currentIndex = Math.max(0, currentIndex - 1)
|
|
182
|
+
render()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (key === '\x1b[B') {
|
|
186
|
+
currentIndex = Math.min(options.length - 1, currentIndex + 1)
|
|
187
|
+
render()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (key === ' ') {
|
|
191
|
+
options[currentIndex].selected = !options[currentIndex].selected
|
|
192
|
+
render()
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
process.stdin.on('data', handleKey)
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Install router for a CLI-based provider (Claude/Gemini)
|
|
202
|
+
* Note: Cursor uses project-level config, not global
|
|
203
|
+
*/
|
|
204
|
+
async function installRouter(provider: AIProviderName): Promise<boolean> {
|
|
205
|
+
const config = Providers[provider]
|
|
206
|
+
|
|
207
|
+
// Skip project-level providers (Cursor)
|
|
208
|
+
if (!config.configDir) {
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
// Create commands directory
|
|
214
|
+
const commandsDir = path.join(config.configDir, 'commands')
|
|
215
|
+
fs.mkdirSync(commandsDir, { recursive: true })
|
|
216
|
+
|
|
217
|
+
// Find package root (where templates are)
|
|
218
|
+
const { getPackageRoot } = await import('../utils/version')
|
|
219
|
+
const packageRoot = getPackageRoot()
|
|
220
|
+
|
|
221
|
+
// Copy router file
|
|
222
|
+
const routerFile = provider === 'claude' ? 'p.md' : 'p.toml'
|
|
223
|
+
const src = path.join(packageRoot, 'templates', 'commands', routerFile)
|
|
224
|
+
const dest = path.join(commandsDir, routerFile)
|
|
225
|
+
|
|
226
|
+
if (fs.existsSync(src)) {
|
|
227
|
+
fs.copyFileSync(src, dest)
|
|
228
|
+
return true
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return false
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error(` ${YELLOW}⚠${RESET} Failed to install ${provider} router: ${(error as Error).message}`)
|
|
234
|
+
return false
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Install global config for a CLI-based provider (Claude/Gemini)
|
|
240
|
+
* Note: Cursor uses project-level config, not global
|
|
241
|
+
*/
|
|
242
|
+
async function installGlobalConfig(provider: AIProviderName): Promise<boolean> {
|
|
243
|
+
const config = Providers[provider]
|
|
244
|
+
|
|
245
|
+
// Skip project-level providers (Cursor)
|
|
246
|
+
if (!config.configDir) {
|
|
247
|
+
return false
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
// Ensure config directory exists
|
|
252
|
+
fs.mkdirSync(config.configDir, { recursive: true })
|
|
253
|
+
|
|
254
|
+
// Find package root
|
|
255
|
+
const { getPackageRoot } = await import('../utils/version')
|
|
256
|
+
const packageRoot = getPackageRoot()
|
|
257
|
+
|
|
258
|
+
// Copy global config
|
|
259
|
+
const configFile = provider === 'claude' ? 'CLAUDE.md' : 'GEMINI.md'
|
|
260
|
+
const src = path.join(packageRoot, 'templates', 'global', configFile)
|
|
261
|
+
const dest = path.join(config.configDir, configFile)
|
|
262
|
+
|
|
263
|
+
if (fs.existsSync(src)) {
|
|
264
|
+
const content = fs.readFileSync(src, 'utf-8')
|
|
265
|
+
|
|
266
|
+
// Check if file exists and has our markers
|
|
267
|
+
if (fs.existsSync(dest)) {
|
|
268
|
+
const existing = fs.readFileSync(dest, 'utf-8')
|
|
269
|
+
const startMarker = '<!-- prjct:start - DO NOT REMOVE THIS MARKER -->'
|
|
270
|
+
const endMarker = '<!-- prjct:end - DO NOT REMOVE THIS MARKER -->'
|
|
271
|
+
|
|
272
|
+
if (existing.includes(startMarker) && existing.includes(endMarker)) {
|
|
273
|
+
// Replace between markers
|
|
274
|
+
const before = existing.substring(0, existing.indexOf(startMarker))
|
|
275
|
+
const after = existing.substring(existing.indexOf(endMarker) + endMarker.length)
|
|
276
|
+
const prjctSection = content.substring(
|
|
277
|
+
content.indexOf(startMarker),
|
|
278
|
+
content.indexOf(endMarker) + endMarker.length
|
|
279
|
+
)
|
|
280
|
+
fs.writeFileSync(dest, before + prjctSection + after)
|
|
281
|
+
} else {
|
|
282
|
+
// Append
|
|
283
|
+
fs.writeFileSync(dest, existing + '\n\n' + content)
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
// Create new
|
|
287
|
+
fs.writeFileSync(dest, content)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return true
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return false
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error(` ${YELLOW}⚠${RESET} Failed to install ${provider} config: ${(error as Error).message}`)
|
|
296
|
+
return false
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Save setup configuration
|
|
302
|
+
*/
|
|
303
|
+
async function saveSetupConfig(providers: AIProviderName[]): Promise<void> {
|
|
304
|
+
const configDir = path.join(os.homedir(), '.prjct-cli', 'config')
|
|
305
|
+
fs.mkdirSync(configDir, { recursive: true })
|
|
306
|
+
|
|
307
|
+
const configPath = path.join(configDir, 'installed-editors.json')
|
|
308
|
+
const config = {
|
|
309
|
+
version: VERSION,
|
|
310
|
+
providers,
|
|
311
|
+
editor: providers[0], // deprecated, for backward compat
|
|
312
|
+
provider: providers[0],
|
|
313
|
+
lastInstall: new Date().toISOString(),
|
|
314
|
+
path: path.join(os.homedir(), `.${providers[0]}`, 'commands'),
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Show completion message
|
|
322
|
+
*/
|
|
323
|
+
function showCompletion(providers: AIProviderName[]): void {
|
|
324
|
+
console.log(`\n${GREEN}${BOLD} ✓ Setup complete!${RESET}\n`)
|
|
325
|
+
|
|
326
|
+
console.log(` ${DIM}Configured providers:${RESET}`)
|
|
327
|
+
providers.forEach(p => {
|
|
328
|
+
const config = Providers[p]
|
|
329
|
+
console.log(` ${GREEN}✓${RESET} ${config.displayName}`)
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
console.log(`
|
|
333
|
+
${BOLD}Next steps:${RESET}
|
|
334
|
+
|
|
335
|
+
${CYAN}1.${RESET} Navigate to your project directory
|
|
336
|
+
${CYAN}2.${RESET} Run ${BOLD}p. init${RESET} to initialize prjct for that project
|
|
337
|
+
${CYAN}3.${RESET} Start tracking with ${BOLD}p. task "your task"${RESET}
|
|
338
|
+
|
|
339
|
+
${DIM}Tips:${RESET}
|
|
340
|
+
${DIM}•${RESET} Use ${BOLD}p. sync${RESET} to analyze your codebase
|
|
341
|
+
${DIM}•${RESET} Use ${BOLD}p. done${RESET} to complete tasks
|
|
342
|
+
${DIM}•${RESET} Use ${BOLD}p. ship${RESET} to create PRs
|
|
343
|
+
|
|
344
|
+
${DIM}Learn more: https://prjct.app/docs${RESET}
|
|
345
|
+
`)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Main start command
|
|
350
|
+
*/
|
|
351
|
+
export async function runStart(): Promise<void> {
|
|
352
|
+
showBanner()
|
|
353
|
+
|
|
354
|
+
// Check if already configured
|
|
355
|
+
const configPath = path.join(os.homedir(), '.prjct-cli', 'config', 'installed-editors.json')
|
|
356
|
+
if (fs.existsSync(configPath)) {
|
|
357
|
+
const existing = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
|
358
|
+
if (existing.version === VERSION) {
|
|
359
|
+
console.log(` ${YELLOW}ℹ${RESET} Already configured for v${VERSION}`)
|
|
360
|
+
console.log(` ${DIM}Run with --force to reconfigure${RESET}\n`)
|
|
361
|
+
|
|
362
|
+
if (!process.argv.includes('--force')) {
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Select providers
|
|
369
|
+
const selectedProviders = await selectProviders()
|
|
370
|
+
|
|
371
|
+
console.log(`\n ${CYAN}Setting up...${RESET}\n`)
|
|
372
|
+
|
|
373
|
+
// Install for each selected provider
|
|
374
|
+
for (const provider of selectedProviders) {
|
|
375
|
+
const config = Providers[provider]
|
|
376
|
+
process.stdout.write(` ${DIM}•${RESET} ${config.displayName}... `)
|
|
377
|
+
|
|
378
|
+
const routerOk = await installRouter(provider)
|
|
379
|
+
const configOk = await installGlobalConfig(provider)
|
|
380
|
+
|
|
381
|
+
if (routerOk && configOk) {
|
|
382
|
+
console.log(`${GREEN}✓${RESET}`)
|
|
383
|
+
} else if (routerOk || configOk) {
|
|
384
|
+
console.log(`${YELLOW}partial${RESET}`)
|
|
385
|
+
} else {
|
|
386
|
+
console.log(`${YELLOW}skipped${RESET}`)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Save configuration
|
|
391
|
+
await saveSetupConfig(selectedProviders)
|
|
392
|
+
|
|
393
|
+
// Show completion
|
|
394
|
+
showCompletion(selectedProviders)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export default { runStart }
|
|
@@ -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
|