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
|
@@ -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.
|
|
264
|
+
await fs.mkdir(geminiCommandsDir, { recursive: true })
|
|
264
265
|
|
|
265
266
|
// Copy router
|
|
266
|
-
if (
|
|
267
|
-
fs.
|
|
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.
|
|
288
|
+
await fs.mkdir(geminiDir, { recursive: true })
|
|
288
289
|
|
|
289
290
|
// Read template content
|
|
290
|
-
const templateContent = fs.
|
|
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
|
|
295
|
+
let configExists = false
|
|
295
296
|
|
|
296
297
|
try {
|
|
297
|
-
existingContent = fs.
|
|
298
|
-
|
|
298
|
+
existingContent = await fs.readFile(globalConfigPath, 'utf-8')
|
|
299
|
+
configExists = true
|
|
299
300
|
} catch (error) {
|
|
300
301
|
if (isNotFoundError(error)) {
|
|
301
|
-
|
|
302
|
+
configExists = false
|
|
302
303
|
} else {
|
|
303
304
|
throw error
|
|
304
305
|
}
|
|
305
306
|
}
|
|
306
307
|
|
|
307
|
-
if (!
|
|
308
|
+
if (!configExists) {
|
|
308
309
|
// Create new file with full template
|
|
309
|
-
fs.
|
|
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.
|
|
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.
|
|
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.
|
|
369
|
+
await fs.mkdir(prjctSkillDir, { recursive: true })
|
|
369
370
|
|
|
370
371
|
// Check if SKILL.md already exists
|
|
371
|
-
const
|
|
372
|
+
const skillExists = await fileExists(skillMdPath)
|
|
372
373
|
|
|
373
374
|
// Read template content
|
|
374
|
-
if (!
|
|
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.
|
|
380
|
+
const templateContent = await fs.readFile(templatePath, 'utf-8')
|
|
380
381
|
|
|
381
382
|
// Write SKILL.md
|
|
382
|
-
fs.
|
|
383
|
+
await fs.writeFile(skillMdPath, templateContent, 'utf-8')
|
|
383
384
|
|
|
384
|
-
return { success: true, action:
|
|
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.
|
|
440
|
-
fs.
|
|
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 (
|
|
444
|
-
fs.
|
|
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 (
|
|
451
|
-
const commandFiles = fs.
|
|
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.
|
|
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
|
|
494
|
+
let configExists = false
|
|
494
495
|
|
|
495
496
|
try {
|
|
496
|
-
content = fs.
|
|
497
|
-
|
|
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 =
|
|
511
|
+
const newContent = configExists
|
|
511
512
|
? `${content.trimEnd()}\n\n${entriesToAdd.join('\n')}\n`
|
|
512
513
|
: `${entriesToAdd.join('\n')}\n`
|
|
513
514
|
|
|
514
|
-
fs.
|
|
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
|
|
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
|
|
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.
|
|
588
|
-
fs.
|
|
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 (
|
|
592
|
-
fs.
|
|
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 (
|
|
599
|
-
const workflowFiles = fs.
|
|
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.
|
|
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
|
|
644
|
+
let configExists = false
|
|
642
645
|
|
|
643
646
|
try {
|
|
644
|
-
content = fs.
|
|
645
|
-
|
|
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 =
|
|
661
|
+
const newContent = configExists
|
|
659
662
|
? `${content.trimEnd()}\n\n${entriesToAdd.join('\n')}\n`
|
|
660
663
|
: `${entriesToAdd.join('\n')}\n`
|
|
661
664
|
|
|
662
|
-
fs.
|
|
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
|
|
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
|
|
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 (!
|
|
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 (!
|
|
712
|
+
if (!(await fileExists(projectJsonPath))) {
|
|
711
713
|
continue
|
|
712
714
|
}
|
|
713
715
|
|
|
714
716
|
try {
|
|
715
|
-
const content = fs.
|
|
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.
|
|
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(
|
|
749
|
+
async function ensureStatusLineSettings(
|
|
750
|
+
settingsPath: string,
|
|
751
|
+
statusLinePath: string
|
|
752
|
+
): Promise<void> {
|
|
748
753
|
let settings: Record<string, unknown> = {}
|
|
749
|
-
if (
|
|
754
|
+
if (await fileExists(settingsPath)) {
|
|
750
755
|
try {
|
|
751
|
-
settings = JSON.parse(fs.
|
|
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.
|
|
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 (!
|
|
794
|
-
fs.
|
|
798
|
+
if (!(await fileExists(claudeDir))) {
|
|
799
|
+
await fs.mkdir(claudeDir, { recursive: true })
|
|
795
800
|
}
|
|
796
|
-
if (!
|
|
797
|
-
fs.
|
|
801
|
+
if (!(await fileExists(prjctStatusLineDir))) {
|
|
802
|
+
await fs.mkdir(prjctStatusLineDir, { recursive: true })
|
|
798
803
|
}
|
|
799
|
-
if (!
|
|
800
|
-
fs.
|
|
804
|
+
if (!(await fileExists(prjctThemesDir))) {
|
|
805
|
+
await fs.mkdir(prjctThemesDir, { recursive: true })
|
|
801
806
|
}
|
|
802
|
-
if (!
|
|
803
|
-
fs.
|
|
807
|
+
if (!(await fileExists(prjctLibDir))) {
|
|
808
|
+
await fs.mkdir(prjctLibDir, { recursive: true })
|
|
804
809
|
}
|
|
805
|
-
if (!
|
|
806
|
-
fs.
|
|
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 (
|
|
811
|
-
const existingContent = fs.
|
|
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.
|
|
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 (
|
|
844
|
+
if (await fileExists(sourceScript)) {
|
|
840
845
|
// Copy script and update version
|
|
841
|
-
let scriptContent = fs.
|
|
846
|
+
let scriptContent = await fs.readFile(sourceScript, 'utf8')
|
|
842
847
|
scriptContent = scriptContent.replace(/CLI_VERSION="[^"]*"/, `CLI_VERSION="${VERSION}"`)
|
|
843
|
-
fs.
|
|
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 (
|
|
853
|
-
const themes = fs.
|
|
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.
|
|
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 (!
|
|
864
|
-
fs.
|
|
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.
|
|
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 (!
|
|
928
|
-
fs.
|
|
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 (
|
|
947
|
+
if (await fileExists(mcpConfigPath)) {
|
|
943
948
|
// Read existing config
|
|
944
|
-
const existingContent = fs.
|
|
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.
|
|
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.
|
|
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 (!
|
|
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.
|
|
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.
|
|
982
|
-
fs.
|
|
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 (
|
|
994
|
-
const stats = fs.
|
|
998
|
+
if (await fileExists(linkPath)) {
|
|
999
|
+
const stats = await fs.lstat(linkPath)
|
|
995
1000
|
if (stats.isSymbolicLink()) {
|
|
996
|
-
const existingTarget = fs.
|
|
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.
|
|
1007
|
+
await fs.unlink(linkPath)
|
|
1003
1008
|
}
|
|
1004
1009
|
// Create symlink
|
|
1005
|
-
fs.
|
|
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 (
|
|
1010
|
-
fs.
|
|
1011
|
-
fs.
|
|
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
|