prjct-cli 0.37.0 → 0.39.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +84 -37
  3. package/bin/prjct.ts +11 -1
  4. package/core/index.ts +53 -26
  5. package/core/infrastructure/ai-provider.ts +157 -1
  6. package/core/infrastructure/setup.ts +225 -7
  7. package/core/types/provider.ts +18 -1
  8. package/dist/bin/prjct.mjs +3607 -1050
  9. package/dist/core/infrastructure/command-installer.js +458 -47
  10. package/dist/core/infrastructure/setup.js +728 -211
  11. package/package.json +1 -1
  12. package/templates/antigravity/SKILL.md +39 -0
  13. package/templates/cursor/commands/bug.md +8 -0
  14. package/templates/cursor/commands/done.md +4 -0
  15. package/templates/cursor/commands/pause.md +6 -0
  16. package/templates/cursor/commands/resume.md +4 -0
  17. package/templates/cursor/commands/ship.md +8 -0
  18. package/templates/cursor/commands/sync.md +4 -0
  19. package/templates/cursor/commands/task.md +8 -0
  20. package/templates/cursor/router.mdc +6 -6
  21. package/templates/global/ANTIGRAVITY.md +256 -0
  22. package/templates/global/CLAUDE.md +30 -0
  23. package/templates/global/CURSOR.mdc +60 -25
  24. package/templates/global/GEMINI.md +30 -0
  25. package/templates/global/WINDSURF.md +268 -0
  26. package/templates/windsurf/router.md +28 -0
  27. package/templates/windsurf/workflows/bug.md +8 -0
  28. package/templates/windsurf/workflows/done.md +4 -0
  29. package/templates/windsurf/workflows/pause.md +4 -0
  30. package/templates/windsurf/workflows/resume.md +4 -0
  31. package/templates/windsurf/workflows/ship.md +8 -0
  32. package/templates/windsurf/workflows/sync.md +4 -0
  33. package/templates/windsurf/workflows/task.md +8 -0
@@ -29,7 +29,9 @@ import {
29
29
  selectProvider,
30
30
  detectProvider,
31
31
  detectAllProviders,
32
+ detectAntigravity,
32
33
  Providers,
34
+ AntigravityProvider,
33
35
  } from './ai-provider'
34
36
  import type { AIProviderName, AIProviderConfig } from '../types/provider'
35
37
 
@@ -185,6 +187,15 @@ export async function run(): Promise<SetupResults> {
185
187
  results.providers.push(providerResult)
186
188
  }
187
189
 
190
+ // Step 2b: Install for Antigravity if detected (separate from CLI providers)
191
+ const antigravityDetection = detectAntigravity()
192
+ if (antigravityDetection.installed) {
193
+ const antigravityResult = await installAntigravitySkill()
194
+ if (antigravityResult.success) {
195
+ console.log(` ${GREEN}✓${NC} Antigravity skill installed`)
196
+ }
197
+ }
198
+
188
199
  // Step 3: Save version in editors-config
189
200
  await editorsConfig.saveConfig(VERSION, installer.getInstallPath(), selection.provider)
190
201
 
@@ -296,6 +307,55 @@ async function installGeminiGlobalConfig(): Promise<{ success: boolean; action:
296
307
  }
297
308
  }
298
309
 
310
+ // =============================================================================
311
+ // Antigravity Installation (Skills-based)
312
+ // =============================================================================
313
+
314
+ /**
315
+ * Install prjct as a skill for Google Antigravity
316
+ *
317
+ * Antigravity uses SKILL.md files in ~/.gemini/antigravity/skills/
318
+ * This is the recommended integration method (not MCP).
319
+ */
320
+ export async function installAntigravitySkill(): Promise<{ success: boolean; action: string | null }> {
321
+ try {
322
+ const antigravitySkillsDir = path.join(os.homedir(), '.gemini', 'antigravity', 'skills')
323
+ const prjctSkillDir = path.join(antigravitySkillsDir, 'prjct')
324
+ const skillMdPath = path.join(prjctSkillDir, 'SKILL.md')
325
+ const templatePath = path.join(getPackageRoot(), 'templates', 'antigravity', 'SKILL.md')
326
+
327
+ // Ensure skills directory exists
328
+ fs.mkdirSync(prjctSkillDir, { recursive: true })
329
+
330
+ // Check if SKILL.md already exists
331
+ const fileExists = fs.existsSync(skillMdPath)
332
+
333
+ // Read template content
334
+ if (!fs.existsSync(templatePath)) {
335
+ console.error('Antigravity SKILL.md template not found')
336
+ return { success: false, action: null }
337
+ }
338
+
339
+ const templateContent = fs.readFileSync(templatePath, 'utf-8')
340
+
341
+ // Write SKILL.md
342
+ fs.writeFileSync(skillMdPath, templateContent, 'utf-8')
343
+
344
+ return { success: true, action: fileExists ? 'updated' : 'created' }
345
+ } catch (error) {
346
+ console.error(`Antigravity skill warning: ${(error as Error).message}`)
347
+ return { success: false, action: null }
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Check if Antigravity skill needs installation or update
353
+ */
354
+ export function needsAntigravityInstallation(): boolean {
355
+ const detection = detectAntigravity()
356
+ return detection.installed && !detection.skillInstalled
357
+ }
358
+
299
359
  // =============================================================================
300
360
  // Cursor IDE Installation (Project-Level)
301
361
  // =============================================================================
@@ -307,6 +367,7 @@ async function installGeminiGlobalConfig(): Promise<{ success: boolean; action:
307
367
  * configuration in .cursor/rules/ and .cursor/commands/.
308
368
  *
309
369
  * Creates minimal routers that point to the npm package for real instructions.
370
+ * Installs individual command files for better Cursor UX (/sync, /task, etc.)
310
371
  *
311
372
  * @param projectRoot - The project root directory
312
373
  * @returns Object with success status and files created
@@ -330,10 +391,9 @@ export async function installCursorProject(projectRoot: string): Promise<{
330
391
  const commandsDir = path.join(cursorDir, 'commands')
331
392
 
332
393
  const routerMdcDest = path.join(rulesDir, 'prjct.mdc')
333
- const commandRouterDest = path.join(commandsDir, 'p.md')
334
394
 
335
395
  const routerMdcSource = path.join(getPackageRoot(), 'templates', 'cursor', 'router.mdc')
336
- const commandRouterSource = path.join(getPackageRoot(), 'templates', 'cursor', 'p.md')
396
+ const cursorCommandsSource = path.join(getPackageRoot(), 'templates', 'cursor', 'commands')
337
397
 
338
398
  // Ensure directories exist
339
399
  fs.mkdirSync(rulesDir, { recursive: true })
@@ -345,10 +405,18 @@ export async function installCursorProject(projectRoot: string): Promise<{
345
405
  result.rulesCreated = true
346
406
  }
347
407
 
348
- // Copy p.md → .cursor/commands/p.md
349
- if (fs.existsSync(commandRouterSource)) {
350
- fs.copyFileSync(commandRouterSource, commandRouterDest)
351
- result.commandsCreated = true
408
+ // Copy individual command files → .cursor/commands/
409
+ // This enables /sync, /task, /done, /ship, etc. syntax in Cursor
410
+ if (fs.existsSync(cursorCommandsSource)) {
411
+ const commandFiles = fs.readdirSync(cursorCommandsSource)
412
+ .filter(f => f.endsWith('.md'))
413
+
414
+ for (const file of commandFiles) {
415
+ const src = path.join(cursorCommandsSource, file)
416
+ const dest = path.join(commandsDir, file)
417
+ fs.copyFileSync(src, dest)
418
+ }
419
+ result.commandsCreated = commandFiles.length > 0
352
420
  }
353
421
 
354
422
  // Update .gitignore to exclude prjct Cursor routers
@@ -373,7 +441,13 @@ async function addCursorToGitignore(projectRoot: string): Promise<boolean> {
373
441
  const entriesToAdd = [
374
442
  '# prjct Cursor routers (regenerated per-developer)',
375
443
  '.cursor/rules/prjct.mdc',
376
- '.cursor/commands/p.md',
444
+ '.cursor/commands/sync.md',
445
+ '.cursor/commands/task.md',
446
+ '.cursor/commands/done.md',
447
+ '.cursor/commands/ship.md',
448
+ '.cursor/commands/bug.md',
449
+ '.cursor/commands/pause.md',
450
+ '.cursor/commands/resume.md',
377
451
  ]
378
452
 
379
453
  let content = ''
@@ -424,6 +498,150 @@ export function needsCursorRegeneration(projectRoot: string): boolean {
424
498
  return fs.existsSync(cursorDir) && !fs.existsSync(routerPath)
425
499
  }
426
500
 
501
+ // =============================================================================
502
+ // Windsurf IDE Installation (Project-Level)
503
+ // =============================================================================
504
+
505
+ /**
506
+ * Install prjct routers for Windsurf IDE in a project
507
+ *
508
+ * Unlike Claude/Gemini which have global config, Windsurf uses project-level
509
+ * configuration in .windsurf/rules/ and .windsurf/workflows/.
510
+ *
511
+ * Key differences from Cursor:
512
+ * - Uses .md files (not .mdc) with YAML frontmatter
513
+ * - Uses "workflows" directory instead of "commands"
514
+ * - Frontmatter uses `trigger: always_on` instead of `alwaysApply: true`
515
+ *
516
+ * @param projectRoot - The project root directory
517
+ * @returns Object with success status and files created
518
+ */
519
+ export async function installWindsurfProject(projectRoot: string): Promise<{
520
+ success: boolean
521
+ rulesCreated: boolean
522
+ workflowsCreated: boolean
523
+ gitignoreUpdated: boolean
524
+ }> {
525
+ const result = {
526
+ success: false,
527
+ rulesCreated: false,
528
+ workflowsCreated: false,
529
+ gitignoreUpdated: false,
530
+ }
531
+
532
+ try {
533
+ const windsurfDir = path.join(projectRoot, '.windsurf')
534
+ const rulesDir = path.join(windsurfDir, 'rules')
535
+ const workflowsDir = path.join(windsurfDir, 'workflows')
536
+
537
+ const routerDest = path.join(rulesDir, 'prjct.md')
538
+
539
+ const routerSource = path.join(getPackageRoot(), 'templates', 'windsurf', 'router.md')
540
+ const windsurfWorkflowsSource = path.join(getPackageRoot(), 'templates', 'windsurf', 'workflows')
541
+
542
+ // Ensure directories exist
543
+ fs.mkdirSync(rulesDir, { recursive: true })
544
+ fs.mkdirSync(workflowsDir, { recursive: true })
545
+
546
+ // Copy router.md → .windsurf/rules/prjct.md
547
+ if (fs.existsSync(routerSource)) {
548
+ fs.copyFileSync(routerSource, routerDest)
549
+ result.rulesCreated = true
550
+ }
551
+
552
+ // Copy individual workflow files → .windsurf/workflows/
553
+ // This enables /sync, /task, /done, /ship, etc. syntax in Windsurf
554
+ if (fs.existsSync(windsurfWorkflowsSource)) {
555
+ const workflowFiles = fs.readdirSync(windsurfWorkflowsSource)
556
+ .filter(f => f.endsWith('.md'))
557
+
558
+ for (const file of workflowFiles) {
559
+ const src = path.join(windsurfWorkflowsSource, file)
560
+ const dest = path.join(workflowsDir, file)
561
+ fs.copyFileSync(src, dest)
562
+ }
563
+ result.workflowsCreated = workflowFiles.length > 0
564
+ }
565
+
566
+ // Update .gitignore to exclude prjct Windsurf routers
567
+ result.gitignoreUpdated = await addWindsurfToGitignore(projectRoot)
568
+
569
+ result.success = result.rulesCreated || result.workflowsCreated
570
+ return result
571
+ } catch (error) {
572
+ console.error(`Windsurf installation warning: ${(error as Error).message}`)
573
+ return result
574
+ }
575
+ }
576
+
577
+ /**
578
+ * Add Windsurf prjct routers to .gitignore
579
+ *
580
+ * These files are per-developer and regenerated automatically.
581
+ */
582
+ async function addWindsurfToGitignore(projectRoot: string): Promise<boolean> {
583
+ try {
584
+ const gitignorePath = path.join(projectRoot, '.gitignore')
585
+ const entriesToAdd = [
586
+ '# prjct Windsurf routers (regenerated per-developer)',
587
+ '.windsurf/rules/prjct.md',
588
+ '.windsurf/workflows/sync.md',
589
+ '.windsurf/workflows/task.md',
590
+ '.windsurf/workflows/done.md',
591
+ '.windsurf/workflows/ship.md',
592
+ '.windsurf/workflows/bug.md',
593
+ '.windsurf/workflows/pause.md',
594
+ '.windsurf/workflows/resume.md',
595
+ ]
596
+
597
+ let content = ''
598
+ let fileExists = false
599
+
600
+ try {
601
+ content = fs.readFileSync(gitignorePath, 'utf-8')
602
+ fileExists = true
603
+ } catch (error) {
604
+ if (!isNotFoundError(error)) {
605
+ throw error
606
+ }
607
+ }
608
+
609
+ // Check if already added
610
+ if (content.includes('.windsurf/rules/prjct.md')) {
611
+ return false // Already added
612
+ }
613
+
614
+ // Append to .gitignore
615
+ const newContent = fileExists
616
+ ? content.trimEnd() + '\n\n' + entriesToAdd.join('\n') + '\n'
617
+ : entriesToAdd.join('\n') + '\n'
618
+
619
+ fs.writeFileSync(gitignorePath, newContent, 'utf-8')
620
+ return true
621
+ } catch (error) {
622
+ console.error(`Gitignore update warning: ${(error as Error).message}`)
623
+ return false
624
+ }
625
+ }
626
+
627
+ /**
628
+ * Check if a project has Windsurf configured (has .windsurf/ directory)
629
+ */
630
+ export function hasWindsurfProject(projectRoot: string): boolean {
631
+ return fs.existsSync(path.join(projectRoot, '.windsurf'))
632
+ }
633
+
634
+ /**
635
+ * Check if Windsurf routers need regeneration
636
+ */
637
+ export function needsWindsurfRegeneration(projectRoot: string): boolean {
638
+ const windsurfDir = path.join(projectRoot, '.windsurf')
639
+ const routerPath = path.join(windsurfDir, 'rules', 'prjct.md')
640
+
641
+ // Only check if .windsurf/ exists (project uses Windsurf)
642
+ return fs.existsSync(windsurfDir) && !fs.existsSync(routerPath)
643
+ }
644
+
427
645
  /**
428
646
  * Migrate existing projects to add cliVersion field
429
647
  * This clears the status line warning after npm update
@@ -5,19 +5,22 @@
5
5
  * - Claude Code (CLI)
6
6
  * - Gemini CLI (CLI)
7
7
  * - Cursor IDE (GUI, project-level config)
8
+ * - Windsurf IDE (GUI, project-level config)
8
9
  *
9
10
  * Key discovery: Skills use identical SKILL.md format for CLI providers.
10
11
  * Cursor uses .mdc files with frontmatter for rules.
12
+ * Windsurf uses .md files with YAML frontmatter for rules.
11
13
  *
12
14
  * @see https://geminicli.com/docs/cli/gemini-md/
13
15
  * @see https://geminicli.com/docs/cli/skills/
14
16
  * @see https://cursor.com/docs/context/rules
17
+ * @see https://docs.windsurf.com/windsurf/cascade/memories
15
18
  */
16
19
 
17
20
  /**
18
21
  * Supported AI provider names
19
22
  */
20
- export type AIProviderName = 'claude' | 'gemini' | 'cursor'
23
+ export type AIProviderName = 'claude' | 'gemini' | 'cursor' | 'antigravity' | 'windsurf'
21
24
 
22
25
  /**
23
26
  * Command format for each provider
@@ -122,6 +125,20 @@ export interface CursorProjectDetection {
122
125
  projectRoot?: string
123
126
  }
124
127
 
128
+ /**
129
+ * Result of Windsurf project detection
130
+ */
131
+ export interface WindsurfProjectDetection {
132
+ /** Whether .windsurf/ directory exists in project */
133
+ detected: boolean
134
+
135
+ /** Whether prjct router is installed */
136
+ routerInstalled: boolean
137
+
138
+ /** Project root path */
139
+ projectRoot?: string
140
+ }
141
+
125
142
  /**
126
143
  * Provider-aware branding configuration
127
144
  */