prjct-cli 0.35.4 → 0.36.1
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 +43 -0
- package/README.md +63 -618
- package/bin/prjct.ts +116 -17
- package/core/cli/start.ts +387 -0
- package/core/index.ts +101 -35
- package/core/infrastructure/ai-provider.ts +312 -0
- package/core/infrastructure/command-installer.ts +49 -5
- package/core/infrastructure/editors-config.ts +20 -6
- package/core/infrastructure/setup.ts +227 -62
- package/core/services/skill-service.ts +52 -16
- package/core/types/index.ts +12 -0
- package/core/types/provider.ts +110 -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/agents/AGENTS.md +9 -1
- package/templates/commands/p.md +1 -1
- package/templates/commands/p.toml +37 -0
- package/templates/global/CLAUDE.md +33 -1
- 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/core/index.ts
CHANGED
|
@@ -9,6 +9,10 @@ import { commandRegistry } from './commands/registry'
|
|
|
9
9
|
import './commands/register' // Ensure commands are registered
|
|
10
10
|
import out from './utils/output'
|
|
11
11
|
import type { CommandMeta } from './commands/registry'
|
|
12
|
+
import { detectAllProviders, Providers } from './infrastructure/ai-provider'
|
|
13
|
+
import fs from 'fs'
|
|
14
|
+
import path from 'path'
|
|
15
|
+
import os from 'os'
|
|
12
16
|
|
|
13
17
|
interface ParsedCommandArgs {
|
|
14
18
|
parsedArgs: string[]
|
|
@@ -27,7 +31,7 @@ async function main(): Promise<void> {
|
|
|
27
31
|
|
|
28
32
|
if (['-v', '--version', 'version'].includes(commandName)) {
|
|
29
33
|
const packageJson = await import('../package.json')
|
|
30
|
-
|
|
34
|
+
displayVersion(packageJson.version)
|
|
31
35
|
process.exit(0)
|
|
32
36
|
}
|
|
33
37
|
|
|
@@ -175,44 +179,106 @@ function parseCommandArgs(cmd: CommandMeta, rawArgs: string[]): ParsedCommandArg
|
|
|
175
179
|
return { parsedArgs, options }
|
|
176
180
|
}
|
|
177
181
|
|
|
182
|
+
// Colors for version display
|
|
183
|
+
const CYAN = '\x1b[36m'
|
|
184
|
+
const GREEN = '\x1b[32m'
|
|
185
|
+
const YELLOW = '\x1b[33m'
|
|
186
|
+
const DIM = '\x1b[2m'
|
|
187
|
+
const RESET = '\x1b[0m'
|
|
188
|
+
|
|
178
189
|
/**
|
|
179
|
-
* Display
|
|
190
|
+
* Display version with provider status
|
|
180
191
|
*/
|
|
181
|
-
function
|
|
182
|
-
const
|
|
183
|
-
|
|
192
|
+
function displayVersion(version: string): void {
|
|
193
|
+
const detection = detectAllProviders()
|
|
194
|
+
|
|
195
|
+
// Check if prjct commands are installed for each provider
|
|
196
|
+
const claudeCommandPath = path.join(os.homedir(), '.claude', 'commands', 'p.md')
|
|
197
|
+
const geminiCommandPath = path.join(os.homedir(), '.gemini', 'commands', 'p.toml')
|
|
198
|
+
const claudeConfigured = fs.existsSync(claudeCommandPath)
|
|
199
|
+
const geminiConfigured = fs.existsSync(geminiCommandPath)
|
|
200
|
+
|
|
201
|
+
console.log(`
|
|
202
|
+
${CYAN}p/${RESET} prjct v${version}
|
|
203
|
+
${DIM}Context layer for AI coding agents${RESET}
|
|
204
|
+
|
|
205
|
+
${DIM}Providers:${RESET}`)
|
|
206
|
+
|
|
207
|
+
// Claude status
|
|
208
|
+
if (detection.claude.installed) {
|
|
209
|
+
const status = claudeConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● installed${RESET}`
|
|
210
|
+
const ver = detection.claude.version ? ` (v${detection.claude.version})` : ''
|
|
211
|
+
console.log(` Claude Code ${status}${DIM}${ver}${RESET}`)
|
|
212
|
+
} else {
|
|
213
|
+
console.log(` Claude Code ${DIM}○ not installed${RESET}`)
|
|
214
|
+
}
|
|
184
215
|
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
|
|
216
|
+
// Gemini status
|
|
217
|
+
if (detection.gemini.installed) {
|
|
218
|
+
const status = geminiConfigured ? `${GREEN}✓ ready${RESET}` : `${YELLOW}● installed${RESET}`
|
|
219
|
+
const ver = detection.gemini.version ? ` (v${detection.gemini.version})` : ''
|
|
220
|
+
console.log(` Gemini CLI ${status}${DIM}${ver}${RESET}`)
|
|
221
|
+
} else {
|
|
222
|
+
console.log(` Gemini CLI ${DIM}○ not installed${RESET}`)
|
|
223
|
+
}
|
|
188
224
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
225
|
+
console.log(`
|
|
226
|
+
${DIM}Run 'prjct start' to configure providers${RESET}
|
|
227
|
+
${CYAN}https://prjct.app${RESET}
|
|
228
|
+
`)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Display help using registry
|
|
233
|
+
*/
|
|
234
|
+
function displayHelp(): void {
|
|
235
|
+
console.log(`
|
|
236
|
+
prjct - Context layer for AI coding agents
|
|
237
|
+
Works with Claude Code, Gemini CLI, and more.
|
|
238
|
+
|
|
239
|
+
QUICK START
|
|
240
|
+
-----------
|
|
241
|
+
1. prjct start Configure your AI provider (Claude/Gemini)
|
|
242
|
+
2. Open project in your AI coding agent
|
|
243
|
+
3. Type: p. sync Analyze project and generate context
|
|
244
|
+
4. Type: p. task "..." Start working on a task
|
|
245
|
+
|
|
246
|
+
HOW IT WORKS
|
|
247
|
+
------------
|
|
248
|
+
prjct gives AI agents the context they need about your project.
|
|
249
|
+
Use "p." commands inside Claude Code or Gemini CLI:
|
|
250
|
+
|
|
251
|
+
p. sync Analyze project, generate domain agents
|
|
252
|
+
p. task "desc" Start task with auto-classification
|
|
253
|
+
p. done Complete current subtask
|
|
254
|
+
p. ship "name" Ship feature with PR + version
|
|
255
|
+
|
|
256
|
+
TERMINAL COMMANDS (this CLI)
|
|
257
|
+
----------------------------
|
|
258
|
+
prjct start First-time setup
|
|
259
|
+
prjct setup Reconfigure installations
|
|
260
|
+
prjct init Initialize project (creates .prjct/)
|
|
261
|
+
prjct sync Sync project state
|
|
262
|
+
|
|
263
|
+
EXAMPLES
|
|
264
|
+
--------
|
|
265
|
+
# First time setup
|
|
266
|
+
$ prjct start
|
|
267
|
+
|
|
268
|
+
# Initialize a new project
|
|
269
|
+
$ cd my-project && prjct init
|
|
270
|
+
|
|
271
|
+
# Inside Claude Code or Gemini CLI
|
|
272
|
+
> p. sync
|
|
273
|
+
> p. task "add user authentication"
|
|
274
|
+
> p. done
|
|
275
|
+
> p. ship "user auth"
|
|
276
|
+
|
|
277
|
+
MORE INFO
|
|
278
|
+
---------
|
|
279
|
+
Documentation: https://prjct.app
|
|
280
|
+
GitHub: https://github.com/jlopezlira/prjct-cli
|
|
281
|
+
`)
|
|
216
282
|
}
|
|
217
283
|
|
|
218
284
|
// Run CLI
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Provider - Multi-agent support for prjct-cli
|
|
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
|
|
10
|
+
*
|
|
11
|
+
* @see https://geminicli.com/docs/cli/gemini-md/
|
|
12
|
+
* @see https://geminicli.com/docs/cli/skills/
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { execSync } from 'child_process'
|
|
16
|
+
import fs from 'fs'
|
|
17
|
+
import path from 'path'
|
|
18
|
+
import os from 'os'
|
|
19
|
+
import type {
|
|
20
|
+
AIProviderName,
|
|
21
|
+
AIProviderConfig,
|
|
22
|
+
ProviderDetectionResult,
|
|
23
|
+
ProviderSelectionResult,
|
|
24
|
+
ProviderBranding,
|
|
25
|
+
} from '../types/provider'
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Provider Configurations
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Claude Code provider configuration
|
|
33
|
+
*/
|
|
34
|
+
export const ClaudeProvider: AIProviderConfig = {
|
|
35
|
+
name: 'claude',
|
|
36
|
+
displayName: 'Claude Code',
|
|
37
|
+
cliCommand: 'claude',
|
|
38
|
+
configDir: path.join(os.homedir(), '.claude'),
|
|
39
|
+
contextFile: 'CLAUDE.md',
|
|
40
|
+
skillsDir: path.join(os.homedir(), '.claude', 'skills'),
|
|
41
|
+
commandsDir: '.claude/commands',
|
|
42
|
+
commandFormat: 'md',
|
|
43
|
+
settingsFile: 'settings.json',
|
|
44
|
+
projectSettingsFile: 'settings.local.json',
|
|
45
|
+
ignoreFile: '.claudeignore',
|
|
46
|
+
websiteUrl: 'https://www.anthropic.com/claude',
|
|
47
|
+
docsUrl: 'https://docs.anthropic.com/claude-code',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Gemini CLI provider configuration
|
|
52
|
+
*/
|
|
53
|
+
export const GeminiProvider: AIProviderConfig = {
|
|
54
|
+
name: 'gemini',
|
|
55
|
+
displayName: 'Gemini CLI',
|
|
56
|
+
cliCommand: 'gemini',
|
|
57
|
+
configDir: path.join(os.homedir(), '.gemini'),
|
|
58
|
+
contextFile: 'GEMINI.md',
|
|
59
|
+
skillsDir: path.join(os.homedir(), '.gemini', 'skills'),
|
|
60
|
+
commandsDir: '.gemini/commands',
|
|
61
|
+
commandFormat: 'toml',
|
|
62
|
+
settingsFile: 'settings.json',
|
|
63
|
+
projectSettingsFile: 'settings.json',
|
|
64
|
+
ignoreFile: '.geminiignore',
|
|
65
|
+
websiteUrl: 'https://geminicli.com',
|
|
66
|
+
docsUrl: 'https://geminicli.com/docs',
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* All available providers
|
|
71
|
+
*/
|
|
72
|
+
export const Providers: Record<AIProviderName, AIProviderConfig> = {
|
|
73
|
+
claude: ClaudeProvider,
|
|
74
|
+
gemini: GeminiProvider,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// =============================================================================
|
|
78
|
+
// Provider Detection
|
|
79
|
+
// =============================================================================
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if a CLI command is available
|
|
83
|
+
*/
|
|
84
|
+
function whichCommand(command: string): string | null {
|
|
85
|
+
try {
|
|
86
|
+
const result = execSync(`which ${command}`, { stdio: 'pipe', encoding: 'utf-8' })
|
|
87
|
+
return result.trim()
|
|
88
|
+
} catch {
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get CLI version
|
|
95
|
+
*/
|
|
96
|
+
function getCliVersion(command: string): string | null {
|
|
97
|
+
try {
|
|
98
|
+
const result = execSync(`${command} --version`, { stdio: 'pipe', encoding: 'utf-8' })
|
|
99
|
+
// Extract version number from output (e.g., "claude 1.0.0" -> "1.0.0")
|
|
100
|
+
const match = result.match(/\d+\.\d+\.\d+/)
|
|
101
|
+
return match ? match[0] : result.trim()
|
|
102
|
+
} catch {
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Detect if a specific provider is installed
|
|
109
|
+
*/
|
|
110
|
+
export function detectProvider(provider: AIProviderName): ProviderDetectionResult {
|
|
111
|
+
const config = Providers[provider]
|
|
112
|
+
const cliPath = whichCommand(config.cliCommand)
|
|
113
|
+
|
|
114
|
+
if (!cliPath) {
|
|
115
|
+
return { installed: false }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const version = getCliVersion(config.cliCommand)
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
installed: true,
|
|
122
|
+
version: version || undefined,
|
|
123
|
+
path: cliPath,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Detect all available providers
|
|
129
|
+
*/
|
|
130
|
+
export function detectAllProviders(): Record<AIProviderName, ProviderDetectionResult> {
|
|
131
|
+
return {
|
|
132
|
+
claude: detectProvider('claude'),
|
|
133
|
+
gemini: detectProvider('gemini'),
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get the active provider based on detection or configuration
|
|
139
|
+
*
|
|
140
|
+
* Priority:
|
|
141
|
+
* 1. Check project config for saved provider preference
|
|
142
|
+
* 2. Auto-detect single installed provider
|
|
143
|
+
* 3. Default to Claude if both installed (backward compatibility)
|
|
144
|
+
*/
|
|
145
|
+
export function getActiveProvider(projectProvider?: AIProviderName): AIProviderConfig {
|
|
146
|
+
// If project has a saved preference, use it
|
|
147
|
+
if (projectProvider && Providers[projectProvider]) {
|
|
148
|
+
return Providers[projectProvider]
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Auto-detect
|
|
152
|
+
const detection = detectAllProviders()
|
|
153
|
+
|
|
154
|
+
// If only one is installed, use it
|
|
155
|
+
if (detection.claude.installed && !detection.gemini.installed) {
|
|
156
|
+
return ClaudeProvider
|
|
157
|
+
}
|
|
158
|
+
if (detection.gemini.installed && !detection.claude.installed) {
|
|
159
|
+
return GeminiProvider
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Default to Claude for backward compatibility
|
|
163
|
+
return ClaudeProvider
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check if config directory exists for a provider
|
|
168
|
+
*/
|
|
169
|
+
export function hasProviderConfig(provider: AIProviderName): boolean {
|
|
170
|
+
const config = Providers[provider]
|
|
171
|
+
return fs.existsSync(config.configDir)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// =============================================================================
|
|
175
|
+
// Provider Branding
|
|
176
|
+
// =============================================================================
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get provider-specific branding
|
|
180
|
+
*/
|
|
181
|
+
export function getProviderBranding(provider: AIProviderName): ProviderBranding {
|
|
182
|
+
const config = Providers[provider]
|
|
183
|
+
|
|
184
|
+
if (provider === 'gemini') {
|
|
185
|
+
return {
|
|
186
|
+
commitFooter: `🤖 Generated with [p/](https://www.prjct.app/)
|
|
187
|
+
Designed for [Gemini](${config.websiteUrl})`,
|
|
188
|
+
signature: '⚡ prjct + Gemini',
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Default: Claude
|
|
193
|
+
return {
|
|
194
|
+
commitFooter: `🤖 Generated with [p/](https://www.prjct.app/)
|
|
195
|
+
Designed for [Claude](${config.websiteUrl})`,
|
|
196
|
+
signature: '⚡ prjct + Claude',
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// =============================================================================
|
|
201
|
+
// Provider Paths
|
|
202
|
+
// =============================================================================
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get full path to global context file
|
|
206
|
+
*/
|
|
207
|
+
export function getGlobalContextPath(provider: AIProviderName): string {
|
|
208
|
+
const config = Providers[provider]
|
|
209
|
+
return path.join(config.configDir, config.contextFile)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get full path to global settings file
|
|
214
|
+
*/
|
|
215
|
+
export function getGlobalSettingsPath(provider: AIProviderName): string {
|
|
216
|
+
const config = Providers[provider]
|
|
217
|
+
return path.join(config.configDir, config.settingsFile)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get full path to skills directory
|
|
222
|
+
*/
|
|
223
|
+
export function getSkillsPath(provider: AIProviderName): string {
|
|
224
|
+
return Providers[provider].skillsDir
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get commands directory relative to project root
|
|
229
|
+
*/
|
|
230
|
+
export function getCommandsDir(provider: AIProviderName): string {
|
|
231
|
+
return Providers[provider].commandsDir
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get full path to commands directory in a project
|
|
236
|
+
*/
|
|
237
|
+
export function getProjectCommandsPath(provider: AIProviderName, projectRoot: string): string {
|
|
238
|
+
const config = Providers[provider]
|
|
239
|
+
return path.join(projectRoot, config.commandsDir)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// =============================================================================
|
|
243
|
+
// Provider Selection (for setup)
|
|
244
|
+
// =============================================================================
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Determine which provider to use during setup
|
|
248
|
+
* Returns selection result with detection details
|
|
249
|
+
*/
|
|
250
|
+
export function selectProvider(): ProviderSelectionResult {
|
|
251
|
+
const detection = detectAllProviders()
|
|
252
|
+
|
|
253
|
+
const claudeInstalled = detection.claude.installed
|
|
254
|
+
const geminiInstalled = detection.gemini.installed
|
|
255
|
+
|
|
256
|
+
// Neither installed
|
|
257
|
+
if (!claudeInstalled && !geminiInstalled) {
|
|
258
|
+
// Default to Claude, setup will prompt to install
|
|
259
|
+
return {
|
|
260
|
+
provider: 'claude',
|
|
261
|
+
userSelected: false,
|
|
262
|
+
detection,
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Only Claude installed
|
|
267
|
+
if (claudeInstalled && !geminiInstalled) {
|
|
268
|
+
return {
|
|
269
|
+
provider: 'claude',
|
|
270
|
+
userSelected: false,
|
|
271
|
+
detection,
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Only Gemini installed
|
|
276
|
+
if (geminiInstalled && !claudeInstalled) {
|
|
277
|
+
return {
|
|
278
|
+
provider: 'gemini',
|
|
279
|
+
userSelected: false,
|
|
280
|
+
detection,
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Both installed - will need user selection
|
|
285
|
+
// For now, default to Claude (caller should prompt user)
|
|
286
|
+
return {
|
|
287
|
+
provider: 'claude',
|
|
288
|
+
userSelected: true, // Indicates user should be prompted
|
|
289
|
+
detection,
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// =============================================================================
|
|
294
|
+
// Exports
|
|
295
|
+
// =============================================================================
|
|
296
|
+
|
|
297
|
+
export default {
|
|
298
|
+
Providers,
|
|
299
|
+
ClaudeProvider,
|
|
300
|
+
GeminiProvider,
|
|
301
|
+
detectProvider,
|
|
302
|
+
detectAllProviders,
|
|
303
|
+
getActiveProvider,
|
|
304
|
+
hasProviderConfig,
|
|
305
|
+
getProviderBranding,
|
|
306
|
+
getGlobalContextPath,
|
|
307
|
+
getGlobalSettingsPath,
|
|
308
|
+
getSkillsPath,
|
|
309
|
+
getCommandsDir,
|
|
310
|
+
getProjectCommandsPath,
|
|
311
|
+
selectProvider,
|
|
312
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Command Installer
|
|
3
|
-
* Installs prjct commands in Claude Code and other
|
|
3
|
+
* Installs prjct commands in Claude Code and other AI CLI agents.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* to
|
|
5
|
+
* Architecture:
|
|
6
|
+
* - Claude: Full command sync to ~/.claude/commands/p/ (workaround for bug #2422)
|
|
7
|
+
* - Gemini: Simple router (p.toml) to ~/.gemini/commands/ (handled by setup.ts)
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* This module handles the more complex Claude installation.
|
|
10
|
+
* For Gemini, see setup.ts::installGeminiRouter()
|
|
11
|
+
*
|
|
12
|
+
* @version 0.6.0 - Multi-provider support
|
|
10
13
|
*/
|
|
11
14
|
|
|
12
15
|
import fs from 'fs/promises'
|
|
@@ -522,6 +525,47 @@ export class CommandInstaller {
|
|
|
522
525
|
}
|
|
523
526
|
}
|
|
524
527
|
|
|
528
|
+
// =============================================================================
|
|
529
|
+
// Multi-Provider Support
|
|
530
|
+
// =============================================================================
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Get installation paths for all providers
|
|
534
|
+
*/
|
|
535
|
+
export function getProviderPaths(): {
|
|
536
|
+
claude: { commands: string; config: string; router: string }
|
|
537
|
+
gemini: { commands: string; config: string; router: string }
|
|
538
|
+
} {
|
|
539
|
+
const homeDir = os.homedir()
|
|
540
|
+
return {
|
|
541
|
+
claude: {
|
|
542
|
+
commands: path.join(homeDir, '.claude', 'commands', 'p'),
|
|
543
|
+
config: path.join(homeDir, '.claude'),
|
|
544
|
+
router: path.join(homeDir, '.claude', 'commands', 'p.md'),
|
|
545
|
+
},
|
|
546
|
+
gemini: {
|
|
547
|
+
commands: path.join(homeDir, '.gemini', 'commands'),
|
|
548
|
+
config: path.join(homeDir, '.gemini'),
|
|
549
|
+
router: path.join(homeDir, '.gemini', 'commands', 'p.toml'),
|
|
550
|
+
},
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Check if provider router is installed
|
|
556
|
+
*/
|
|
557
|
+
export async function isRouterInstalled(provider: 'claude' | 'gemini'): Promise<boolean> {
|
|
558
|
+
const paths = getProviderPaths()
|
|
559
|
+
const routerPath = paths[provider].router
|
|
560
|
+
|
|
561
|
+
try {
|
|
562
|
+
await fs.access(routerPath)
|
|
563
|
+
return true
|
|
564
|
+
} catch {
|
|
565
|
+
return false
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
525
569
|
// =============================================================================
|
|
526
570
|
// Exports
|
|
527
571
|
// =============================================================================
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* EditorsConfig - Manages
|
|
2
|
+
* EditorsConfig - Manages AI CLI installation tracking
|
|
3
3
|
*
|
|
4
|
-
* Tracks prjct commands installation in Claude
|
|
4
|
+
* Tracks prjct commands installation in AI CLIs (Claude Code, Gemini CLI),
|
|
5
5
|
* enabling automatic updates when npm package is updated.
|
|
6
6
|
*
|
|
7
7
|
* Config location: ~/.prjct-cli/config/installed-editors.json
|
|
8
8
|
*
|
|
9
|
-
* @version 0.
|
|
9
|
+
* @version 0.6.0
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import fs from 'fs/promises'
|
|
13
13
|
import path from 'path'
|
|
14
14
|
import os from 'os'
|
|
15
|
+
import type { AIProviderName } from '../types/provider'
|
|
15
16
|
|
|
16
17
|
interface EditorConfig {
|
|
17
18
|
version: string
|
|
19
|
+
/** @deprecated Use 'provider' instead */
|
|
18
20
|
editor: string
|
|
21
|
+
/** AI provider name (claude or gemini) */
|
|
22
|
+
provider: AIProviderName
|
|
19
23
|
lastInstall: string
|
|
20
24
|
path: string
|
|
21
25
|
}
|
|
@@ -61,15 +65,16 @@ class EditorsConfig {
|
|
|
61
65
|
/**
|
|
62
66
|
* Save installation configuration
|
|
63
67
|
*/
|
|
64
|
-
async saveConfig(version: string,
|
|
68
|
+
async saveConfig(version: string, installPath: string, provider: AIProviderName = 'claude'): Promise<boolean> {
|
|
65
69
|
try {
|
|
66
70
|
await this.ensureConfigDir()
|
|
67
71
|
|
|
68
72
|
const config: EditorConfig = {
|
|
69
73
|
version,
|
|
70
|
-
editor:
|
|
74
|
+
editor: provider, // deprecated, kept for backward compatibility
|
|
75
|
+
provider,
|
|
71
76
|
lastInstall: new Date().toISOString(),
|
|
72
|
-
path:
|
|
77
|
+
path: installPath,
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
await fs.writeFile(this.configFile, JSON.stringify(config, null, 2), 'utf-8')
|
|
@@ -81,6 +86,15 @@ class EditorsConfig {
|
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Get the configured provider
|
|
91
|
+
*/
|
|
92
|
+
async getProvider(): Promise<AIProviderName | null> {
|
|
93
|
+
const config = await this.loadConfig()
|
|
94
|
+
if (!config) return null
|
|
95
|
+
return config.provider || (config.editor as AIProviderName) || 'claude'
|
|
96
|
+
}
|
|
97
|
+
|
|
84
98
|
/**
|
|
85
99
|
* Get last installed version
|
|
86
100
|
*/
|