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.
@@ -18,7 +18,7 @@
18
18
  */
19
19
 
20
20
  import { execSync } from 'node:child_process'
21
- import fs from 'node:fs'
21
+ import fs from 'node:fs/promises'
22
22
  import os from 'node:os'
23
23
  import path from 'node:path'
24
24
  import chalk from 'chalk'
@@ -26,6 +26,7 @@ import { getTimeout } from '../constants'
26
26
  import { dependencyValidator } from '../services/dependency-validator'
27
27
  import { isNotFoundError } from '../types/fs'
28
28
  import type { AIProviderConfig, AIProviderName } from '../types/provider'
29
+ import { fileExists } from '../utils/fs-helpers'
29
30
  import { getPackageRoot, VERSION } from '../utils/version'
30
31
  import {
31
32
  detectAllProviders,
@@ -58,7 +59,7 @@ interface SetupResults {
58
59
  * Check if an AI CLI is installed
59
60
  */
60
61
  async function _hasAICLI(provider: AIProviderConfig): Promise<boolean> {
61
- const detection = detectProvider(provider.name)
62
+ const detection = await detectProvider(provider.name)
62
63
  return detection.installed
63
64
  }
64
65
 
@@ -129,8 +130,8 @@ async function installAICLI(provider: AIProviderConfig): Promise<boolean> {
129
130
  */
130
131
  export async function run(): Promise<SetupResults> {
131
132
  // Step 0: Detect all available providers
132
- const detection = detectAllProviders()
133
- const selection = selectProvider()
133
+ const detection = await detectAllProviders()
134
+ const selection = await selectProvider()
134
135
  const _primaryProvider = Providers[selection.provider]
135
136
 
136
137
  const results: SetupResults = {
@@ -225,7 +226,7 @@ export async function run(): Promise<SetupResults> {
225
226
  }
226
227
 
227
228
  // Step 2b: Install for Antigravity if detected (separate from CLI providers)
228
- const antigravityDetection = detectAntigravity()
229
+ const antigravityDetection = await detectAntigravity()
229
230
  if (antigravityDetection.installed) {
230
231
  const antigravityResult = await installAntigravitySkill()
231
232
  if (antigravityResult.success) {
@@ -234,7 +235,7 @@ export async function run(): Promise<SetupResults> {
234
235
  }
235
236
 
236
237
  // Step 3: Save version in editors-config
237
- await editorsConfig.saveConfig(VERSION, installer.getInstallPath(), selection.provider)
238
+ await editorsConfig.saveConfig(VERSION, await installer.getInstallPath(), selection.provider)
238
239
 
239
240
  // Step 4: Migrate existing projects to add cliVersion
240
241
  await migrateProjectsCliVersion()
@@ -260,11 +261,11 @@ async function installGeminiRouter(): Promise<boolean> {
260
261
  const routerDest = path.join(geminiCommandsDir, 'p.toml')
261
262
 
262
263
  // Ensure commands directory exists
263
- fs.mkdirSync(geminiCommandsDir, { recursive: true })
264
+ await fs.mkdir(geminiCommandsDir, { recursive: true })
264
265
 
265
266
  // Copy router
266
- if (fs.existsSync(routerSource)) {
267
- fs.copyFileSync(routerSource, routerDest)
267
+ if (await fileExists(routerSource)) {
268
+ await fs.copyFile(routerSource, routerDest)
268
269
  return true
269
270
  }
270
271
  return false
@@ -284,29 +285,29 @@ async function installGeminiGlobalConfig(): Promise<{ success: boolean; action:
284
285
  const templatePath = path.join(getPackageRoot(), 'templates', 'global', 'GEMINI.md')
285
286
 
286
287
  // Ensure ~/.gemini directory exists
287
- fs.mkdirSync(geminiDir, { recursive: true })
288
+ await fs.mkdir(geminiDir, { recursive: true })
288
289
 
289
290
  // Read template content
290
- const templateContent = fs.readFileSync(templatePath, 'utf-8')
291
+ const templateContent = await fs.readFile(templatePath, 'utf-8')
291
292
 
292
293
  // Check if global config already exists
293
294
  let existingContent = ''
294
- let fileExists = false
295
+ let configExists = false
295
296
 
296
297
  try {
297
- existingContent = fs.readFileSync(globalConfigPath, 'utf-8')
298
- fileExists = true
298
+ existingContent = await fs.readFile(globalConfigPath, 'utf-8')
299
+ configExists = true
299
300
  } catch (error) {
300
301
  if (isNotFoundError(error)) {
301
- fileExists = false
302
+ configExists = false
302
303
  } else {
303
304
  throw error
304
305
  }
305
306
  }
306
307
 
307
- if (!fileExists) {
308
+ if (!configExists) {
308
309
  // Create new file with full template
309
- fs.writeFileSync(globalConfigPath, templateContent, 'utf-8')
310
+ await fs.writeFile(globalConfigPath, templateContent, 'utf-8')
310
311
  return { success: true, action: 'created' }
311
312
  }
312
313
 
@@ -319,7 +320,7 @@ async function installGeminiGlobalConfig(): Promise<{ success: boolean; action:
319
320
  if (!hasMarkers) {
320
321
  // No markers - append prjct section at the end
321
322
  const updatedContent = `${existingContent}\n\n${templateContent}`
322
- fs.writeFileSync(globalConfigPath, updatedContent, 'utf-8')
323
+ await fs.writeFile(globalConfigPath, updatedContent, 'utf-8')
323
324
  return { success: true, action: 'appended' }
324
325
  }
325
326
 
@@ -336,7 +337,7 @@ async function installGeminiGlobalConfig(): Promise<{ success: boolean; action:
336
337
  )
337
338
 
338
339
  const updatedContent = beforeMarker + prjctSection + afterMarker
339
- fs.writeFileSync(globalConfigPath, updatedContent, 'utf-8')
340
+ await fs.writeFile(globalConfigPath, updatedContent, 'utf-8')
340
341
  return { success: true, action: 'updated' }
341
342
  } catch (error) {
342
343
  console.error(`Gemini config warning: ${(error as Error).message}`)
@@ -365,23 +366,23 @@ export async function installAntigravitySkill(): Promise<{
365
366
  const templatePath = path.join(getPackageRoot(), 'templates', 'antigravity', 'SKILL.md')
366
367
 
367
368
  // Ensure skills directory exists
368
- fs.mkdirSync(prjctSkillDir, { recursive: true })
369
+ await fs.mkdir(prjctSkillDir, { recursive: true })
369
370
 
370
371
  // Check if SKILL.md already exists
371
- const fileExists = fs.existsSync(skillMdPath)
372
+ const skillExists = await fileExists(skillMdPath)
372
373
 
373
374
  // Read template content
374
- if (!fs.existsSync(templatePath)) {
375
+ if (!(await fileExists(templatePath))) {
375
376
  console.error('Antigravity SKILL.md template not found')
376
377
  return { success: false, action: null }
377
378
  }
378
379
 
379
- const templateContent = fs.readFileSync(templatePath, 'utf-8')
380
+ const templateContent = await fs.readFile(templatePath, 'utf-8')
380
381
 
381
382
  // Write SKILL.md
382
- fs.writeFileSync(skillMdPath, templateContent, 'utf-8')
383
+ await fs.writeFile(skillMdPath, templateContent, 'utf-8')
383
384
 
384
- return { success: true, action: fileExists ? 'updated' : 'created' }
385
+ return { success: true, action: skillExists ? 'updated' : 'created' }
385
386
  } catch (error) {
386
387
  console.error(`Antigravity skill warning: ${(error as Error).message}`)
387
388
  return { success: false, action: null }
@@ -391,8 +392,8 @@ export async function installAntigravitySkill(): Promise<{
391
392
  /**
392
393
  * Check if Antigravity skill needs installation or update
393
394
  */
394
- export function needsAntigravityInstallation(): boolean {
395
- const detection = detectAntigravity()
395
+ export async function needsAntigravityInstallation(): Promise<boolean> {
396
+ const detection = await detectAntigravity()
396
397
  return detection.installed && !detection.skillInstalled
397
398
  }
398
399
 
@@ -436,24 +437,24 @@ export async function installCursorProject(projectRoot: string): Promise<{
436
437
  const cursorCommandsSource = path.join(getPackageRoot(), 'templates', 'cursor', 'commands')
437
438
 
438
439
  // Ensure directories exist
439
- fs.mkdirSync(rulesDir, { recursive: true })
440
- fs.mkdirSync(commandsDir, { recursive: true })
440
+ await fs.mkdir(rulesDir, { recursive: true })
441
+ await fs.mkdir(commandsDir, { recursive: true })
441
442
 
442
443
  // Copy router.mdc → .cursor/rules/prjct.mdc
443
- if (fs.existsSync(routerMdcSource)) {
444
- fs.copyFileSync(routerMdcSource, routerMdcDest)
444
+ if (await fileExists(routerMdcSource)) {
445
+ await fs.copyFile(routerMdcSource, routerMdcDest)
445
446
  result.rulesCreated = true
446
447
  }
447
448
 
448
449
  // Copy individual command files → .cursor/commands/
449
450
  // This enables /sync, /task, /done, /ship, etc. syntax in Cursor
450
- if (fs.existsSync(cursorCommandsSource)) {
451
- const commandFiles = fs.readdirSync(cursorCommandsSource).filter((f) => f.endsWith('.md'))
451
+ if (await fileExists(cursorCommandsSource)) {
452
+ const commandFiles = (await fs.readdir(cursorCommandsSource)).filter((f) => f.endsWith('.md'))
452
453
 
453
454
  for (const file of commandFiles) {
454
455
  const src = path.join(cursorCommandsSource, file)
455
456
  const dest = path.join(commandsDir, file)
456
- fs.copyFileSync(src, dest)
457
+ await fs.copyFile(src, dest)
457
458
  }
458
459
  result.commandsCreated = commandFiles.length > 0
459
460
  }
@@ -490,11 +491,11 @@ async function addCursorToGitignore(projectRoot: string): Promise<boolean> {
490
491
  ]
491
492
 
492
493
  let content = ''
493
- let fileExists = false
494
+ let configExists = false
494
495
 
495
496
  try {
496
- content = fs.readFileSync(gitignorePath, 'utf-8')
497
- fileExists = true
497
+ content = await fs.readFile(gitignorePath, 'utf-8')
498
+ configExists = true
498
499
  } catch (error) {
499
500
  if (!isNotFoundError(error)) {
500
501
  throw error
@@ -507,11 +508,11 @@ async function addCursorToGitignore(projectRoot: string): Promise<boolean> {
507
508
  }
508
509
 
509
510
  // Append to .gitignore
510
- const newContent = fileExists
511
+ const newContent = configExists
511
512
  ? `${content.trimEnd()}\n\n${entriesToAdd.join('\n')}\n`
512
513
  : `${entriesToAdd.join('\n')}\n`
513
514
 
514
- fs.writeFileSync(gitignorePath, newContent, 'utf-8')
515
+ await fs.writeFile(gitignorePath, newContent, 'utf-8')
515
516
  return true
516
517
  } catch (error) {
517
518
  console.error(`Gitignore update warning: ${(error as Error).message}`)
@@ -522,19 +523,19 @@ async function addCursorToGitignore(projectRoot: string): Promise<boolean> {
522
523
  /**
523
524
  * Check if a project has Cursor configured (has .cursor/ directory)
524
525
  */
525
- export function hasCursorProject(projectRoot: string): boolean {
526
- return fs.existsSync(path.join(projectRoot, '.cursor'))
526
+ export async function hasCursorProject(projectRoot: string): Promise<boolean> {
527
+ return await fileExists(path.join(projectRoot, '.cursor'))
527
528
  }
528
529
 
529
530
  /**
530
531
  * Check if Cursor routers need regeneration
531
532
  */
532
- export function needsCursorRegeneration(projectRoot: string): boolean {
533
+ export async function needsCursorRegeneration(projectRoot: string): Promise<boolean> {
533
534
  const cursorDir = path.join(projectRoot, '.cursor')
534
535
  const routerPath = path.join(cursorDir, 'rules', 'prjct.mdc')
535
536
 
536
537
  // Only check if .cursor/ exists (project uses Cursor)
537
- return fs.existsSync(cursorDir) && !fs.existsSync(routerPath)
538
+ return (await fileExists(cursorDir)) && !(await fileExists(routerPath))
538
539
  }
539
540
 
540
541
  // =============================================================================
@@ -584,24 +585,26 @@ export async function installWindsurfProject(projectRoot: string): Promise<{
584
585
  )
585
586
 
586
587
  // Ensure directories exist
587
- fs.mkdirSync(rulesDir, { recursive: true })
588
- fs.mkdirSync(workflowsDir, { recursive: true })
588
+ await fs.mkdir(rulesDir, { recursive: true })
589
+ await fs.mkdir(workflowsDir, { recursive: true })
589
590
 
590
591
  // Copy router.md → .windsurf/rules/prjct.md
591
- if (fs.existsSync(routerSource)) {
592
- fs.copyFileSync(routerSource, routerDest)
592
+ if (await fileExists(routerSource)) {
593
+ await fs.copyFile(routerSource, routerDest)
593
594
  result.rulesCreated = true
594
595
  }
595
596
 
596
597
  // Copy individual workflow files → .windsurf/workflows/
597
598
  // This enables /sync, /task, /done, /ship, etc. syntax in Windsurf
598
- if (fs.existsSync(windsurfWorkflowsSource)) {
599
- const workflowFiles = fs.readdirSync(windsurfWorkflowsSource).filter((f) => f.endsWith('.md'))
599
+ if (await fileExists(windsurfWorkflowsSource)) {
600
+ const workflowFiles = (await fs.readdir(windsurfWorkflowsSource)).filter((f) =>
601
+ f.endsWith('.md')
602
+ )
600
603
 
601
604
  for (const file of workflowFiles) {
602
605
  const src = path.join(windsurfWorkflowsSource, file)
603
606
  const dest = path.join(workflowsDir, file)
604
- fs.copyFileSync(src, dest)
607
+ await fs.copyFile(src, dest)
605
608
  }
606
609
  result.workflowsCreated = workflowFiles.length > 0
607
610
  }
@@ -638,11 +641,11 @@ async function addWindsurfToGitignore(projectRoot: string): Promise<boolean> {
638
641
  ]
639
642
 
640
643
  let content = ''
641
- let fileExists = false
644
+ let configExists = false
642
645
 
643
646
  try {
644
- content = fs.readFileSync(gitignorePath, 'utf-8')
645
- fileExists = true
647
+ content = await fs.readFile(gitignorePath, 'utf-8')
648
+ configExists = true
646
649
  } catch (error) {
647
650
  if (!isNotFoundError(error)) {
648
651
  throw error
@@ -655,11 +658,11 @@ async function addWindsurfToGitignore(projectRoot: string): Promise<boolean> {
655
658
  }
656
659
 
657
660
  // Append to .gitignore
658
- const newContent = fileExists
661
+ const newContent = configExists
659
662
  ? `${content.trimEnd()}\n\n${entriesToAdd.join('\n')}\n`
660
663
  : `${entriesToAdd.join('\n')}\n`
661
664
 
662
- fs.writeFileSync(gitignorePath, newContent, 'utf-8')
665
+ await fs.writeFile(gitignorePath, newContent, 'utf-8')
663
666
  return true
664
667
  } catch (error) {
665
668
  console.error(`Gitignore update warning: ${(error as Error).message}`)
@@ -670,19 +673,19 @@ async function addWindsurfToGitignore(projectRoot: string): Promise<boolean> {
670
673
  /**
671
674
  * Check if a project has Windsurf configured (has .windsurf/ directory)
672
675
  */
673
- export function hasWindsurfProject(projectRoot: string): boolean {
674
- return fs.existsSync(path.join(projectRoot, '.windsurf'))
676
+ export async function hasWindsurfProject(projectRoot: string): Promise<boolean> {
677
+ return await fileExists(path.join(projectRoot, '.windsurf'))
675
678
  }
676
679
 
677
680
  /**
678
681
  * Check if Windsurf routers need regeneration
679
682
  */
680
- export function needsWindsurfRegeneration(projectRoot: string): boolean {
683
+ export async function needsWindsurfRegeneration(projectRoot: string): Promise<boolean> {
681
684
  const windsurfDir = path.join(projectRoot, '.windsurf')
682
685
  const routerPath = path.join(windsurfDir, 'rules', 'prjct.md')
683
686
 
684
687
  // Only check if .windsurf/ exists (project uses Windsurf)
685
- return fs.existsSync(windsurfDir) && !fs.existsSync(routerPath)
688
+ return (await fileExists(windsurfDir)) && !(await fileExists(routerPath))
686
689
  }
687
690
 
688
691
  /**
@@ -693,12 +696,11 @@ async function migrateProjectsCliVersion(): Promise<void> {
693
696
  try {
694
697
  const projectsDir = path.join(os.homedir(), '.prjct-cli', 'projects')
695
698
 
696
- if (!fs.existsSync(projectsDir)) {
699
+ if (!(await fileExists(projectsDir))) {
697
700
  return
698
701
  }
699
702
 
700
- const projectDirs = fs
701
- .readdirSync(projectsDir, { withFileTypes: true })
703
+ const projectDirs = (await fs.readdir(projectsDir, { withFileTypes: true }))
702
704
  .filter((dirent) => dirent.isDirectory())
703
705
  .map((dirent) => dirent.name)
704
706
 
@@ -707,18 +709,18 @@ async function migrateProjectsCliVersion(): Promise<void> {
707
709
  for (const projectId of projectDirs) {
708
710
  const projectJsonPath = path.join(projectsDir, projectId, 'project.json')
709
711
 
710
- if (!fs.existsSync(projectJsonPath)) {
712
+ if (!(await fileExists(projectJsonPath))) {
711
713
  continue
712
714
  }
713
715
 
714
716
  try {
715
- const content = fs.readFileSync(projectJsonPath, 'utf8')
717
+ const content = await fs.readFile(projectJsonPath, 'utf8')
716
718
  const project = JSON.parse(content)
717
719
 
718
720
  // Only update if cliVersion is missing or different
719
721
  if (project.cliVersion !== VERSION) {
720
722
  project.cliVersion = VERSION
721
- fs.writeFileSync(projectJsonPath, JSON.stringify(project, null, 2))
723
+ await fs.writeFile(projectJsonPath, JSON.stringify(project, null, 2))
722
724
  migrated++
723
725
  }
724
726
  } catch (error) {
@@ -744,11 +746,14 @@ async function migrateProjectsCliVersion(): Promise<void> {
744
746
  /**
745
747
  * Ensure settings.json has statusLine configured
746
748
  */
747
- function ensureStatusLineSettings(settingsPath: string, statusLinePath: string): void {
749
+ async function ensureStatusLineSettings(
750
+ settingsPath: string,
751
+ statusLinePath: string
752
+ ): Promise<void> {
748
753
  let settings: Record<string, unknown> = {}
749
- if (fs.existsSync(settingsPath)) {
754
+ if (await fileExists(settingsPath)) {
750
755
  try {
751
- settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'))
756
+ settings = JSON.parse(await fs.readFile(settingsPath, 'utf8'))
752
757
  } catch (error) {
753
758
  // Invalid JSON, start fresh - but propagate unexpected errors
754
759
  if (!(error instanceof SyntaxError)) {
@@ -757,7 +762,7 @@ function ensureStatusLineSettings(settingsPath: string, statusLinePath: string):
757
762
  }
758
763
  }
759
764
  settings.statusLine = { type: 'command', command: statusLinePath }
760
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2))
765
+ await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2))
761
766
  }
762
767
 
763
768
  /**
@@ -790,25 +795,25 @@ async function installStatusLine(): Promise<void> {
790
795
  const sourceConfigPath = path.join(assetsDir, 'default-config.json')
791
796
 
792
797
  // Ensure directories exist
793
- if (!fs.existsSync(claudeDir)) {
794
- fs.mkdirSync(claudeDir, { recursive: true })
798
+ if (!(await fileExists(claudeDir))) {
799
+ await fs.mkdir(claudeDir, { recursive: true })
795
800
  }
796
- if (!fs.existsSync(prjctStatusLineDir)) {
797
- fs.mkdirSync(prjctStatusLineDir, { recursive: true })
801
+ if (!(await fileExists(prjctStatusLineDir))) {
802
+ await fs.mkdir(prjctStatusLineDir, { recursive: true })
798
803
  }
799
- if (!fs.existsSync(prjctThemesDir)) {
800
- fs.mkdirSync(prjctThemesDir, { recursive: true })
804
+ if (!(await fileExists(prjctThemesDir))) {
805
+ await fs.mkdir(prjctThemesDir, { recursive: true })
801
806
  }
802
- if (!fs.existsSync(prjctLibDir)) {
803
- fs.mkdirSync(prjctLibDir, { recursive: true })
807
+ if (!(await fileExists(prjctLibDir))) {
808
+ await fs.mkdir(prjctLibDir, { recursive: true })
804
809
  }
805
- if (!fs.existsSync(prjctComponentsDir)) {
806
- fs.mkdirSync(prjctComponentsDir, { recursive: true })
810
+ if (!(await fileExists(prjctComponentsDir))) {
811
+ await fs.mkdir(prjctComponentsDir, { recursive: true })
807
812
  }
808
813
 
809
814
  // Check if statusline already exists
810
- if (fs.existsSync(prjctStatusLinePath)) {
811
- const existingContent = fs.readFileSync(prjctStatusLinePath, 'utf8')
815
+ if (await fileExists(prjctStatusLinePath)) {
816
+ const existingContent = await fs.readFile(prjctStatusLinePath, 'utf8')
812
817
 
813
818
  if (existingContent.includes('CLI_VERSION=')) {
814
819
  // Has CLI_VERSION - update if needed
@@ -820,48 +825,48 @@ async function installStatusLine(): Promise<void> {
820
825
  /CLI_VERSION="[^"]*"/,
821
826
  `CLI_VERSION="${VERSION}"`
822
827
  )
823
- fs.writeFileSync(prjctStatusLinePath, updatedContent, { mode: 0o755 })
828
+ await fs.writeFile(prjctStatusLinePath, updatedContent, { mode: 0o755 })
824
829
  }
825
830
 
826
831
  // Ensure modular structure is installed (upgrade path)
827
- installStatusLineModules(sourceLibDir, prjctLibDir)
828
- installStatusLineModules(sourceComponentsDir, prjctComponentsDir)
832
+ await installStatusLineModules(sourceLibDir, prjctLibDir)
833
+ await installStatusLineModules(sourceComponentsDir, prjctComponentsDir)
829
834
 
830
835
  // Ensure symlink and settings
831
- ensureStatusLineSymlink(claudeStatusLinePath, prjctStatusLinePath)
832
- ensureStatusLineSettings(settingsPath, claudeStatusLinePath)
836
+ await ensureStatusLineSymlink(claudeStatusLinePath, prjctStatusLinePath)
837
+ await ensureStatusLineSettings(settingsPath, claudeStatusLinePath)
833
838
  return
834
839
  }
835
840
  // else: Script exists WITHOUT CLI_VERSION - fall through to replace with new version
836
841
  }
837
842
 
838
843
  // Install fresh from assets if source exists
839
- if (fs.existsSync(sourceScript)) {
844
+ if (await fileExists(sourceScript)) {
840
845
  // Copy script and update version
841
- let scriptContent = fs.readFileSync(sourceScript, 'utf8')
846
+ let scriptContent = await fs.readFile(sourceScript, 'utf8')
842
847
  scriptContent = scriptContent.replace(/CLI_VERSION="[^"]*"/, `CLI_VERSION="${VERSION}"`)
843
- fs.writeFileSync(prjctStatusLinePath, scriptContent, { mode: 0o755 })
848
+ await fs.writeFile(prjctStatusLinePath, scriptContent, { mode: 0o755 })
844
849
 
845
850
  // Copy lib/ modules
846
- installStatusLineModules(sourceLibDir, prjctLibDir)
851
+ await installStatusLineModules(sourceLibDir, prjctLibDir)
847
852
 
848
853
  // Copy components/
849
- installStatusLineModules(sourceComponentsDir, prjctComponentsDir)
854
+ await installStatusLineModules(sourceComponentsDir, prjctComponentsDir)
850
855
 
851
856
  // Copy themes
852
- if (fs.existsSync(sourceThemeDir)) {
853
- const themes = fs.readdirSync(sourceThemeDir)
857
+ if (await fileExists(sourceThemeDir)) {
858
+ const themes = await fs.readdir(sourceThemeDir)
854
859
  for (const theme of themes) {
855
860
  const src = path.join(sourceThemeDir, theme)
856
861
  const dest = path.join(prjctThemesDir, theme)
857
862
  // Always update themes to get new icons/colors
858
- fs.copyFileSync(src, dest)
863
+ await fs.copyFile(src, dest)
859
864
  }
860
865
  }
861
866
 
862
867
  // Copy default config (only if not exists - preserve user customizations)
863
- if (!fs.existsSync(prjctConfigPath) && fs.existsSync(sourceConfigPath)) {
864
- fs.copyFileSync(sourceConfigPath, prjctConfigPath)
868
+ if (!(await fileExists(prjctConfigPath)) && (await fileExists(sourceConfigPath))) {
869
+ await fs.copyFile(sourceConfigPath, prjctConfigPath)
865
870
  }
866
871
  } else {
867
872
  // Fallback: create simple script inline
@@ -897,12 +902,12 @@ if [ -f "$CONFIG" ]; then
897
902
  fi
898
903
  echo "prjct"
899
904
  `
900
- fs.writeFileSync(prjctStatusLinePath, scriptContent, { mode: 0o755 })
905
+ await fs.writeFile(prjctStatusLinePath, scriptContent, { mode: 0o755 })
901
906
  }
902
907
 
903
908
  // Create symlink and configure settings
904
- ensureStatusLineSymlink(claudeStatusLinePath, prjctStatusLinePath)
905
- ensureStatusLineSettings(settingsPath, claudeStatusLinePath)
909
+ await ensureStatusLineSymlink(claudeStatusLinePath, prjctStatusLinePath)
910
+ await ensureStatusLineSettings(settingsPath, claudeStatusLinePath)
906
911
  } catch (error) {
907
912
  // Silently fail if directories don't exist
908
913
  if (!isNotFoundError(error)) {
@@ -924,8 +929,8 @@ async function installContext7MCP(): Promise<void> {
924
929
  const mcpConfigPath = path.join(claudeDir, 'mcp.json')
925
930
 
926
931
  // Ensure ~/.claude directory exists
927
- if (!fs.existsSync(claudeDir)) {
928
- fs.mkdirSync(claudeDir, { recursive: true })
932
+ if (!(await fileExists(claudeDir))) {
933
+ await fs.mkdir(claudeDir, { recursive: true })
929
934
  }
930
935
 
931
936
  // Context7 MCP configuration
@@ -939,9 +944,9 @@ async function installContext7MCP(): Promise<void> {
939
944
  }
940
945
 
941
946
  // Check if mcp.json exists
942
- if (fs.existsSync(mcpConfigPath)) {
947
+ if (await fileExists(mcpConfigPath)) {
943
948
  // Read existing config
944
- const existingContent = fs.readFileSync(mcpConfigPath, 'utf-8')
949
+ const existingContent = await fs.readFile(mcpConfigPath, 'utf-8')
945
950
  const existingConfig = JSON.parse(existingContent)
946
951
 
947
952
  // Check if context7 is already configured
@@ -953,10 +958,10 @@ async function installContext7MCP(): Promise<void> {
953
958
  // Add context7 to existing config
954
959
  existingConfig.mcpServers = existingConfig.mcpServers || {}
955
960
  existingConfig.mcpServers.context7 = context7Config.mcpServers.context7
956
- fs.writeFileSync(mcpConfigPath, JSON.stringify(existingConfig, null, 2), 'utf-8')
961
+ await fs.writeFile(mcpConfigPath, JSON.stringify(existingConfig, null, 2), 'utf-8')
957
962
  } else {
958
963
  // Create new mcp.json with context7
959
- fs.writeFileSync(mcpConfigPath, JSON.stringify(context7Config, null, 2), 'utf-8')
964
+ await fs.writeFile(mcpConfigPath, JSON.stringify(context7Config, null, 2), 'utf-8')
960
965
  }
961
966
  } catch (error) {
962
967
  // Non-fatal error, just log
@@ -968,18 +973,18 @@ async function installContext7MCP(): Promise<void> {
968
973
  * Install statusline modules (lib/ or components/)
969
974
  * Copies .sh files from source to destination, always overwriting for updates
970
975
  */
971
- function installStatusLineModules(sourceDir: string, destDir: string): void {
972
- if (!fs.existsSync(sourceDir)) {
976
+ async function installStatusLineModules(sourceDir: string, destDir: string): Promise<void> {
977
+ if (!(await fileExists(sourceDir))) {
973
978
  return
974
979
  }
975
980
 
976
- const files = fs.readdirSync(sourceDir)
981
+ const files = await fs.readdir(sourceDir)
977
982
  for (const file of files) {
978
983
  if (file.endsWith('.sh')) {
979
984
  const src = path.join(sourceDir, file)
980
985
  const dest = path.join(destDir, file)
981
- fs.copyFileSync(src, dest)
982
- fs.chmodSync(dest, 0o755)
986
+ await fs.copyFile(src, dest)
987
+ await fs.chmod(dest, 0o755)
983
988
  }
984
989
  }
985
990
  }
@@ -987,28 +992,28 @@ function installStatusLineModules(sourceDir: string, destDir: string): void {
987
992
  /**
988
993
  * Ensure symlink from Claude config to prjct statusline
989
994
  */
990
- function ensureStatusLineSymlink(linkPath: string, targetPath: string): void {
995
+ async function ensureStatusLineSymlink(linkPath: string, targetPath: string): Promise<void> {
991
996
  try {
992
997
  // Check if link already points to correct target
993
- if (fs.existsSync(linkPath)) {
994
- const stats = fs.lstatSync(linkPath)
998
+ if (await fileExists(linkPath)) {
999
+ const stats = await fs.lstat(linkPath)
995
1000
  if (stats.isSymbolicLink()) {
996
- const existingTarget = fs.readlinkSync(linkPath)
1001
+ const existingTarget = await fs.readlink(linkPath)
997
1002
  if (existingTarget === targetPath) {
998
1003
  return // Already correct
999
1004
  }
1000
1005
  }
1001
1006
  // Remove existing file/symlink
1002
- fs.unlinkSync(linkPath)
1007
+ await fs.unlink(linkPath)
1003
1008
  }
1004
1009
  // Create symlink
1005
- fs.symlinkSync(targetPath, linkPath)
1010
+ await fs.symlink(targetPath, linkPath)
1006
1011
  } catch (_error) {
1007
1012
  // If symlink fails (e.g., Windows, permission issues), try copy instead
1008
1013
  try {
1009
- if (fs.existsSync(targetPath)) {
1010
- fs.copyFileSync(targetPath, linkPath)
1011
- fs.chmodSync(linkPath, 0o755)
1014
+ if (await fileExists(targetPath)) {
1015
+ await fs.copyFile(targetPath, linkPath)
1016
+ await fs.chmod(linkPath, 0o755)
1012
1017
  }
1013
1018
  } catch (copyError) {
1014
1019
  // Both symlink and copy failed - log if unexpected error