prjct-cli 0.4.3 → 0.4.5

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,96 @@ 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.5] - 2025-10-02
19
+
20
+ ### Fixed
21
+ - **Critical Bug Fixes** - All commands now enforce global architecture correctly
22
+ - `/p:design` now creates designs in global `analysis/designs/` instead of local `.prjct/designs/`
23
+ - `/p:cleanup` now operates on global project data instead of local `.prjct/`
24
+ - `getDaysSinceLastShip()` now reads from global `memory/context.jsonl` instead of local
25
+ - All commands verify project is initialized before execution
26
+ - Prevents creation of invalid local `.prjct/` structures
27
+ - **Fixed post-install.js** - `currentVersion` now declared before use (prevents ReferenceError)
28
+ - **Added 'Apps' directory** to migration scanner for better project detection
29
+
30
+ ### Technical Details
31
+ - **Global Architecture**: All commands now use `pathManager.getGlobalProjectPath(projectId)`
32
+ - design(), cleanup(), getDaysSinceLastShip() fully migrated
33
+ - Prevents LOCAL .prjct/ creation
34
+ - Requires project initialization before command execution
35
+ - **Migration Enhancement**: Added ~/Apps to common directories scan
36
+ - **Post-Install Fix**: currentVersion variable properly declared at function start
37
+
38
+ ## [0.4.4] - 2025-10-02
39
+
40
+ ### Added
41
+ - **Automatic Editor Command Updates** - Commands auto-update when npm package is updated
42
+ - New `core/editors-config.js` tracks which editors user has installed commands to
43
+ - Stores editor selections in `~/.prjct-cli/config/installed-editors.json`
44
+ - Post-install hook (`scripts/post-install.js`) auto-updates commands after `npm update -g prjct-cli`
45
+ - **Auto-installation on first install** - Detects AI editors and installs commands automatically
46
+ - Ensures version consistency across all configured editors (Claude, Cursor, Windsurf, Codex)
47
+ - No manual reinstallation needed - updates happen automatically
48
+ - Respects user's original editor choices from initial setup
49
+
50
+ - **Automatic Cleanup on Uninstall** - Clean uninstallation removes all traces
51
+ - New `scripts/preuninstall.js` runs before `npm uninstall -g prjct-cli`
52
+ - Automatically removes commands from all tracked editors
53
+ - Deletes tracking configuration from `~/.prjct-cli/config/`
54
+ - Added `uninstallFromEditor()` and `uninstallFromAll()` methods to command installer
55
+ - Added `deleteConfig()` method to editors config
56
+ - Prevents orphaned commands when package is uninstalled
57
+ - Clean exit even if cleanup fails (doesn't block uninstall)
58
+
59
+ - **Automatic Data Migration** - Seamless upgrade from v0.1.0 to v0.4.4
60
+ - Post-install hook automatically detects legacy `.prjct/` projects
61
+ - Migrates data from local structure to new global architecture (`~/.prjct-cli/projects/{id}/`)
62
+ - Scans common project directories (Projects, Documents, Developer, Code, **Apps**)
63
+ - Preserves all project data during migration (now, next, shipped, ideas, memory)
64
+ - Cleans up legacy directories while keeping config for compatibility
65
+ - No user intervention required - migration happens automatically on update
66
+ - Uses existing battle-tested `core/migrator.js` system
67
+
68
+ ### Changed
69
+ - **Improved Command Tracking** - Always track editor installations for better update management
70
+ - Modified `installToSelected()` to always save editor config (removed `!forceUpdate` restriction)
71
+ - Ensures tracking happens even during force updates
72
+ - Enables reliable auto-updates across all configured editors
73
+
74
+ ### Fixed
75
+ - **Global Architecture Enforcement** - All commands now enforce global architecture correctly
76
+ - `/p:design` now creates designs in global `analysis/designs/` instead of local `.prjct/designs/`
77
+ - `/p:cleanup` now operates on global project data instead of local `.prjct/`
78
+ - `getDaysSinceLastShip()` now reads from global `memory/context.jsonl` instead of local
79
+ - All commands verify project is initialized before execution
80
+ - Prevents creation of invalid local `.prjct/` structures
81
+ - **Fixed post-install.js** - `currentVersion` now declared before use (prevents ReferenceError)
82
+ - **Added 'Apps' directory** to migration scanner for better project detection
83
+
84
+ ### Technical Details
85
+ - **Post-Install Hook**: Runs after `npm install -g prjct-cli` or `npm update -g prjct-cli`
86
+ - First install: Auto-detects editors and installs commands
87
+ - Updates: Checks version change and auto-updates commands in tracked editors
88
+ - **Migration**: Automatically detects and migrates legacy projects from v0.1.0
89
+ - Silent operation with debug mode via `DEBUG=1` environment variable
90
+ - **Fixed**: currentVersion variable now properly declared at function start
91
+ - **Enhanced**: Better error handling with debug logging
92
+ - **Pre-Uninstall Hook**: Runs before `npm uninstall -g prjct-cli`
93
+ - Reads tracking config to find all installed editors
94
+ - Removes command directories from each editor
95
+ - Cleans up tracking configuration
96
+ - Fails gracefully to not block uninstall
97
+ - **Migration System**: Automatic upgrade path from v0.1.0 to v0.4.4
98
+ - Scans common project directories including **~/Apps** (fast, non-intrusive)
99
+ - Migrates local `.prjct/` to global `~/.prjct-cli/projects/{id}/`
100
+ - Preserves all data: core, progress, planning, analysis, memory layers
101
+ - Cleans legacy directories while keeping compatibility config
102
+ - Zero data loss, seamless upgrade experience
103
+ - **Global Architecture**: All commands now use `pathManager.getGlobalProjectPath(projectId)`
104
+ - design(), cleanup(), getDaysSinceLastShip() fully migrated
105
+ - Prevents LOCAL .prjct/ creation
106
+ - Requires project initialization before command execution
107
+
18
108
  ## [0.4.3] - 2025-10-02
19
109
 
20
110
  ### Added
@@ -22,10 +112,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
22
112
  - New `core/editors-config.js` tracks which editors user has installed commands to
23
113
  - Stores editor selections in `~/.prjct-cli/config/installed-editors.json`
24
114
  - Post-install hook (`scripts/post-install.js`) auto-updates commands after `npm update -g prjct-cli`
115
+ - **Auto-installation on first install** - Detects AI editors and installs commands automatically
25
116
  - Ensures version consistency across all configured editors (Claude, Cursor, Windsurf, Codex)
26
117
  - No manual reinstallation needed - updates happen automatically
27
118
  - Respects user's original editor choices from initial setup
28
119
 
120
+ - **Automatic Cleanup on Uninstall** - Clean uninstallation removes all traces
121
+ - New `scripts/preuninstall.js` runs before `npm uninstall -g prjct-cli`
122
+ - Automatically removes commands from all tracked editors
123
+ - Deletes tracking configuration from `~/.prjct-cli/config/`
124
+ - Added `uninstallFromEditor()` and `uninstallFromAll()` methods to command installer
125
+ - Added `deleteConfig()` method to editors config
126
+ - Prevents orphaned commands when package is uninstalled
127
+ - Clean exit even if cleanup fails (doesn't block uninstall)
128
+
29
129
  - **GitHub Packages Support** - Dual registry publication for better reliability
30
130
  - Package now published to both npm and GitHub Packages automatically
31
131
  - GitHub Actions workflow updated to publish to both registries in parallel
@@ -40,9 +140,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
40
140
  - Alternative: GitHub Packages (for advanced users or npm fallback)
41
141
  - Clear instructions for both installation methods
42
142
 
143
+ - **Command Installer Improvements**
144
+ - `installToSelected()` now always saves editor config (removed force-update restriction)
145
+ - Better tracking ensures updates work correctly even for force updates
146
+ - Improved error handling and reporting
147
+
43
148
  ### Technical Details
44
- - **Editor Tracking**: Configuration saved after successful command installation
45
- - **Post-Install Hook**: Runs only for global installations, skips for local/dev installs
149
+ - **Editor Tracking**: Configuration saved after all successful command installations
150
+ - **Post-Install Hook**:
151
+ - Runs only for global installations, skips for local/dev installs
152
+ - Auto-detects and installs to all AI editors on first install
153
+ - Auto-updates commands when version changes
154
+ - **Pre-Uninstall Hook**:
155
+ - Runs only for global uninstallations
156
+ - Removes slash commands from `~/.claude/`, `~/.cursor/`, `~/.windsurf/`
157
+ - Removes AGENTS.md from `~/.codex/` if present
158
+ - Deletes tracking configuration
46
159
  - **Version Detection**: Compares current version with last installed version
47
160
  - **Force Update**: Automatically updates commands when version changes
48
161
  - **Parallel Publication**: npm and GitHub Packages jobs run simultaneously for faster releases
package/bin/prjct CHANGED
@@ -139,6 +139,15 @@ async function main() {
139
139
 
140
140
  result = await commands.analyze(analyzeOptions);
141
141
  break;
142
+ case '--version':
143
+ case '-v':
144
+ case 'version':
145
+ const packageJson = require('../package.json');
146
+ result = {
147
+ success: true,
148
+ message: `prjct-cli v${packageJson.version}`
149
+ };
150
+ break;
142
151
  case '--help':
143
152
  case '-h':
144
153
  case 'help':
@@ -444,8 +444,8 @@ For detailed implementation, see prjct-cli documentation.
444
444
  const totalUpdated = Object.values(results)
445
445
  .reduce((sum, r) => sum + (r.updated || 0), 0)
446
446
 
447
- // Save editor selections to config (only on successful new installations, not updates)
448
- if (!forceUpdate && successfulEditors.length > 0) {
447
+ // Always save editor selections to config for tracking updates
448
+ if (successfulEditors.length > 0) {
449
449
  await this.saveEditorConfig(successfulEditors)
450
450
  }
451
451
 
@@ -807,6 +807,119 @@ This command uses the global prjct architecture:
807
807
 
808
808
  return results
809
809
  }
810
+
811
+ /**
812
+ * Uninstall commands from a specific editor
813
+ * @param {string} editorKey - Editor identifier (claude, cursor, codex, windsurf)
814
+ * @returns {Promise<Object>} Uninstallation result
815
+ */
816
+ async uninstallFromEditor(editorKey) {
817
+ const editor = this.editors[editorKey]
818
+
819
+ if (!editor) {
820
+ return { success: false, editor: editorKey, message: `Unknown editor: ${editorKey}` }
821
+ }
822
+
823
+ try {
824
+ switch (editor.format) {
825
+ case 'slash-commands':
826
+ // Remove the /p commands directory
827
+ await fs.rm(editor.commandsPath, { recursive: true, force: true })
828
+ return {
829
+ success: true,
830
+ editor: editor.name,
831
+ path: editor.commandsPath,
832
+ message: `Removed slash commands from ${editor.name}`,
833
+ }
834
+
835
+ case 'agents-md':
836
+ // Remove AGENTS.md if it exists
837
+ const exists = await this.fileExists(editor.commandsPath)
838
+ if (exists) {
839
+ await fs.unlink(editor.commandsPath)
840
+ }
841
+ return {
842
+ success: true,
843
+ editor: editor.name,
844
+ path: editor.commandsPath,
845
+ message: `Removed AGENTS.md from ${editor.name}`,
846
+ }
847
+
848
+ case 'workflows':
849
+ // Remove all p_*.md workflow files
850
+ try {
851
+ const files = await fs.readdir(editor.commandsPath)
852
+ const prjctWorkflows = files.filter(f => f.startsWith('p_') && f.endsWith('.md'))
853
+
854
+ for (const file of prjctWorkflows) {
855
+ await fs.unlink(path.join(editor.commandsPath, file))
856
+ }
857
+
858
+ return {
859
+ success: true,
860
+ editor: editor.name,
861
+ path: editor.commandsPath,
862
+ removed: prjctWorkflows.length,
863
+ message: `Removed ${prjctWorkflows.length} workflows from ${editor.name}`,
864
+ }
865
+ } catch (error) {
866
+ // Directory might not exist, that's ok
867
+ return {
868
+ success: true,
869
+ editor: editor.name,
870
+ message: `No workflows found in ${editor.name}`,
871
+ }
872
+ }
873
+
874
+ default:
875
+ return {
876
+ success: false,
877
+ editor: editor.name,
878
+ message: `Unknown format: ${editor.format}`,
879
+ }
880
+ }
881
+ } catch (error) {
882
+ return {
883
+ success: false,
884
+ editor: editor.name,
885
+ message: `Uninstall failed: ${error.message}`,
886
+ }
887
+ }
888
+ }
889
+
890
+ /**
891
+ * Uninstall commands from all tracked editors
892
+ * @returns {Promise<Object>} Uninstallation results
893
+ */
894
+ async uninstallFromAll() {
895
+ const editorsConfig = require('./editors-config')
896
+ const trackedEditors = await editorsConfig.getTrackedEditors()
897
+
898
+ if (trackedEditors.length === 0) {
899
+ return {
900
+ success: true,
901
+ message: 'No editors tracked, nothing to uninstall',
902
+ results: {},
903
+ }
904
+ }
905
+
906
+ const results = {}
907
+ const successfulEditors = []
908
+
909
+ for (const editorKey of trackedEditors) {
910
+ results[editorKey] = await this.uninstallFromEditor(editorKey)
911
+ if (results[editorKey].success) {
912
+ successfulEditors.push(this.editors[editorKey]?.name || editorKey)
913
+ }
914
+ }
915
+
916
+ return {
917
+ success: true,
918
+ editors: successfulEditors,
919
+ totalRemoved: successfulEditors.length,
920
+ results,
921
+ }
922
+ }
810
923
  }
811
924
 
812
925
  module.exports = new CommandInstaller()
package/core/commands.js CHANGED
@@ -1236,9 +1236,23 @@ ${dryRun ? '⚠️ DRY RUN - No changes were made' : '✅ All changes applied su
1236
1236
  try {
1237
1237
  await this.initializeAgent()
1238
1238
 
1239
+ // Verify project is initialized
1240
+ if (!await configManager.isConfigured(projectPath)) {
1241
+ return {
1242
+ success: false,
1243
+ message: this.agent.formatResponse(
1244
+ `Project not initialized. Run ${this.agentInfo.config.commandPrefix}init first.`,
1245
+ 'warning'
1246
+ ),
1247
+ }
1248
+ }
1249
+
1239
1250
  const type = options.type || 'architecture'
1240
1251
 
1241
- const designDir = path.join(projectPath, this.prjctDir, 'designs')
1252
+ // Use global architecture
1253
+ const projectId = await configManager.getProjectId(projectPath)
1254
+ const globalPath = pathManager.getGlobalProjectPath(projectId)
1255
+ const designDir = path.join(globalPath, 'analysis', 'designs')
1242
1256
  await this.agent.createDirectory(designDir)
1243
1257
 
1244
1258
  let designContent = ''
@@ -1484,7 +1498,11 @@ ${diagram}
1484
1498
  async getDaysSinceLastShip(projectPath) {
1485
1499
  try {
1486
1500
  await this.initializeAgent()
1487
- const memoryFile = path.join(projectPath, this.prjctDir, 'memory.jsonl')
1501
+
1502
+ // Use global architecture
1503
+ const projectId = await configManager.getProjectId(projectPath)
1504
+ const globalPath = pathManager.getGlobalProjectPath(projectId)
1505
+ const memoryFile = path.join(globalPath, 'memory', 'context.jsonl')
1488
1506
  const memory = await this.agent.readFile(memoryFile)
1489
1507
  const lines = memory
1490
1508
  .trim()
@@ -1670,14 +1688,28 @@ ${diagram}
1670
1688
  async cleanup(projectPath = process.cwd()) {
1671
1689
  try {
1672
1690
  await this.initializeAgent()
1673
- const prjctPath = path.join(projectPath, this.prjctDir)
1691
+
1692
+ // Verify project is initialized
1693
+ if (!await configManager.isConfigured(projectPath)) {
1694
+ return {
1695
+ success: false,
1696
+ message: this.agent.formatResponse(
1697
+ `Project not initialized. Run ${this.agentInfo.config.commandPrefix}init first.`,
1698
+ 'warning'
1699
+ ),
1700
+ }
1701
+ }
1702
+
1703
+ // Use global architecture
1704
+ const projectId = await configManager.getProjectId(projectPath)
1705
+ const globalPath = pathManager.getGlobalProjectPath(projectId)
1674
1706
 
1675
1707
  let totalFreed = 0
1676
1708
  let filesRemoved = 0
1677
1709
  let tasksArchived = 0
1678
1710
 
1679
1711
  try {
1680
- const tempDir = path.join(prjctPath, 'temp')
1712
+ const tempDir = path.join(globalPath, 'temp')
1681
1713
  const tempFiles = await fs.readdir(tempDir).catch(() => [])
1682
1714
  for (const file of tempFiles) {
1683
1715
  const filePath = path.join(tempDir, file)
@@ -1690,7 +1722,7 @@ ${diagram}
1690
1722
  }
1691
1723
 
1692
1724
  try {
1693
- const memoryFile = path.join(prjctPath, 'memory.jsonl')
1725
+ const memoryFile = path.join(globalPath, 'memory', 'context.jsonl')
1694
1726
  const content = await this.agent.readFile(memoryFile)
1695
1727
  const lines = content.split('\n').filter(line => line.trim())
1696
1728
  const now = new Date()
@@ -1714,7 +1746,7 @@ ${diagram}
1714
1746
  }
1715
1747
 
1716
1748
  if (archivedLines.length > 0) {
1717
- const archiveFile = path.join(prjctPath, `memory-archive-${now.toISOString().split('T')[0]}.jsonl`)
1749
+ const archiveFile = path.join(globalPath, 'memory', `archive-${now.toISOString().split('T')[0]}.jsonl`)
1718
1750
  await this.agent.writeFile(archiveFile, archivedLines.join('\n') + '\n')
1719
1751
  await this.agent.writeFile(memoryFile, recentLines.join('\n') + '\n')
1720
1752
  tasksArchived = archivedLines.length
@@ -1722,10 +1754,10 @@ ${diagram}
1722
1754
  } catch (e) {
1723
1755
  }
1724
1756
 
1725
- const files = await fs.readdir(prjctPath)
1757
+ const files = await fs.readdir(globalPath)
1726
1758
  for (const file of files) {
1727
1759
  if (file.endsWith('.md') || file.endsWith('.txt')) {
1728
- const filePath = path.join(prjctPath, file)
1760
+ const filePath = path.join(globalPath, file)
1729
1761
  const stats = await fs.stat(filePath)
1730
1762
  if (stats.size === 0) {
1731
1763
  await fs.unlink(filePath)
@@ -1735,7 +1767,7 @@ ${diagram}
1735
1767
  }
1736
1768
 
1737
1769
  try {
1738
- const shippedFile = path.join(prjctPath, 'shipped.md')
1770
+ const shippedFile = path.join(globalPath, 'progress', 'shipped.md')
1739
1771
  const content = await this.agent.readFile(shippedFile)
1740
1772
  const lines = content.split('\n')
1741
1773
  const now = new Date()
@@ -155,6 +155,24 @@ class EditorsConfig {
155
155
  return false
156
156
  }
157
157
  }
158
+
159
+ /**
160
+ * Delete configuration file
161
+ * Used during uninstallation to clean up tracking data
162
+ * @returns {Promise<boolean>} Success status
163
+ */
164
+ async deleteConfig() {
165
+ try {
166
+ const exists = await this.configExists()
167
+ if (exists) {
168
+ await fs.unlink(this.configFile)
169
+ }
170
+ return true
171
+ } catch (error) {
172
+ console.error('[editors-config] Error deleting config:', error.message)
173
+ return false
174
+ }
175
+ }
158
176
  }
159
177
 
160
178
  module.exports = new EditorsConfig()
package/core/migrator.js CHANGED
@@ -510,7 +510,7 @@ class Migrator {
510
510
  if (deepScan) {
511
511
  searchPaths = [os.homedir()]
512
512
  } else {
513
- const commonDirs = ['Projects', 'Documents', 'Developer', 'Code', 'dev', 'workspace', 'repos', 'src']
513
+ const commonDirs = ['Projects', 'Documents', 'Developer', 'Code', 'dev', 'workspace', 'repos', 'src', 'Apps']
514
514
  searchPaths = commonDirs
515
515
  .map(dir => path.join(os.homedir(), dir))
516
516
  .filter(dirPath => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
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": {
@@ -12,6 +12,7 @@
12
12
  },
13
13
  "scripts": {
14
14
  "postinstall": "node scripts/post-install.js",
15
+ "preuninstall": "node scripts/preuninstall.js",
15
16
  "install-global": "./scripts/install.sh",
16
17
  "test": "echo 'No tests configured'",
17
18
  "lint": "eslint \"**/*.js\" --ignore-pattern \"node_modules/**\" --ignore-pattern \"website/**\"",
@@ -69,6 +70,7 @@
69
70
  "scripts/install.sh",
70
71
  "scripts/verify-installation.sh",
71
72
  "scripts/post-install.js",
73
+ "scripts/preuninstall.js",
72
74
  "LICENSE",
73
75
  "README.md",
74
76
  "CHANGELOG.md",
@@ -12,7 +12,7 @@
12
12
  * 3. If config exists and version changed, force-update commands in tracked editors
13
13
  * 4. Update version in config
14
14
  *
15
- * @version 0.4.2
15
+ * @version 0.4.4
16
16
  */
17
17
 
18
18
  const path = require('path')
@@ -21,11 +21,18 @@ const { execSync } = require('child_process')
21
21
 
22
22
  async function main() {
23
23
  try {
24
+ // Get current package version (needed by multiple code paths)
25
+ const packageJson = require('../package.json')
26
+ const currentVersion = packageJson.version
27
+
24
28
  // Check if this is a global installation
25
29
  const isGlobal = await checkIfGlobalInstall()
26
30
 
27
31
  if (!isGlobal) {
28
32
  // Skip post-install for local/dev installations
33
+ if (process.env.DEBUG) {
34
+ console.log(chalk.gray('[post-install] Skipping - not a global install'))
35
+ }
29
36
  return
30
37
  }
31
38
 
@@ -34,15 +41,44 @@ async function main() {
34
41
  const configExists = await editorsConfig.configExists()
35
42
 
36
43
  if (!configExists) {
37
- // First-time install, no editors tracked yet
38
- // User will configure editors during `prjct init` or `prjct install`
44
+ // First-time install - auto-detect and install to all editors
45
+ console.log(chalk.cyan('\n🔍 First-time installation detected...\n'))
46
+
47
+ // Load command installer
48
+ const commandInstaller = require('../core/command-installer')
49
+
50
+ // Detect available editors
51
+ const detected = await commandInstaller.detectEditors()
52
+ const detectedEditors = Object.entries(detected)
53
+ .filter(([_, info]) => info.detected)
54
+ .map(([key, _]) => key)
55
+
56
+ if (detectedEditors.length === 0) {
57
+ // No editors detected, user will install manually later
58
+ console.log(chalk.yellow('ℹ️ No AI editors detected'))
59
+ console.log(chalk.gray(' Run `prjct install` when you set up Claude Code, Cursor, or Windsurf\n'))
60
+ return
61
+ }
62
+
63
+ console.log(chalk.cyan(`📦 Installing commands to: ${detectedEditors.map(k => commandInstaller.editors[k]?.name || k).join(', ')}\n`))
64
+
65
+ // Install to all detected editors
66
+ const results = await commandInstaller.installToAll(false)
67
+
68
+ if (results.success) {
69
+ console.log(chalk.green(`✅ Commands installed in: ${results.editors.join(', ')}`))
70
+ console.log(chalk.gray(` Commands installed: ${results.totalInstalled}`))
71
+ console.log(chalk.cyan(`\n✨ prjct-cli ${currentVersion} is ready!\n`))
72
+ } else {
73
+ console.log(chalk.yellow('⚠️ Some editors could not be configured'))
74
+ if (process.env.DEBUG) {
75
+ console.log(chalk.gray(` Error: ${results.message || 'Unknown error'}`))
76
+ }
77
+ }
78
+
39
79
  return
40
80
  }
41
81
 
42
- // Get current package version
43
- const packageJson = require('../package.json')
44
- const currentVersion = packageJson.version
45
-
46
82
  // Check if version has changed
47
83
  const versionChanged = await editorsConfig.hasVersionChanged(currentVersion)
48
84
 
@@ -78,6 +114,34 @@ async function main() {
78
114
  // Update version in config
79
115
  await editorsConfig.updateVersion(currentVersion)
80
116
 
117
+ // Auto-migrate legacy projects (v0.1.0 → v0.4.4)
118
+ try {
119
+ console.log(chalk.cyan('\n🔍 Checking for legacy projects to migrate...\n'))
120
+
121
+ const migrator = require('../core/migrator')
122
+
123
+ // Quick scan of common directories (not entire home directory)
124
+ const summary = await migrator.migrateAll({
125
+ deepScan: false, // Only scan common dirs (fast)
126
+ removeLegacy: false, // Keep legacy dirs for safety
127
+ cleanupLegacy: true, // Remove old dirs but keep config
128
+ dryRun: false, // Actually perform migration
129
+ interactive: false // No user prompts during npm install
130
+ })
131
+
132
+ if (summary.successfullyMigrated > 0) {
133
+ console.log(chalk.green(`✅ Migrated ${summary.successfullyMigrated} legacy project(s) to new structure`))
134
+ console.log(chalk.gray(` Data location: ~/.prjct-cli/projects/`))
135
+ } else if (summary.totalFound > 0 && summary.alreadyMigrated === summary.totalFound) {
136
+ console.log(chalk.gray('ℹ️ All projects already using new structure'))
137
+ }
138
+ } catch (migrationError) {
139
+ // Don't block install if migration fails
140
+ if (process.env.DEBUG) {
141
+ console.error(chalk.yellow('[post-install] Migration warning:'), migrationError.message)
142
+ }
143
+ }
144
+
81
145
  console.log(chalk.cyan(`\n✨ prjct-cli ${currentVersion} is ready!\n`))
82
146
 
83
147
  } catch (error) {
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Pre-Uninstall Script
5
+ *
6
+ * Runs automatically BEFORE `npm uninstall -g prjct-cli`.
7
+ * Cleans up slash commands from all tracked AI editors.
8
+ *
9
+ * Flow:
10
+ * 1. Check if running as global uninstall
11
+ * 2. Read tracked editors from ~/.prjct-cli/config/installed-editors.json
12
+ * 3. Remove all prjct commands from tracked editors
13
+ * 4. Delete tracking configuration
14
+ * 5. Clean exit (don't block uninstall)
15
+ *
16
+ * @version 0.4.4
17
+ */
18
+
19
+ const chalk = require('chalk')
20
+ const { execSync } = require('child_process')
21
+ const path = require('path')
22
+
23
+ async function main() {
24
+ try {
25
+ // Check if this is a global uninstallation
26
+ const isGlobal = await checkIfGlobalInstall()
27
+
28
+ if (!isGlobal) {
29
+ // Skip cleanup for local/dev uninstalls
30
+ return
31
+ }
32
+
33
+ // Load editors config
34
+ const editorsConfig = require('../core/editors-config')
35
+ const configExists = await editorsConfig.configExists()
36
+
37
+ if (!configExists) {
38
+ // No config, nothing to clean up
39
+ return
40
+ }
41
+
42
+ console.log(chalk.cyan('\n🧹 Cleaning up prjct commands from AI editors...\n'))
43
+
44
+ // Load command installer
45
+ const commandInstaller = require('../core/command-installer')
46
+
47
+ // Uninstall from all tracked editors
48
+ const results = await commandInstaller.uninstallFromAll()
49
+
50
+ if (results.success && results.editors.length > 0) {
51
+ console.log(chalk.green(`✅ Removed from: ${results.editors.join(', ')}`))
52
+ }
53
+
54
+ // Delete tracking config
55
+ await editorsConfig.deleteConfig()
56
+
57
+ console.log(chalk.green('\n✨ prjct-cli uninstalled cleanly\n'))
58
+
59
+ } catch (error) {
60
+ // Silently fail - don't block npm uninstall
61
+ // Only log if explicitly debugging
62
+ if (process.env.DEBUG) {
63
+ console.error(chalk.red('[preuninstall] Error:'), error.message)
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Check if package is being uninstalled globally
70
+ * @returns {Promise<boolean>} True if global uninstall
71
+ */
72
+ async function checkIfGlobalInstall() {
73
+ try {
74
+ // Get npm global root directory
75
+ const globalRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim()
76
+
77
+ // Get current package directory
78
+ const currentDir = path.resolve(__dirname, '..')
79
+
80
+ // Check if current directory is under global node_modules
81
+ return currentDir.startsWith(globalRoot)
82
+ } catch {
83
+ return false
84
+ }
85
+ }
86
+
87
+ // Run main function
88
+ main().catch(error => {
89
+ // Silently fail - don't block npm uninstall
90
+ if (process.env.DEBUG) {
91
+ console.error('[preuninstall] Fatal error:', error)
92
+ }
93
+ process.exit(0) // Exit with success to not block npm uninstall
94
+ })