prjct-cli 0.4.8 → 0.4.9

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 CHANGED
@@ -15,6 +15,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
15
  - Cross-platform file operations
16
16
  - Windows Terminal integration
17
17
 
18
+ ## [0.4.9] - 2025-10-02
19
+
20
+ ### Added
21
+ - **Editor Uninstallation** - `prjct start` now allows removing editors
22
+ - Interactive checkbox selection shows currently installed editors
23
+ - Uncheck an editor to remove all prjct commands from it
24
+ - Automatically cleans up `~/.prjct-cli/config/installed-editors.json`
25
+ - User has full control over which editors have prjct commands
26
+
27
+ ### Changed
28
+ - **`prjct start` Never Blocks** - Removed "already set up" error
29
+ - Can run `prjct start` anytime to reconfigure editors
30
+ - Shows beautiful ASCII art every time
31
+ - User decides which editors to keep/remove
32
+
33
+ ### Fixed
34
+ - **Interactive Prompts** - Better UX for editor selection
35
+ - Pre-selects currently installed editors
36
+ - Clear message: "Select AI editors (uncheck to remove)"
37
+ - Allows selecting 0 editors (removes all)
38
+
18
39
  ## [0.4.8] - 2025-10-02
19
40
 
20
41
  ### Added
@@ -480,24 +480,93 @@ For detailed implementation, see prjct-cli documentation.
480
480
  return await this.installToSelected(detectedEditors, forceUpdate)
481
481
  }
482
482
 
483
+ /**
484
+ * Uninstall commands from a specific editor
485
+ * @param {string} editorKey - Editor key (claude, cursor, windsurf, codex)
486
+ * @returns {Promise<Object>} Uninstall result
487
+ */
488
+ async uninstallFromEditor(editorKey) {
489
+ const chalk = require('chalk')
490
+ const editorsConfig = require('./editors-config')
491
+
492
+ const editor = this.editors[editorKey]
493
+ if (!editor) {
494
+ return {
495
+ success: false,
496
+ message: `Unknown editor: ${editorKey}`,
497
+ }
498
+ }
499
+
500
+ try {
501
+ // Delete commands from editor
502
+ const commandsPath = editor.path
503
+ const fs = require('fs').promises
504
+
505
+ // Get list of prjct commands
506
+ const commands = ['init', 'now', 'done', 'ship', 'next', 'idea', 'recap', 'progress', 'stuck', 'context',
507
+ 'cleanup', 'design', 'task', 'git', 'test', 'roadmap', 'fix', 'analyze']
508
+
509
+ let deletedCount = 0
510
+
511
+ for (const command of commands) {
512
+ const commandFile = path.join(commandsPath, `p:${command}.md`)
513
+ try {
514
+ await fs.unlink(commandFile)
515
+ deletedCount++
516
+ } catch {
517
+ // Command file doesn't exist, skip
518
+ }
519
+ }
520
+
521
+ // Remove editor from tracked list
522
+ await editorsConfig.removeTrackedEditor(editorKey)
523
+
524
+ console.log(chalk.yellow(`🗑️ Removed ${deletedCount} commands from ${editor.name}`))
525
+
526
+ return {
527
+ success: true,
528
+ message: `Removed commands from ${editor.name}`,
529
+ deleted: deletedCount,
530
+ }
531
+ } catch (error) {
532
+ console.error(chalk.red(`❌ Error removing commands from ${editor.name}:`), error.message)
533
+ return {
534
+ success: false,
535
+ message: `Failed to remove commands: ${error.message}`,
536
+ }
537
+ }
538
+ }
539
+
483
540
  /**
484
541
  * Interactive installation with user selection using prompts
485
- * @param {boolean} forceUpdate - Force update existing commands
542
+ * @param {boolean} allowUninstall - Allow uninstalling editors
486
543
  * @returns {Promise<Object>} Installation results
487
544
  */
488
- async interactiveInstall(forceUpdate = false) {
545
+ async interactiveInstall(allowUninstall = false) {
489
546
  const prompts = require('prompts')
547
+ const editorsConfig = require('./editors-config')
490
548
 
491
549
  // Detect all editors
492
550
  const detected = await this.detectEditors(this.projectPath)
493
551
 
552
+ // Get currently installed editors if allowUninstall
553
+ let installedEditors = []
554
+ if (allowUninstall) {
555
+ try {
556
+ installedEditors = await editorsConfig.getTrackedEditors()
557
+ } catch {
558
+ // No editors installed yet
559
+ }
560
+ }
561
+
494
562
  // Create choices for prompts
495
563
  const availableEditors = Object.entries(detected)
496
564
  .filter(([_, info]) => info.detected)
497
565
  .map(([key, info]) => ({
498
566
  title: `${this.editors[key].name} (${info.path})`,
499
567
  value: key,
500
- selected: true, // Pre-select all detected editors
568
+ // Pre-select if allowUninstall and already installed, otherwise select all
569
+ selected: allowUninstall ? installedEditors.includes(key) : true,
501
570
  }))
502
571
 
503
572
  if (availableEditors.length === 0) {
@@ -513,15 +582,15 @@ For detailed implementation, see prjct-cli documentation.
513
582
  const response = await prompts({
514
583
  type: 'multiselect',
515
584
  name: 'selectedEditors',
516
- message: 'Select AI editors to install commands to:',
585
+ message: allowUninstall ? 'Select AI editors (uncheck to remove):' : 'Select AI editors to install commands to:',
517
586
  choices: availableEditors,
518
- min: 1,
587
+ min: allowUninstall ? 0 : 1, // Allow 0 selections if uninstalling
519
588
  hint: '- Space to select. Return to submit',
520
589
  instructions: false,
521
590
  })
522
591
 
523
592
  // Check if user cancelled
524
- if (!response.selectedEditors || response.selectedEditors.length === 0) {
593
+ if (response.selectedEditors === undefined) {
525
594
  return {
526
595
  success: false,
527
596
  message: 'Installation cancelled by user',
@@ -530,8 +599,28 @@ For detailed implementation, see prjct-cli documentation.
530
599
  }
531
600
  }
532
601
 
533
- // Install to selected editors
534
- return await this.installToSelected(response.selectedEditors, forceUpdate)
602
+ const selectedEditors = response.selectedEditors || []
603
+
604
+ // If allowUninstall, handle editors that were deselected
605
+ if (allowUninstall) {
606
+ const deselectedEditors = installedEditors.filter(e => !selectedEditors.includes(e))
607
+
608
+ for (const editorKey of deselectedEditors) {
609
+ await this.uninstallFromEditor(editorKey)
610
+ }
611
+ }
612
+
613
+ // Install to selected editors (or return success if none selected)
614
+ if (selectedEditors.length === 0) {
615
+ return {
616
+ success: true,
617
+ message: 'All editors removed',
618
+ editors: [],
619
+ results: {},
620
+ }
621
+ }
622
+
623
+ return await this.installToSelected(selectedEditors, true) // Force update
535
624
  }
536
625
 
537
626
  /**
package/core/commands.js CHANGED
@@ -2220,20 +2220,6 @@ ${diagram}
2220
2220
  try {
2221
2221
  await this.initializeAgent()
2222
2222
 
2223
- // Check if already configured
2224
- const editorsConfig = require('./editors-config')
2225
- const configExists = await editorsConfig.configExists()
2226
-
2227
- if (configExists) {
2228
- return {
2229
- success: false,
2230
- message: this.agent.formatResponse(
2231
- 'prjct is already set up!\n\nTo reconfigure editors: prjct setup',
2232
- 'warning'
2233
- ),
2234
- }
2235
- }
2236
-
2237
2223
  // ASCII Art
2238
2224
  const chalk = require('chalk')
2239
2225
  console.log('')
@@ -2277,8 +2263,8 @@ ${diagram}
2277
2263
  })
2278
2264
  console.log('')
2279
2265
 
2280
- // Interactive selection
2281
- const installResult = await commandInstaller.interactiveInstall(false)
2266
+ // Interactive selection (allow uninstall = true)
2267
+ const installResult = await commandInstaller.interactiveInstall(true)
2282
2268
 
2283
2269
  if (!installResult.success) {
2284
2270
  return {
@@ -87,6 +87,35 @@ class EditorsConfig {
87
87
  return config ? config.editors : []
88
88
  }
89
89
 
90
+ /**
91
+ * Remove an editor from tracked list
92
+ * @param {string} editorKey - Editor key to remove
93
+ * @returns {Promise<boolean>} Success status
94
+ */
95
+ async removeTrackedEditor(editorKey) {
96
+ try {
97
+ const config = await this.loadConfig()
98
+ if (!config) return false
99
+
100
+ // Remove from editors array
101
+ config.editors = config.editors.filter(e => e !== editorKey)
102
+
103
+ // Remove from paths object
104
+ if (config.paths && config.paths[editorKey]) {
105
+ delete config.paths[editorKey]
106
+ }
107
+
108
+ // Save updated config
109
+ await fs.mkdir(this.configDir, { recursive: true })
110
+ await fs.writeFile(this.configPath, JSON.stringify(config, null, 2), 'utf-8')
111
+
112
+ return true
113
+ } catch (error) {
114
+ console.error('[editors-config] Error removing tracked editor:', error.message)
115
+ return false
116
+ }
117
+ }
118
+
90
119
  /**
91
120
  * Get editor paths from configuration
92
121
  * @returns {Promise<Object>} Object mapping editor keys to paths
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "AI-integrated project management for indie hackers - works with Claude Code, Cursor, and Warp",
5
5
  "main": "core/index.js",
6
6
  "bin": {