prjct-cli 0.4.7 โ†’ 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,50 @@ 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
+
39
+ ## [0.4.8] - 2025-10-02
40
+
41
+ ### Added
42
+ - **Intelligent Project Initialization** - `prjct init` now works seamlessly in all scenarios
43
+ - Preserves existing project IDs when re-initializing
44
+ - Automatically merges local data with global structure
45
+ - Smart fusion of markdown files (now.md, next.md, shipped.md, ideas.md)
46
+ - Chronological merging of memory/context.jsonl entries
47
+ - Cleans up local `.prjct/` directory, keeping only `prjct.config.json`
48
+ - Multiple developers can initialize the same project without conflicts
49
+
50
+ ### Fixed
51
+ - **Auto-Migration Removed** - Disabled intrusive automatic migration
52
+ - Migration no longer runs automatically during `prjct init` or any command
53
+ - Users can run `prjct migrate-all` manually if needed
54
+ - Removes blocking behavior that prevented normal usage
55
+ - Cleaner, non-intrusive installation and update experience
56
+
57
+ ### Changed
58
+ - **`prjct init` Never Blocks** - Removed "already initialized" error
59
+ - Always allows re-initialization with intelligent data fusion
60
+ - Global architecture enables multi-developer workflows
61
+
18
62
  ## [0.4.7] - 2025-10-02
19
63
 
20
64
  ### Fixed
@@ -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
@@ -192,8 +192,6 @@ class PrjctCommands {
192
192
 
193
193
  this.agent = new Agent()
194
194
 
195
- await this.checkAndRunAutoMigration()
196
-
197
195
  // Check for updates in background (non-blocking)
198
196
  this.checkAndNotifyUpdates().catch(() => {
199
197
  // Fail silently - don't interrupt workflow
@@ -316,41 +314,46 @@ class PrjctCommands {
316
314
  async init(projectPath = process.cwd()) {
317
315
  try {
318
316
  await this.initializeAgent()
319
-
320
- if (await configManager.isConfigured(projectPath)) {
321
- return {
322
- success: false,
323
- message: this.agent.formatResponse('Project already initialized!', 'warning'),
324
- }
325
- }
326
-
327
317
  const author = await authorDetector.detect()
328
318
 
329
- const hasLegacy = await pathManager.hasLegacyStructure(projectPath)
330
- let migrationPerformed = false
319
+ // Check if local config exists
320
+ const isConfigured = await configManager.isConfigured(projectPath)
321
+ let projectId
322
+ let existingLocalData = null
323
+ let fusionPerformed = false
331
324
 
332
- if (hasLegacy) {
333
- const config = await configManager.createConfig(projectPath, author)
334
- const projectId = config.projectId
335
- await pathManager.ensureProjectStructure(projectId)
325
+ if (isConfigured) {
326
+ // Use existing ID from local config
327
+ const existingConfig = await configManager.readConfig(projectPath)
328
+ projectId = existingConfig.projectId
329
+ console.log(`๐Ÿ”„ Using existing project ID: ${projectId}`)
336
330
 
331
+ // Check if there's local data to merge
332
+ const localPrjctPath = path.join(projectPath, '.prjct')
337
333
  try {
338
- const migrationResult = await migrator.migrate(projectPath, {
339
- removeLegacy: false,
340
- cleanupLegacy: true,
341
- dryRun: false,
342
- })
343
- migrationPerformed = migrationResult.success
334
+ const localFiles = await fs.readdir(localPrjctPath)
335
+ if (localFiles.length > 1) { // More than just prjct.config.json
336
+ existingLocalData = await this.collectLocalData(localPrjctPath)
337
+ }
344
338
  } catch (error) {
345
- console.error('[prjct] Migration warning:', error.message)
339
+ // No local data to merge
346
340
  }
341
+ } else {
342
+ // Generate new ID based on path hash
343
+ const config = await configManager.createConfig(projectPath, author)
344
+ projectId = config.projectId
345
+ console.log(`โœจ Created new project ID: ${projectId}`)
347
346
  }
348
347
 
349
- if (!migrationPerformed) {
350
- const config = await configManager.createConfig(projectPath, author)
351
- const projectId = config.projectId
352
- await pathManager.ensureProjectStructure(projectId)
348
+ // Ensure global structure exists
349
+ await pathManager.ensureProjectStructure(projectId)
350
+ const globalPath = pathManager.getGlobalProjectPath(projectId)
351
+
352
+ // Check if global data exists
353
+ const globalExists = await this.checkGlobalDataExists(globalPath)
353
354
 
355
+ if (!globalExists) {
356
+ // Create fresh global structure
354
357
  const files = {
355
358
  'core/now.md': '# NOW\n\nNo current task. Use `/p:now` to set focus.\n',
356
359
  'core/next.md': '# NEXT\n\n## Priority Queue\n\n',
@@ -362,22 +365,25 @@ class PrjctCommands {
362
365
  'memory/context.jsonl': '',
363
366
  }
364
367
 
365
- const globalPath = pathManager.getGlobalProjectPath(projectId)
366
368
  for (const [filePath, content] of Object.entries(files)) {
367
369
  await this.agent.writeFile(path.join(globalPath, filePath), content)
368
370
  }
371
+ console.log('โœ… Created global structure')
372
+ } else {
373
+ console.log('โœ… Using existing global data')
369
374
  }
370
375
 
371
- const config = await configManager.readConfig(projectPath)
372
- const projectId = config.projectId
373
- const globalPath = pathManager.getGlobalProjectPath(projectId)
376
+ // Merge local data if exists
377
+ if (existingLocalData) {
378
+ console.log('๐Ÿ”„ Merging local data with global...')
379
+ await this.mergeProjectData(existingLocalData, globalPath)
380
+ fusionPerformed = true
381
+ }
374
382
 
375
- const projectInfo = await this.detectProjectType(projectPath)
383
+ // Clean up local data (keep only prjct.config.json)
384
+ await this.cleanupLocalData(projectPath)
376
385
 
377
- const installResult = await this.install({ force: false, interactive: true })
378
- const editorsInstalled = installResult.success
379
- ? `\n๐Ÿค– Commands installed to: ${installResult.message.split('Editors: ')[1]?.split('\n')[0] || 'selected editors'}`
380
- : ''
386
+ const projectInfo = await this.detectProjectType(projectPath)
381
387
 
382
388
  let analysisMessage = ''
383
389
  const hasExistingCode = await this.detectExistingCode(projectPath)
@@ -403,13 +409,14 @@ class PrjctCommands {
403
409
  }
404
410
 
405
411
  const displayPath = pathManager.getDisplayPath(globalPath)
412
+ const fusionMessage = fusionPerformed ? '\n๐Ÿ”„ Merged local data with global' : ''
406
413
  const message =
407
414
  `Initializing prjct v${VERSION} for ${this.agentInfo.name}...\n` +
408
- `โœ… Created global structure at ${displayPath}\n` +
409
- 'โœ… Created prjct.config.json\n' +
415
+ `โœ… Project ID: ${projectId}\n` +
416
+ `โœ… Global data: ${displayPath}\n` +
410
417
  `๐Ÿ‘ค Author: ${authorDetector.formatAuthor(author)}\n` +
411
418
  `๐Ÿ“‹ Project: ${projectInfo}` +
412
- editorsInstalled +
419
+ fusionMessage +
413
420
  analysisMessage +
414
421
  `\n\nReady! Start with ${this.agentInfo.config.commandPrefix}now "your first task"`
415
422
 
@@ -426,6 +433,270 @@ class PrjctCommands {
426
433
  }
427
434
  }
428
435
 
436
+ /**
437
+ * Check if global data exists for a project
438
+ *
439
+ * @param {string} globalPath - Global project path
440
+ * @returns {Promise<boolean>} True if global data exists
441
+ * @private
442
+ */
443
+ async checkGlobalDataExists(globalPath) {
444
+ try {
445
+ const nowFile = path.join(globalPath, 'core', 'now.md')
446
+ await fs.access(nowFile)
447
+ return true
448
+ } catch {
449
+ return false
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Collect all data from local .prjct directory
455
+ *
456
+ * @param {string} localPrjctPath - Path to local .prjct directory
457
+ * @returns {Promise<Object>} Object with local data organized by category
458
+ * @private
459
+ */
460
+ async collectLocalData(localPrjctPath) {
461
+ const data = {
462
+ core: {},
463
+ progress: {},
464
+ planning: {},
465
+ memory: {},
466
+ }
467
+
468
+ try {
469
+ // Collect core files
470
+ const coreFiles = ['now.md', 'next.md', 'context.md']
471
+ for (const file of coreFiles) {
472
+ try {
473
+ const filePath = path.join(localPrjctPath, file)
474
+ data.core[file] = await this.agent.readFile(filePath)
475
+ } catch {
476
+ // File doesn't exist locally
477
+ }
478
+ }
479
+
480
+ // Collect progress files
481
+ const progressFiles = ['shipped.md', 'metrics.md']
482
+ for (const file of progressFiles) {
483
+ try {
484
+ const filePath = path.join(localPrjctPath, file)
485
+ data.progress[file] = await this.agent.readFile(filePath)
486
+ } catch {
487
+ // File doesn't exist locally
488
+ }
489
+ }
490
+
491
+ // Collect planning files
492
+ const planningFiles = ['ideas.md', 'roadmap.md']
493
+ for (const file of planningFiles) {
494
+ try {
495
+ const filePath = path.join(localPrjctPath, file)
496
+ data.planning[file] = await this.agent.readFile(filePath)
497
+ } catch {
498
+ // File doesn't exist locally
499
+ }
500
+ }
501
+
502
+ // Collect memory
503
+ try {
504
+ const memoryPath = path.join(localPrjctPath, 'memory.jsonl')
505
+ data.memory['context.jsonl'] = await this.agent.readFile(memoryPath)
506
+ } catch {
507
+ // Memory doesn't exist locally
508
+ }
509
+ } catch (error) {
510
+ console.error('[prjct] Error collecting local data:', error.message)
511
+ }
512
+
513
+ return data
514
+ }
515
+
516
+ /**
517
+ * Merge local project data with global data
518
+ *
519
+ * @param {Object} localData - Local data collected from .prjct
520
+ * @param {string} globalPath - Global project path
521
+ * @returns {Promise<void>}
522
+ * @private
523
+ */
524
+ async mergeProjectData(localData, globalPath) {
525
+ try {
526
+ // Merge core files
527
+ for (const [filename, localContent] of Object.entries(localData.core)) {
528
+ if (!localContent) continue
529
+
530
+ const globalFilePath = path.join(globalPath, 'core', filename)
531
+ let globalContent = ''
532
+ try {
533
+ globalContent = await this.agent.readFile(globalFilePath)
534
+ } catch {
535
+ // Global file doesn't exist yet
536
+ }
537
+
538
+ const merged = this.intelligentMerge(localContent, globalContent, filename)
539
+ await this.agent.writeFile(globalFilePath, merged)
540
+ }
541
+
542
+ // Merge progress files
543
+ for (const [filename, localContent] of Object.entries(localData.progress)) {
544
+ if (!localContent) continue
545
+
546
+ const globalFilePath = path.join(globalPath, 'progress', filename)
547
+ let globalContent = ''
548
+ try {
549
+ globalContent = await this.agent.readFile(globalFilePath)
550
+ } catch {
551
+ // Global file doesn't exist yet
552
+ }
553
+
554
+ const merged = this.intelligentMerge(localContent, globalContent, filename)
555
+ await this.agent.writeFile(globalFilePath, merged)
556
+ }
557
+
558
+ // Merge planning files
559
+ for (const [filename, localContent] of Object.entries(localData.planning)) {
560
+ if (!localContent) continue
561
+
562
+ const globalFilePath = path.join(globalPath, 'planning', filename)
563
+ let globalContent = ''
564
+ try {
565
+ globalContent = await this.agent.readFile(globalFilePath)
566
+ } catch {
567
+ // Global file doesn't exist yet
568
+ }
569
+
570
+ const merged = this.intelligentMerge(localContent, globalContent, filename)
571
+ await this.agent.writeFile(globalFilePath, merged)
572
+ }
573
+
574
+ // Merge memory (chronologically)
575
+ if (localData.memory['context.jsonl']) {
576
+ const globalMemoryPath = path.join(globalPath, 'memory', 'context.jsonl')
577
+ let globalMemory = ''
578
+ try {
579
+ globalMemory = await this.agent.readFile(globalMemoryPath)
580
+ } catch {
581
+ // Global memory doesn't exist yet
582
+ }
583
+
584
+ const merged = this.mergeMemory(localData.memory['context.jsonl'], globalMemory)
585
+ await this.agent.writeFile(globalMemoryPath, merged)
586
+ }
587
+ } catch (error) {
588
+ console.error('[prjct] Error merging data:', error.message)
589
+ }
590
+ }
591
+
592
+ /**
593
+ * Intelligent merge of markdown content
594
+ *
595
+ * @param {string} localContent - Content from local file
596
+ * @param {string} globalContent - Content from global file
597
+ * @param {string} filename - Name of the file being merged
598
+ * @returns {string} Merged content
599
+ * @private
600
+ */
601
+ intelligentMerge(localContent, globalContent, filename) {
602
+ // If global is empty, use local
603
+ if (!globalContent || globalContent.trim() === '' || globalContent.includes('No current task')) {
604
+ return localContent
605
+ }
606
+
607
+ // If local is empty, use global
608
+ if (!localContent || localContent.trim() === '') {
609
+ return globalContent
610
+ }
611
+
612
+ // For now.md, prefer local (most recent)
613
+ if (filename === 'now.md') {
614
+ return localContent
615
+ }
616
+
617
+ // For shipped.md, next.md, ideas.md, roadmap.md - combine unique entries
618
+ const localLines = localContent.split('\n').filter((l) => l.trim())
619
+ const globalLines = globalContent.split('\n').filter((l) => l.trim())
620
+
621
+ // Keep header from global
622
+ const header = globalLines[0] || localLines[0]
623
+
624
+ // Combine unique content (skip headers and empty lines)
625
+ const allLines = [...globalLines.slice(1), ...localLines.slice(1)]
626
+ const uniqueLines = [...new Set(allLines)]
627
+
628
+ return [header, '', ...uniqueLines].join('\n')
629
+ }
630
+
631
+ /**
632
+ * Merge memory.jsonl files chronologically
633
+ *
634
+ * @param {string} localMemory - Local memory content
635
+ * @param {string} globalMemory - Global memory content
636
+ * @returns {string} Merged memory sorted by timestamp
637
+ * @private
638
+ */
639
+ mergeMemory(localMemory, globalMemory) {
640
+ if (!globalMemory) return localMemory
641
+ if (!localMemory) return globalMemory
642
+
643
+ const localEntries = localMemory.split('\n').filter((l) => l.trim())
644
+ const globalEntries = globalMemory.split('\n').filter((l) => l.trim())
645
+
646
+ const allEntries = [...globalEntries, ...localEntries].map((entry) => {
647
+ try {
648
+ return JSON.parse(entry)
649
+ } catch {
650
+ return null
651
+ }
652
+ }).filter(Boolean)
653
+
654
+ // Sort by timestamp
655
+ allEntries.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp))
656
+
657
+ // Remove duplicates
658
+ const unique = []
659
+ const seen = new Set()
660
+ for (const entry of allEntries) {
661
+ const key = `${entry.timestamp}-${entry.action}`
662
+ if (!seen.has(key)) {
663
+ seen.add(key)
664
+ unique.push(entry)
665
+ }
666
+ }
667
+
668
+ return unique.map((e) => JSON.stringify(e)).join('\n')
669
+ }
670
+
671
+ /**
672
+ * Clean up local .prjct directory, keeping only prjct.config.json
673
+ *
674
+ * @param {string} projectPath - Project path
675
+ * @returns {Promise<void>}
676
+ * @private
677
+ */
678
+ async cleanupLocalData(projectPath) {
679
+ try {
680
+ const localPrjctPath = path.join(projectPath, '.prjct')
681
+ const files = await fs.readdir(localPrjctPath, { withFileTypes: true })
682
+
683
+ for (const file of files) {
684
+ if (file.name === 'prjct.config.json') continue
685
+
686
+ const filePath = path.join(localPrjctPath, file.name)
687
+ if (file.isDirectory()) {
688
+ await fs.rm(filePath, { recursive: true, force: true })
689
+ } else {
690
+ await fs.unlink(filePath)
691
+ }
692
+ }
693
+
694
+ console.log('๐Ÿงน Cleaned up local data - only prjct.config.json remains')
695
+ } catch (error) {
696
+ console.error('[prjct] Warning: Could not clean up local data:', error.message)
697
+ }
698
+ }
699
+
429
700
  /**
430
701
  * Set or view current task
431
702
  *
@@ -1949,20 +2220,6 @@ ${diagram}
1949
2220
  try {
1950
2221
  await this.initializeAgent()
1951
2222
 
1952
- // Check if already configured
1953
- const editorsConfig = require('./editors-config')
1954
- const configExists = await editorsConfig.configExists()
1955
-
1956
- if (configExists) {
1957
- return {
1958
- success: false,
1959
- message: this.agent.formatResponse(
1960
- 'prjct is already set up!\n\nTo reconfigure editors: prjct setup',
1961
- 'warning'
1962
- ),
1963
- }
1964
- }
1965
-
1966
2223
  // ASCII Art
1967
2224
  const chalk = require('chalk')
1968
2225
  console.log('')
@@ -2006,8 +2263,8 @@ ${diagram}
2006
2263
  })
2007
2264
  console.log('')
2008
2265
 
2009
- // Interactive selection
2010
- const installResult = await commandInstaller.interactiveInstall(false)
2266
+ // Interactive selection (allow uninstall = true)
2267
+ const installResult = await commandInstaller.interactiveInstall(true)
2011
2268
 
2012
2269
  if (!installResult.success) {
2013
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.7",
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": {
@@ -81,34 +81,6 @@ async function main() {
81
81
  // Update version in config
82
82
  await editorsConfig.updateVersion(currentVersion)
83
83
 
84
- // Auto-migrate legacy projects (v0.1.0 โ†’ v0.4.4)
85
- try {
86
- console.log(chalk.cyan('\n๐Ÿ” Checking for legacy projects to migrate...\n'))
87
-
88
- const migrator = require('../core/migrator')
89
-
90
- // Quick scan of common directories (not entire home directory)
91
- const summary = await migrator.migrateAll({
92
- deepScan: false, // Only scan common dirs (fast)
93
- removeLegacy: false, // Keep legacy dirs for safety
94
- cleanupLegacy: true, // Remove old dirs but keep config
95
- dryRun: false, // Actually perform migration
96
- interactive: false // No user prompts during npm install
97
- })
98
-
99
- if (summary.successfullyMigrated > 0) {
100
- console.log(chalk.green(`โœ… Migrated ${summary.successfullyMigrated} legacy project(s) to new structure`))
101
- console.log(chalk.gray(` Data location: ~/.prjct-cli/projects/`))
102
- } else if (summary.totalFound > 0 && summary.alreadyMigrated === summary.totalFound) {
103
- console.log(chalk.gray('โ„น๏ธ All projects already using new structure'))
104
- }
105
- } catch (migrationError) {
106
- // Don't block install if migration fails
107
- if (process.env.DEBUG) {
108
- console.error(chalk.yellow('[post-install] Migration warning:'), migrationError.message)
109
- }
110
- }
111
-
112
84
  console.log(chalk.cyan(`\nโœจ prjct-cli ${currentVersion} is ready!\n`))
113
85
 
114
86
  } catch (error) {