prjct-cli 1.5.0 → 1.5.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 +44 -1
- package/bin/prjct.ts +23 -14
- package/core/__tests__/agentic/command-executor.test.ts +19 -19
- package/core/__tests__/agentic/prompt-builder.test.ts +16 -16
- package/core/agentic/command-executor.ts +18 -17
- package/core/agentic/prompt-builder.ts +18 -17
- package/core/agentic/template-executor.ts +2 -2
- package/core/ai-tools/registry.ts +17 -14
- package/core/cli/start.ts +18 -17
- package/core/commands/analysis.ts +1 -1
- package/core/commands/setup.ts +8 -8
- package/core/commands/uninstall.ts +11 -11
- package/core/index.ts +12 -11
- package/core/infrastructure/agent-detector.ts +8 -8
- package/core/infrastructure/ai-provider.ts +49 -37
- package/core/infrastructure/command-installer.ts +18 -10
- package/core/infrastructure/path-manager.ts +4 -4
- package/core/infrastructure/setup.ts +124 -119
- package/core/infrastructure/update-checker.ts +14 -13
- package/core/integrations/linear/sync.ts +4 -4
- package/core/services/hooks-service.ts +78 -68
- package/core/services/sync-service.ts +3 -3
- package/core/utils/fs-helpers.ts +14 -0
- package/core/utils/project-credentials.ts +8 -7
- package/dist/bin/prjct.mjs +683 -625
- package/dist/core/infrastructure/command-installer.js +118 -87
- package/dist/core/infrastructure/setup.js +246 -210
- package/package.json +1 -1
|
@@ -17,10 +17,14 @@
|
|
|
17
17
|
* @see https://docs.windsurf.com/windsurf/cascade/memories
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import {
|
|
21
|
-
import fs from 'node:fs'
|
|
20
|
+
import { exec } from 'node:child_process'
|
|
22
21
|
import os from 'node:os'
|
|
23
22
|
import path from 'node:path'
|
|
23
|
+
import { promisify } from 'node:util'
|
|
24
|
+
import { fileExists } from '../utils/fs-helpers'
|
|
25
|
+
|
|
26
|
+
const execAsync = promisify(exec)
|
|
27
|
+
|
|
24
28
|
import type {
|
|
25
29
|
AIProviderConfig,
|
|
26
30
|
AIProviderName,
|
|
@@ -173,10 +177,10 @@ export const Providers: Record<AIProviderName, AIProviderConfig> = {
|
|
|
173
177
|
/**
|
|
174
178
|
* Check if a CLI command is available
|
|
175
179
|
*/
|
|
176
|
-
function whichCommand(command: string): string | null {
|
|
180
|
+
async function whichCommand(command: string): Promise<string | null> {
|
|
177
181
|
try {
|
|
178
|
-
const
|
|
179
|
-
return
|
|
182
|
+
const { stdout } = await execAsync(`which ${command}`)
|
|
183
|
+
return stdout.trim()
|
|
180
184
|
} catch {
|
|
181
185
|
return null
|
|
182
186
|
}
|
|
@@ -185,12 +189,12 @@ function whichCommand(command: string): string | null {
|
|
|
185
189
|
/**
|
|
186
190
|
* Get CLI version
|
|
187
191
|
*/
|
|
188
|
-
function getCliVersion(command: string): string | null {
|
|
192
|
+
async function getCliVersion(command: string): Promise<string | null> {
|
|
189
193
|
try {
|
|
190
|
-
const
|
|
194
|
+
const { stdout } = await execAsync(`${command} --version`)
|
|
191
195
|
// Extract version number from output (e.g., "claude 1.0.0" -> "1.0.0")
|
|
192
|
-
const match =
|
|
193
|
-
return match ? match[0] :
|
|
196
|
+
const match = stdout.match(/\d+\.\d+\.\d+/)
|
|
197
|
+
return match ? match[0] : stdout.trim()
|
|
194
198
|
} catch {
|
|
195
199
|
return null
|
|
196
200
|
}
|
|
@@ -200,7 +204,7 @@ function getCliVersion(command: string): string | null {
|
|
|
200
204
|
* Detect if a specific CLI-based provider is installed
|
|
201
205
|
* Note: Cursor is NOT a CLI, use detectCursorProject() instead
|
|
202
206
|
*/
|
|
203
|
-
export function detectProvider(provider: AIProviderName): ProviderDetectionResult {
|
|
207
|
+
export async function detectProvider(provider: AIProviderName): Promise<ProviderDetectionResult> {
|
|
204
208
|
const config = Providers[provider]
|
|
205
209
|
|
|
206
210
|
// Cursor is not a CLI - return not installed for CLI detection
|
|
@@ -208,13 +212,13 @@ export function detectProvider(provider: AIProviderName): ProviderDetectionResul
|
|
|
208
212
|
return { installed: false }
|
|
209
213
|
}
|
|
210
214
|
|
|
211
|
-
const cliPath = whichCommand(config.cliCommand)
|
|
215
|
+
const cliPath = await whichCommand(config.cliCommand)
|
|
212
216
|
|
|
213
217
|
if (!cliPath) {
|
|
214
218
|
return { installed: false }
|
|
215
219
|
}
|
|
216
220
|
|
|
217
|
-
const version = getCliVersion(config.cliCommand)
|
|
221
|
+
const version = await getCliVersion(config.cliCommand)
|
|
218
222
|
|
|
219
223
|
return {
|
|
220
224
|
installed: true,
|
|
@@ -227,14 +231,12 @@ export function detectProvider(provider: AIProviderName): ProviderDetectionResul
|
|
|
227
231
|
* Detect all available CLI-based providers
|
|
228
232
|
* Note: Cursor detection is project-level, use detectCursorProject() separately
|
|
229
233
|
*/
|
|
230
|
-
export function detectAllProviders(): {
|
|
234
|
+
export async function detectAllProviders(): Promise<{
|
|
231
235
|
claude: ProviderDetectionResult
|
|
232
236
|
gemini: ProviderDetectionResult
|
|
233
|
-
} {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
gemini: detectProvider('gemini'),
|
|
237
|
-
}
|
|
237
|
+
}> {
|
|
238
|
+
const [claude, gemini] = await Promise.all([detectProvider('claude'), detectProvider('gemini')])
|
|
239
|
+
return { claude, gemini }
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
/**
|
|
@@ -245,14 +247,16 @@ export function detectAllProviders(): {
|
|
|
245
247
|
* 2. Auto-detect single installed provider
|
|
246
248
|
* 3. Default to Claude if both installed (backward compatibility)
|
|
247
249
|
*/
|
|
248
|
-
export function getActiveProvider(
|
|
250
|
+
export async function getActiveProvider(
|
|
251
|
+
projectProvider?: AIProviderName
|
|
252
|
+
): Promise<AIProviderConfig> {
|
|
249
253
|
// If project has a saved preference, use it
|
|
250
254
|
if (projectProvider && Providers[projectProvider]) {
|
|
251
255
|
return Providers[projectProvider]
|
|
252
256
|
}
|
|
253
257
|
|
|
254
258
|
// Auto-detect
|
|
255
|
-
const detection = detectAllProviders()
|
|
259
|
+
const detection = await detectAllProviders()
|
|
256
260
|
|
|
257
261
|
// If only one is installed, use it
|
|
258
262
|
if (detection.claude.installed && !detection.gemini.installed) {
|
|
@@ -270,12 +274,12 @@ export function getActiveProvider(projectProvider?: AIProviderName): AIProviderC
|
|
|
270
274
|
* Check if config directory exists for a provider
|
|
271
275
|
* Returns false for project-level providers (Cursor)
|
|
272
276
|
*/
|
|
273
|
-
export function hasProviderConfig(provider: AIProviderName): boolean {
|
|
277
|
+
export async function hasProviderConfig(provider: AIProviderName): Promise<boolean> {
|
|
274
278
|
const config = Providers[provider]
|
|
275
279
|
if (!config.configDir) {
|
|
276
280
|
return false // Cursor has no global config directory
|
|
277
281
|
}
|
|
278
|
-
return
|
|
282
|
+
return fileExists(config.configDir)
|
|
279
283
|
}
|
|
280
284
|
|
|
281
285
|
// =============================================================================
|
|
@@ -313,13 +317,15 @@ export function getProviderBranding(provider: AIProviderName): ProviderBranding
|
|
|
313
317
|
* Cursor has NO global config (~/.cursor/ doesn't exist).
|
|
314
318
|
* Detection is based on project-level .cursor/ directory.
|
|
315
319
|
*/
|
|
316
|
-
export function detectCursorProject(projectRoot: string): CursorProjectDetection {
|
|
320
|
+
export async function detectCursorProject(projectRoot: string): Promise<CursorProjectDetection> {
|
|
317
321
|
const cursorDir = path.join(projectRoot, '.cursor')
|
|
318
322
|
const rulesDir = path.join(cursorDir, 'rules')
|
|
319
323
|
const routerPath = path.join(rulesDir, 'prjct.mdc')
|
|
320
324
|
|
|
321
|
-
const detected =
|
|
322
|
-
|
|
325
|
+
const [detected, routerInstalled] = await Promise.all([
|
|
326
|
+
fileExists(cursorDir),
|
|
327
|
+
fileExists(routerPath),
|
|
328
|
+
])
|
|
323
329
|
|
|
324
330
|
return {
|
|
325
331
|
detected,
|
|
@@ -331,8 +337,8 @@ export function detectCursorProject(projectRoot: string): CursorProjectDetection
|
|
|
331
337
|
/**
|
|
332
338
|
* Check if Cursor routers need to be regenerated
|
|
333
339
|
*/
|
|
334
|
-
export function needsCursorRouterRegeneration(projectRoot: string): boolean {
|
|
335
|
-
const detection = detectCursorProject(projectRoot)
|
|
340
|
+
export async function needsCursorRouterRegeneration(projectRoot: string): Promise<boolean> {
|
|
341
|
+
const detection = await detectCursorProject(projectRoot)
|
|
336
342
|
|
|
337
343
|
// Only check if .cursor/ exists (project uses Cursor)
|
|
338
344
|
// and prjct router is missing
|
|
@@ -349,13 +355,17 @@ export function needsCursorRouterRegeneration(projectRoot: string): boolean {
|
|
|
349
355
|
* Windsurf has NO global config (~/.windsurf/ doesn't exist).
|
|
350
356
|
* Detection is based on project-level .windsurf/ directory.
|
|
351
357
|
*/
|
|
352
|
-
export function detectWindsurfProject(
|
|
358
|
+
export async function detectWindsurfProject(
|
|
359
|
+
projectRoot: string
|
|
360
|
+
): Promise<WindsurfProjectDetection> {
|
|
353
361
|
const windsurfDir = path.join(projectRoot, '.windsurf')
|
|
354
362
|
const rulesDir = path.join(windsurfDir, 'rules')
|
|
355
363
|
const routerPath = path.join(rulesDir, 'prjct.md')
|
|
356
364
|
|
|
357
|
-
const detected =
|
|
358
|
-
|
|
365
|
+
const [detected, routerInstalled] = await Promise.all([
|
|
366
|
+
fileExists(windsurfDir),
|
|
367
|
+
fileExists(routerPath),
|
|
368
|
+
])
|
|
359
369
|
|
|
360
370
|
return {
|
|
361
371
|
detected,
|
|
@@ -367,8 +377,8 @@ export function detectWindsurfProject(projectRoot: string): WindsurfProjectDetec
|
|
|
367
377
|
/**
|
|
368
378
|
* Check if Windsurf routers need to be regenerated
|
|
369
379
|
*/
|
|
370
|
-
export function needsWindsurfRouterRegeneration(projectRoot: string): boolean {
|
|
371
|
-
const detection = detectWindsurfProject(projectRoot)
|
|
380
|
+
export async function needsWindsurfRouterRegeneration(projectRoot: string): Promise<boolean> {
|
|
381
|
+
const detection = await detectWindsurfProject(projectRoot)
|
|
372
382
|
|
|
373
383
|
// Only check if .windsurf/ exists (project uses Windsurf)
|
|
374
384
|
// and prjct router is missing
|
|
@@ -399,15 +409,17 @@ export interface AntigravityDetection {
|
|
|
399
409
|
* Antigravity is NOT a CLI command - it's a GUI platform.
|
|
400
410
|
* Detection is based on ~/.gemini/antigravity/ directory.
|
|
401
411
|
*/
|
|
402
|
-
export function detectAntigravity(): AntigravityDetection {
|
|
412
|
+
export async function detectAntigravity(): Promise<AntigravityDetection> {
|
|
403
413
|
const configPath = AntigravityProvider.configDir
|
|
404
414
|
if (!configPath) {
|
|
405
415
|
return { installed: false, skillInstalled: false }
|
|
406
416
|
}
|
|
407
417
|
|
|
408
|
-
const installed = fs.existsSync(configPath)
|
|
409
418
|
const skillPath = path.join(configPath, 'skills', 'prjct', 'SKILL.md')
|
|
410
|
-
const skillInstalled =
|
|
419
|
+
const [installed, skillInstalled] = await Promise.all([
|
|
420
|
+
fileExists(configPath),
|
|
421
|
+
fileExists(skillPath),
|
|
422
|
+
])
|
|
411
423
|
|
|
412
424
|
return {
|
|
413
425
|
installed,
|
|
@@ -475,8 +487,8 @@ export function getProjectCommandsPath(provider: AIProviderName, projectRoot: st
|
|
|
475
487
|
* Determine which provider to use during setup
|
|
476
488
|
* Returns selection result with detection details
|
|
477
489
|
*/
|
|
478
|
-
export function selectProvider(): ProviderSelectionResult {
|
|
479
|
-
const detection = detectAllProviders()
|
|
490
|
+
export async function selectProvider(): Promise<ProviderSelectionResult> {
|
|
491
|
+
const detection = await detectAllProviders()
|
|
480
492
|
|
|
481
493
|
const claudeInstalled = detection.claude.installed
|
|
482
494
|
const geminiInstalled = detection.gemini.installed
|
|
@@ -159,11 +159,11 @@ export async function installDocs(): Promise<{ success: boolean; error?: string
|
|
|
159
159
|
*/
|
|
160
160
|
export async function installGlobalConfig(): Promise<GlobalConfigResult> {
|
|
161
161
|
const aiProvider = require('./ai-provider')
|
|
162
|
-
const activeProvider = aiProvider.getActiveProvider()
|
|
162
|
+
const activeProvider = await aiProvider.getActiveProvider()
|
|
163
163
|
const providerName = activeProvider.name
|
|
164
164
|
|
|
165
165
|
// Check if provider is installed
|
|
166
|
-
const detection = aiProvider.detectProvider(providerName)
|
|
166
|
+
const detection = await aiProvider.detectProvider(providerName)
|
|
167
167
|
if (!detection.installed && !activeProvider.configDir) {
|
|
168
168
|
return {
|
|
169
169
|
success: false,
|
|
@@ -289,15 +289,21 @@ export async function installGlobalConfig(): Promise<GlobalConfigResult> {
|
|
|
289
289
|
|
|
290
290
|
export class CommandInstaller {
|
|
291
291
|
homeDir: string
|
|
292
|
-
claudeCommandsPath
|
|
293
|
-
claudeConfigPath
|
|
292
|
+
claudeCommandsPath = ''
|
|
293
|
+
claudeConfigPath = ''
|
|
294
294
|
templatesDir: string
|
|
295
|
+
private _initialized = false
|
|
295
296
|
|
|
296
297
|
constructor() {
|
|
297
298
|
this.homeDir = os.homedir()
|
|
299
|
+
this.templatesDir = path.join(getPackageRoot(), 'templates', 'commands')
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private async ensureInit(): Promise<void> {
|
|
303
|
+
if (this._initialized) return
|
|
298
304
|
|
|
299
305
|
const aiProvider = require('./ai-provider')
|
|
300
|
-
const activeProvider = aiProvider.getActiveProvider()
|
|
306
|
+
const activeProvider = await aiProvider.getActiveProvider()
|
|
301
307
|
|
|
302
308
|
// Command paths are provider-specific
|
|
303
309
|
if (activeProvider.name === 'gemini') {
|
|
@@ -308,13 +314,14 @@ export class CommandInstaller {
|
|
|
308
314
|
}
|
|
309
315
|
|
|
310
316
|
this.claudeConfigPath = activeProvider.configDir
|
|
311
|
-
this.
|
|
317
|
+
this._initialized = true
|
|
312
318
|
}
|
|
313
319
|
|
|
314
320
|
/**
|
|
315
321
|
* Detect if active provider is installed
|
|
316
322
|
*/
|
|
317
323
|
async detectActiveProvider(): Promise<boolean> {
|
|
324
|
+
await this.ensureInit()
|
|
318
325
|
try {
|
|
319
326
|
await fs.access(this.claudeConfigPath)
|
|
320
327
|
return true
|
|
@@ -372,7 +379,7 @@ export class CommandInstaller {
|
|
|
372
379
|
async installCommands(): Promise<InstallResult> {
|
|
373
380
|
const providerDetected = await this.detectActiveProvider()
|
|
374
381
|
const aiProvider = require('./ai-provider')
|
|
375
|
-
const activeProvider = aiProvider.getActiveProvider()
|
|
382
|
+
const activeProvider = await aiProvider.getActiveProvider()
|
|
376
383
|
|
|
377
384
|
if (!providerDetected) {
|
|
378
385
|
return {
|
|
@@ -522,7 +529,8 @@ export class CommandInstaller {
|
|
|
522
529
|
/**
|
|
523
530
|
* Get installation path for Claude commands
|
|
524
531
|
*/
|
|
525
|
-
getInstallPath(): string {
|
|
532
|
+
async getInstallPath(): Promise<string> {
|
|
533
|
+
await this.ensureInit()
|
|
526
534
|
return this.claudeCommandsPath
|
|
527
535
|
}
|
|
528
536
|
|
|
@@ -548,7 +556,7 @@ export class CommandInstaller {
|
|
|
548
556
|
*/
|
|
549
557
|
async installRouter(): Promise<boolean> {
|
|
550
558
|
const aiProvider = require('./ai-provider')
|
|
551
|
-
const activeProvider = aiProvider.getActiveProvider()
|
|
559
|
+
const activeProvider = await aiProvider.getActiveProvider()
|
|
552
560
|
const routerFile = activeProvider.name === 'gemini' ? 'p.toml' : 'p.md'
|
|
553
561
|
|
|
554
562
|
try {
|
|
@@ -575,7 +583,7 @@ export class CommandInstaller {
|
|
|
575
583
|
*/
|
|
576
584
|
async removeLegacyCommands(): Promise<number> {
|
|
577
585
|
const aiProvider = require('./ai-provider')
|
|
578
|
-
const activeProvider = aiProvider.getActiveProvider()
|
|
586
|
+
const activeProvider = await aiProvider.getActiveProvider()
|
|
579
587
|
const commandsRoot = path.join(activeProvider.configDir, 'commands')
|
|
580
588
|
|
|
581
589
|
let removed = 0
|
|
@@ -324,16 +324,16 @@ class PathManager {
|
|
|
324
324
|
* Get the Claude/Gemini directory path (~/.claude or ~/.gemini)
|
|
325
325
|
* Contains AI CLI configuration
|
|
326
326
|
*/
|
|
327
|
-
getAgentDir(): string {
|
|
328
|
-
const provider = require('./ai-provider').getActiveProvider()
|
|
327
|
+
async getAgentDir(): Promise<string> {
|
|
328
|
+
const provider = await require('./ai-provider').getActiveProvider()
|
|
329
329
|
return provider.configDir
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
/**
|
|
333
333
|
* Get the agent settings file path (~/.claude/settings.json or ~/.gemini/settings.json)
|
|
334
334
|
*/
|
|
335
|
-
getAgentSettingsPath(): string {
|
|
336
|
-
const provider = require('./ai-provider').getActiveProvider()
|
|
335
|
+
async getAgentSettingsPath(): Promise<string> {
|
|
336
|
+
const provider = await require('./ai-provider').getActiveProvider()
|
|
337
337
|
const aiProvider = require('./ai-provider')
|
|
338
338
|
return aiProvider.getGlobalSettingsPath(provider.name)
|
|
339
339
|
}
|