appclean 1.8.0

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.
Files changed (154) hide show
  1. package/.github/workflows/publish.yml +41 -0
  2. package/.github/workflows/test.yml +37 -0
  3. package/ACTION_CHECKLIST.md +342 -0
  4. package/APPCLEAN_SUMMARY.md +309 -0
  5. package/CHANGELOG.md +205 -0
  6. package/CODE_OF_CONDUCT.md +49 -0
  7. package/CODE_REVIEW_REPORT.md +447 -0
  8. package/COMMUNITY_POSTS.md +307 -0
  9. package/CONTRIBUTING.md +121 -0
  10. package/DEPLOYMENT_GUIDE.md +345 -0
  11. package/DEPLOYMENT_STATUS.md +182 -0
  12. package/EXECUTIVE_REPORT.md +393 -0
  13. package/GITHUB_OPTIMIZATION.md +383 -0
  14. package/INDEX.md +165 -0
  15. package/LICENSE +21 -0
  16. package/MARKETING_SUMMARY.md +352 -0
  17. package/NPM_PACKAGE_OPTIMIZATION.md +281 -0
  18. package/NPM_PUBLISH.md +116 -0
  19. package/PROJECT_SUMMARY.txt +249 -0
  20. package/QUICKSTART.md +219 -0
  21. package/README.md +548 -0
  22. package/SECURITY.md +104 -0
  23. package/SETUP_GITHUB.md +237 -0
  24. package/TESTING_SUMMARY.md +379 -0
  25. package/dist/core/appUpdateChecker.d.ts +23 -0
  26. package/dist/core/appUpdateChecker.d.ts.map +1 -0
  27. package/dist/core/appUpdateChecker.js +159 -0
  28. package/dist/core/appUpdateChecker.js.map +1 -0
  29. package/dist/core/detector.d.ts +13 -0
  30. package/dist/core/detector.d.ts.map +1 -0
  31. package/dist/core/detector.js +99 -0
  32. package/dist/core/detector.js.map +1 -0
  33. package/dist/core/duplicateFileFinder.d.ts +14 -0
  34. package/dist/core/duplicateFileFinder.d.ts.map +1 -0
  35. package/dist/core/duplicateFileFinder.js +80 -0
  36. package/dist/core/duplicateFileFinder.js.map +1 -0
  37. package/dist/core/orphanedDependencyDetector.d.ts +19 -0
  38. package/dist/core/orphanedDependencyDetector.d.ts.map +1 -0
  39. package/dist/core/orphanedDependencyDetector.js +148 -0
  40. package/dist/core/orphanedDependencyDetector.js.map +1 -0
  41. package/dist/core/performanceOptimizer.d.ts +37 -0
  42. package/dist/core/performanceOptimizer.d.ts.map +1 -0
  43. package/dist/core/performanceOptimizer.js +128 -0
  44. package/dist/core/performanceOptimizer.js.map +1 -0
  45. package/dist/core/permissionHandler.d.ts +9 -0
  46. package/dist/core/permissionHandler.d.ts.map +1 -0
  47. package/dist/core/permissionHandler.js +89 -0
  48. package/dist/core/permissionHandler.js.map +1 -0
  49. package/dist/core/pluginSystem.d.ts +39 -0
  50. package/dist/core/pluginSystem.d.ts.map +1 -0
  51. package/dist/core/pluginSystem.js +120 -0
  52. package/dist/core/pluginSystem.js.map +1 -0
  53. package/dist/core/removalRecorder.d.ts +32 -0
  54. package/dist/core/removalRecorder.d.ts.map +1 -0
  55. package/dist/core/removalRecorder.js +79 -0
  56. package/dist/core/removalRecorder.js.map +1 -0
  57. package/dist/core/remover.d.ts +15 -0
  58. package/dist/core/remover.d.ts.map +1 -0
  59. package/dist/core/remover.js +225 -0
  60. package/dist/core/remover.js.map +1 -0
  61. package/dist/core/reportGenerator.d.ts +9 -0
  62. package/dist/core/reportGenerator.d.ts.map +1 -0
  63. package/dist/core/reportGenerator.js +328 -0
  64. package/dist/core/reportGenerator.js.map +1 -0
  65. package/dist/core/scheduledCleanup.d.ts +38 -0
  66. package/dist/core/scheduledCleanup.d.ts.map +1 -0
  67. package/dist/core/scheduledCleanup.js +127 -0
  68. package/dist/core/scheduledCleanup.js.map +1 -0
  69. package/dist/core/serviceFileDetector.d.ts +18 -0
  70. package/dist/core/serviceFileDetector.d.ts.map +1 -0
  71. package/dist/core/serviceFileDetector.js +136 -0
  72. package/dist/core/serviceFileDetector.js.map +1 -0
  73. package/dist/core/verificationModule.d.ts +14 -0
  74. package/dist/core/verificationModule.d.ts.map +1 -0
  75. package/dist/core/verificationModule.js +102 -0
  76. package/dist/core/verificationModule.js.map +1 -0
  77. package/dist/index.d.ts +3 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +333 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/managers/brewManager.d.ts +10 -0
  82. package/dist/managers/brewManager.d.ts.map +1 -0
  83. package/dist/managers/brewManager.js +130 -0
  84. package/dist/managers/brewManager.js.map +1 -0
  85. package/dist/managers/customManager.d.ts +8 -0
  86. package/dist/managers/customManager.d.ts.map +1 -0
  87. package/dist/managers/customManager.js +139 -0
  88. package/dist/managers/customManager.js.map +1 -0
  89. package/dist/managers/linuxManager.d.ts +10 -0
  90. package/dist/managers/linuxManager.d.ts.map +1 -0
  91. package/dist/managers/linuxManager.js +191 -0
  92. package/dist/managers/linuxManager.js.map +1 -0
  93. package/dist/managers/npmManager.d.ts +10 -0
  94. package/dist/managers/npmManager.d.ts.map +1 -0
  95. package/dist/managers/npmManager.js +119 -0
  96. package/dist/managers/npmManager.js.map +1 -0
  97. package/dist/types/index.d.ts +44 -0
  98. package/dist/types/index.d.ts.map +1 -0
  99. package/dist/types/index.js +3 -0
  100. package/dist/types/index.js.map +1 -0
  101. package/dist/ui/guiServer.d.ts +10 -0
  102. package/dist/ui/guiServer.d.ts.map +1 -0
  103. package/dist/ui/guiServer.js +134 -0
  104. package/dist/ui/guiServer.js.map +1 -0
  105. package/dist/ui/menu.d.ts +6 -0
  106. package/dist/ui/menu.d.ts.map +1 -0
  107. package/dist/ui/menu.js +93 -0
  108. package/dist/ui/menu.js.map +1 -0
  109. package/dist/ui/prompts.d.ts +13 -0
  110. package/dist/ui/prompts.d.ts.map +1 -0
  111. package/dist/ui/prompts.js +161 -0
  112. package/dist/ui/prompts.js.map +1 -0
  113. package/dist/utils/filesystem.d.ts +13 -0
  114. package/dist/utils/filesystem.d.ts.map +1 -0
  115. package/dist/utils/filesystem.js +152 -0
  116. package/dist/utils/filesystem.js.map +1 -0
  117. package/dist/utils/logger.d.ts +12 -0
  118. package/dist/utils/logger.d.ts.map +1 -0
  119. package/dist/utils/logger.js +49 -0
  120. package/dist/utils/logger.js.map +1 -0
  121. package/dist/utils/platform.d.ts +9 -0
  122. package/dist/utils/platform.d.ts.map +1 -0
  123. package/dist/utils/platform.js +75 -0
  124. package/dist/utils/platform.js.map +1 -0
  125. package/jest.config.js +20 -0
  126. package/logo.svg +60 -0
  127. package/package.json +55 -0
  128. package/setup-github.sh +125 -0
  129. package/src/core/appUpdateChecker.ts +220 -0
  130. package/src/core/detector.ts +133 -0
  131. package/src/core/duplicateFileFinder.ts +113 -0
  132. package/src/core/orphanedDependencyDetector.ts +195 -0
  133. package/src/core/performanceOptimizer.ts +209 -0
  134. package/src/core/permissionHandler.ts +121 -0
  135. package/src/core/pluginSystem.ts +194 -0
  136. package/src/core/removalRecorder.ts +146 -0
  137. package/src/core/remover.ts +280 -0
  138. package/src/core/reportGenerator.ts +354 -0
  139. package/src/core/scheduledCleanup.ts +204 -0
  140. package/src/core/serviceFileDetector.ts +181 -0
  141. package/src/core/verificationModule.ts +140 -0
  142. package/src/index.ts +449 -0
  143. package/src/managers/brewManager.ts +149 -0
  144. package/src/managers/customManager.ts +167 -0
  145. package/src/managers/linuxManager.ts +210 -0
  146. package/src/managers/npmManager.ts +137 -0
  147. package/src/types/index.ts +59 -0
  148. package/src/ui/guiServer.ts +155 -0
  149. package/src/ui/menu.ts +100 -0
  150. package/src/ui/prompts.ts +177 -0
  151. package/src/utils/filesystem.ts +145 -0
  152. package/src/utils/logger.ts +48 -0
  153. package/src/utils/platform.ts +75 -0
  154. package/tsconfig.json +20 -0
package/logo.svg ADDED
@@ -0,0 +1,60 @@
1
+ <svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <!-- Background Circle -->
3
+ <circle cx="256" cy="256" r="256" fill="#1F2937"/>
4
+
5
+ <!-- Inner Circle -->
6
+ <circle cx="256" cy="256" r="240" fill="#111827"/>
7
+
8
+ <!-- Gradient Definition -->
9
+ <defs>
10
+ <linearGradient id="gradientBlue" x1="0%" y1="0%" x2="100%" y2="100%">
11
+ <stop offset="0%" style="stop-color:#3B82F6;stop-opacity:1" />
12
+ <stop offset="100%" style="stop-color:#1E40AF;stop-opacity:1" />
13
+ </linearGradient>
14
+ <linearGradient id="gradientGreen" x1="0%" y1="0%" x2="100%" y2="100%">
15
+ <stop offset="0%" style="stop-color:#10B981;stop-opacity:1" />
16
+ <stop offset="100%" style="stop-color:#059669;stop-opacity:1" />
17
+ </linearGradient>
18
+ </defs>
19
+
20
+ <!-- Main Trash/Cleanup Icon -->
21
+ <!-- Trash can body -->
22
+ <path d="M 150 180 L 362 180 L 380 200 L 132 200 Z" fill="url(#gradientBlue)" opacity="0.9"/>
23
+
24
+ <!-- Trash can top/lid -->
25
+ <rect x="160" y="160" width="192" height="25" rx="4" fill="url(#gradientBlue)" opacity="0.7"/>
26
+
27
+ <!-- Handle -->
28
+ <path d="M 220 160 Q 220 140 256 140 Q 292 140 292 160" stroke="url(#gradientBlue)" stroke-width="12" fill="none" stroke-linecap="round"/>
29
+
30
+ <!-- Trash can container -->
31
+ <rect x="140" y="200" width="232" height="160" rx="8" fill="url(#gradientBlue)" opacity="0.8"/>
32
+
33
+ <!-- Checkmark (clean/success) - overlaid on trash -->
34
+ <g transform="translate(256, 280)">
35
+ <!-- Checkmark circle background -->
36
+ <circle cx="0" cy="0" r="55" fill="url(#gradientGreen)" opacity="0.95"/>
37
+
38
+ <!-- White checkmark -->
39
+ <path d="M -20 0 L -5 15 L 25 -20" stroke="white" stroke-width="8" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
40
+ </g>
41
+
42
+ <!-- Decorative elements - removed items -->
43
+ <!-- Floating particles representing cleanup -->
44
+ <circle cx="100" cy="150" r="8" fill="#3B82F6" opacity="0.6"/>
45
+ <circle cx="412" cy="160" r="6" fill="#10B981" opacity="0.6"/>
46
+ <circle cx="110" cy="350" r="7" fill="#10B981" opacity="0.5"/>
47
+ <circle cx="400" cy="360" r="5" fill="#3B82F6" opacity="0.5"/>
48
+
49
+ <!-- Floating lines (particles dissolving) -->
50
+ <line x1="95" y1="140" x2="70" y2="110" stroke="#3B82F6" stroke-width="2" opacity="0.4"/>
51
+ <line x1="420" y1="180" x2="450" y2="150" stroke="#10B981" stroke-width="2" opacity="0.4"/>
52
+ <line x1="105" y1="370" x2="75" y2="400" stroke="#10B981" stroke-width="2" opacity="0.4"/>
53
+ <line x1="410" y1="375" x2="445" y2="410" stroke="#3B82F6" stroke-width="2" opacity="0.4"/>
54
+
55
+ <!-- Text: APPCLEAN -->
56
+ <text x="256" y="440" font-family="Inter, -apple-system, system-ui, sans-serif" font-size="32" font-weight="700" text-anchor="middle" fill="white" letter-spacing="-0.5">AppClean</text>
57
+
58
+ <!-- Tagline -->
59
+ <text x="256" y="470" font-family="Inter, -apple-system, system-ui, sans-serif" font-size="12" text-anchor="middle" fill="#9CA3AF" letter-spacing="0.5">Clean • Safe • Smart</text>
60
+ </svg>
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "appclean",
3
+ "version": "1.8.0",
4
+ "description": "A powerful CLI tool to intelligently find and safely uninstall applications from your system with all their artifacts",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "appclean": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "ts-node src/index.ts",
12
+ "start": "node dist/index.js",
13
+ "test": "jest",
14
+ "clean": "rm -rf dist",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "keywords": [
18
+ "uninstaller",
19
+ "app-management",
20
+ "cli",
21
+ "cross-platform",
22
+ "npm",
23
+ "brew",
24
+ "package-manager",
25
+ "cleanup"
26
+ ],
27
+ "author": "Praveen Kothapally <dev@praveenkothapally.dev>",
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "chalk": "^4.1.2",
31
+ "commander": "^11.1.0",
32
+ "inquirer": "^8.2.6",
33
+ "ora": "^5.4.1"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20.10.0",
37
+ "@types/inquirer": "^8.2.10",
38
+ "typescript": "^5.3.3",
39
+ "ts-node": "^10.9.2",
40
+ "jest": "^29.7.0",
41
+ "@types/jest": "^29.5.11",
42
+ "ts-jest": "^29.1.1"
43
+ },
44
+ "engines": {
45
+ "node": ">=16.0.0"
46
+ },
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/praveenkay/AppClean.git"
50
+ },
51
+ "bugs": {
52
+ "url": "https://github.com/praveenkay/AppClean/issues"
53
+ },
54
+ "homepage": "https://github.com/praveenkay/AppClean#readme"
55
+ }
@@ -0,0 +1,125 @@
1
+ #!/bin/bash
2
+
3
+ # AppClean GitHub Setup Script
4
+ # This script automates the setup and deployment process
5
+
6
+ set -e
7
+
8
+ echo "╔════════════════════════════════════════════════════════════════╗"
9
+ echo "║ AppClean - GitHub Setup Automation ║"
10
+ echo "╚════════════════════════════════════════════════════════════════╝"
11
+ echo ""
12
+
13
+ # Colors
14
+ RED='\033[0;31m'
15
+ GREEN='\033[0;32m'
16
+ YELLOW='\033[1;33m'
17
+ BLUE='\033[0;34m'
18
+ NC='\033[0m' # No Color
19
+
20
+ # Check prerequisites
21
+ echo -e "${BLUE}[1/5]${NC} Checking prerequisites..."
22
+ if ! command -v git &> /dev/null; then
23
+ echo -e "${RED}❌ Git is not installed${NC}"
24
+ exit 1
25
+ fi
26
+
27
+ if ! command -v npm &> /dev/null; then
28
+ echo -e "${RED}❌ npm is not installed${NC}"
29
+ exit 1
30
+ fi
31
+
32
+ if ! command -v node &> /dev/null; then
33
+ echo -e "${RED}❌ Node.js is not installed${NC}"
34
+ exit 1
35
+ fi
36
+
37
+ echo -e "${GREEN}✓ Git, npm, and Node.js found${NC}"
38
+ echo ""
39
+
40
+ # Get GitHub username
41
+ echo -e "${BLUE}[2/5]${NC} Getting your GitHub username..."
42
+ read -p "Enter your GitHub username: " GITHUB_USERNAME
43
+
44
+ if [ -z "$GITHUB_USERNAME" ]; then
45
+ echo -e "${RED}❌ GitHub username is required${NC}"
46
+ exit 1
47
+ fi
48
+
49
+ echo -e "${GREEN}✓ GitHub username: $GITHUB_USERNAME${NC}"
50
+ echo ""
51
+
52
+ # Get email and name
53
+ echo -e "${BLUE}[3/5]${NC} Getting your information..."
54
+ read -p "Enter your name: " USER_NAME
55
+ read -p "Enter your email: " USER_EMAIL
56
+
57
+ if [ -z "$USER_NAME" ] || [ -z "$USER_EMAIL" ]; then
58
+ echo -e "${RED}❌ Name and email are required${NC}"
59
+ exit 1
60
+ fi
61
+
62
+ echo -e "${GREEN}✓ Name: $USER_NAME${NC}"
63
+ echo -e "${GREEN}✓ Email: $USER_EMAIL${NC}"
64
+ echo ""
65
+
66
+ # Configure git
67
+ echo -e "${BLUE}[4/5]${NC} Configuring Git..."
68
+ git config user.name "$USER_NAME"
69
+ git config user.email "$USER_EMAIL"
70
+
71
+ # Update package.json
72
+ sed -i '' "s|YOUR_USERNAME|$GITHUB_USERNAME|g" package.json
73
+ sed -i '' "s|Your Name|$USER_NAME|g" package.json
74
+
75
+ echo -e "${GREEN}✓ Git configured${NC}"
76
+ echo -e "${GREEN}✓ package.json updated${NC}"
77
+ echo ""
78
+
79
+ # Setup git remote
80
+ echo -e "${BLUE}[5/5]${NC} Setting up Git remote..."
81
+
82
+ git remote rm origin 2>/dev/null || true
83
+ git remote add origin "https://github.com/$GITHUB_USERNAME/appclean.git"
84
+
85
+ # Rename master to main
86
+ if git rev-parse --verify master &>/dev/null; then
87
+ git branch -m master main
88
+ fi
89
+
90
+ echo -e "${GREEN}✓ Git remote configured${NC}"
91
+ echo ""
92
+
93
+ # Summary
94
+ echo "╔════════════════════════════════════════════════════════════════╗"
95
+ echo -e "${GREEN}✓ Setup Complete!${NC}"
96
+ echo "╚════════════════════════════════════════════════════════════════╝"
97
+ echo ""
98
+
99
+ echo -e "${YELLOW}Next Steps:${NC}"
100
+ echo ""
101
+ echo "1. Create GitHub repository:"
102
+ echo " 👉 Visit: https://github.com/new"
103
+ echo " - Name: appclean"
104
+ echo " - Description: A powerful CLI tool to intelligently find and safely uninstall applications"
105
+ echo " - Public"
106
+ echo ""
107
+ echo "2. Push to GitHub:"
108
+ echo " ${BLUE}git push -u origin main${NC}"
109
+ echo ""
110
+ echo "3. Setup npm:"
111
+ echo " ${BLUE}npm login${NC}"
112
+ echo ""
113
+ echo "4. Publish to npm:"
114
+ echo " ${BLUE}git tag v1.0.0${NC}"
115
+ echo " ${BLUE}git push origin v1.0.0${NC}"
116
+ echo " ${BLUE}npm publish${NC}"
117
+ echo ""
118
+ echo -e "${YELLOW}Repository will be at:${NC}"
119
+ echo " 📦 GitHub: https://github.com/$GITHUB_USERNAME/appclean"
120
+ echo " 📝 npm: https://npmjs.com/package/appclean"
121
+ echo ""
122
+ echo -e "${YELLOW}For detailed instructions, see:${NC}"
123
+ echo " 📖 SETUP_GITHUB.md"
124
+ echo " 📖 QUICKSTART.md"
125
+ echo ""
@@ -0,0 +1,220 @@
1
+ /**
2
+ * App Update Checker
3
+ * Checks for available updates for installed applications
4
+ * v1.7.0 Feature
5
+ */
6
+
7
+ import { execSync } from 'child_process';
8
+ import { Logger } from '../utils/logger';
9
+ import { InstalledApp } from '../types';
10
+
11
+ export interface UpdateInfo {
12
+ appName: string;
13
+ currentVersion: string;
14
+ latestVersion: string;
15
+ hasUpdate: boolean;
16
+ releaseDate?: Date;
17
+ changelog?: string;
18
+ installCommand?: string;
19
+ }
20
+
21
+ export class AppUpdateChecker {
22
+ /**
23
+ * Check for updates for a specific app
24
+ */
25
+ async checkForUpdate(app: InstalledApp): Promise<UpdateInfo> {
26
+ Logger.info(`Checking for updates: ${app.name}...`);
27
+
28
+ const info: UpdateInfo = {
29
+ appName: app.name,
30
+ currentVersion: app.version,
31
+ latestVersion: app.version,
32
+ hasUpdate: false,
33
+ };
34
+
35
+ try {
36
+ switch (app.installMethod) {
37
+ case 'npm':
38
+ await this.checkNpmUpdate(info);
39
+ break;
40
+ case 'yarn':
41
+ await this.checkYarnUpdate(info);
42
+ break;
43
+ case 'pnpm':
44
+ await this.checkPnpmUpdate(info);
45
+ break;
46
+ case 'brew':
47
+ await this.checkBrewUpdate(info);
48
+ break;
49
+ case 'apt':
50
+ await this.checkAptUpdate(info);
51
+ break;
52
+ case 'yum':
53
+ case 'dnf':
54
+ await this.checkYumUpdate(info);
55
+ break;
56
+ default:
57
+ Logger.warn(`No update checker available for ${app.installMethod}`);
58
+ }
59
+ } catch (error) {
60
+ Logger.debug(`Update check failed for ${app.name}: ${(error as Error).message}`);
61
+ }
62
+
63
+ return info;
64
+ }
65
+
66
+ /**
67
+ * Check for updates for multiple apps
68
+ */
69
+ async checkForUpdates(apps: InstalledApp[]): Promise<UpdateInfo[]> {
70
+ const updates: UpdateInfo[] = [];
71
+
72
+ for (const app of apps) {
73
+ const update = await this.checkForUpdate(app);
74
+ updates.push(update);
75
+ }
76
+
77
+ return updates;
78
+ }
79
+
80
+ /**
81
+ * Check npm registry for updates
82
+ */
83
+ private async checkNpmUpdate(info: UpdateInfo): Promise<void> {
84
+ try {
85
+ const output = execSync(`npm view ${info.appName} version 2>/dev/null`).toString().trim();
86
+ if (output) {
87
+ info.latestVersion = output;
88
+ info.hasUpdate = this.compareVersions(info.currentVersion, output) < 0;
89
+ info.installCommand = `npm install -g ${info.appName}@latest`;
90
+ }
91
+ } catch {
92
+ // Package not found or network error
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Check yarn registry for updates
98
+ */
99
+ private async checkYarnUpdate(info: UpdateInfo): Promise<void> {
100
+ try {
101
+ const output = execSync(`yarn info ${info.appName} version 2>/dev/null`).toString().trim();
102
+ if (output) {
103
+ info.latestVersion = output;
104
+ info.hasUpdate = this.compareVersions(info.currentVersion, output) < 0;
105
+ info.installCommand = `yarn global upgrade ${info.appName}`;
106
+ }
107
+ } catch {
108
+ // Package not found or network error
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Check pnpm registry for updates
114
+ */
115
+ private async checkPnpmUpdate(info: UpdateInfo): Promise<void> {
116
+ try {
117
+ const output = execSync(`pnpm view ${info.appName} version 2>/dev/null`).toString().trim();
118
+ if (output) {
119
+ info.latestVersion = output;
120
+ info.hasUpdate = this.compareVersions(info.currentVersion, output) < 0;
121
+ info.installCommand = `pnpm add -g ${info.appName}@latest`;
122
+ }
123
+ } catch {
124
+ // Package not found or network error
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Check homebrew for updates
130
+ */
131
+ private async checkBrewUpdate(info: UpdateInfo): Promise<void> {
132
+ try {
133
+ const output = execSync(`brew info ${info.appName} 2>/dev/null | grep "stable"`).toString();
134
+ const match = output.match(/(\d+\.\d+\.\d+)/);
135
+ if (match) {
136
+ info.latestVersion = match[1];
137
+ info.hasUpdate = this.compareVersions(info.currentVersion, match[1]) < 0;
138
+ info.installCommand = `brew upgrade ${info.appName}`;
139
+ }
140
+ } catch {
141
+ // Package not found or network error
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Check apt for updates
147
+ */
148
+ private async checkAptUpdate(info: UpdateInfo): Promise<void> {
149
+ try {
150
+ const output = execSync(`apt-cache policy ${info.appName} 2>/dev/null`).toString();
151
+ const match = output.match(/Candidate: (\S+)/);
152
+ if (match) {
153
+ info.latestVersion = match[1];
154
+ info.hasUpdate = this.compareVersions(info.currentVersion, match[1]) < 0;
155
+ info.installCommand = `sudo apt update && sudo apt upgrade ${info.appName}`;
156
+ }
157
+ } catch {
158
+ // Package not found or network error
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Check yum/dnf for updates
164
+ */
165
+ private async checkYumUpdate(info: UpdateInfo): Promise<void> {
166
+ try {
167
+ const output = execSync(`yum info ${info.appName} 2>/dev/null`).toString();
168
+ const match = output.match(/Version\s+:\s+(\S+)/);
169
+ if (match) {
170
+ info.latestVersion = match[1];
171
+ info.hasUpdate = this.compareVersions(info.currentVersion, match[1]) < 0;
172
+ info.installCommand = `sudo yum update ${info.appName}`;
173
+ }
174
+ } catch {
175
+ // Package not found or network error
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Compare semantic versions
181
+ * Returns: -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2
182
+ */
183
+ private compareVersions(v1: string, v2: string): number {
184
+ const parts1 = v1.split('.').map((p) => parseInt(p, 10));
185
+ const parts2 = v2.split('.').map((p) => parseInt(p, 10));
186
+
187
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
188
+ const p1 = parts1[i] || 0;
189
+ const p2 = parts2[i] || 0;
190
+
191
+ if (p1 < p2) return -1;
192
+ if (p1 > p2) return 1;
193
+ }
194
+
195
+ return 0;
196
+ }
197
+
198
+ /**
199
+ * Get formatted update summary
200
+ */
201
+ getUpdateSummary(updates: UpdateInfo[]): string {
202
+ const available = updates.filter((u) => u.hasUpdate);
203
+
204
+ if (available.length === 0) {
205
+ return 'All applications are up to date!';
206
+ }
207
+
208
+ let summary = `Found ${available.length} update(s) available:\n\n`;
209
+ available.forEach((update) => {
210
+ summary += `📦 ${update.appName}\n`;
211
+ summary += ` Current: ${update.currentVersion} → Latest: ${update.latestVersion}\n`;
212
+ if (update.installCommand) {
213
+ summary += ` Install: ${update.installCommand}\n`;
214
+ }
215
+ summary += '\n';
216
+ });
217
+
218
+ return summary;
219
+ }
220
+ }
@@ -0,0 +1,133 @@
1
+ import { NpmManager } from '../managers/npmManager';
2
+ import { BrewManager } from '../managers/brewManager';
3
+ import { LinuxManager } from '../managers/linuxManager';
4
+ import { CustomManager } from '../managers/customManager';
5
+ import { isMacOS, isLinux } from '../utils/platform';
6
+ import { InstalledApp, ArtifactPath, SearchOptions } from '../types';
7
+ import { getFileSize } from '../utils/filesystem';
8
+
9
+ export class Detector {
10
+ private npmManager: NpmManager;
11
+ private brewManager?: BrewManager;
12
+ private linuxManager?: LinuxManager;
13
+ private customManager: CustomManager;
14
+
15
+ constructor() {
16
+ this.npmManager = new NpmManager();
17
+ this.customManager = new CustomManager();
18
+
19
+ if (isMacOS()) {
20
+ this.brewManager = new BrewManager();
21
+ }
22
+
23
+ if (isLinux()) {
24
+ this.linuxManager = new LinuxManager();
25
+ }
26
+ }
27
+
28
+ async detectAllApps(): Promise<InstalledApp[]> {
29
+ const allApps: InstalledApp[] = [];
30
+
31
+ // npm packages
32
+ const npmApps = await this.npmManager.getInstalledPackages();
33
+ allApps.push(...npmApps);
34
+
35
+ // Homebrew packages (macOS only)
36
+ if (this.brewManager) {
37
+ const brewApps = await this.brewManager.getInstalledPackages();
38
+ allApps.push(...brewApps);
39
+ }
40
+
41
+ // Linux packages
42
+ if (this.linuxManager) {
43
+ const linuxApps = await this.linuxManager.getInstalledPackages();
44
+ allApps.push(...linuxApps);
45
+ }
46
+
47
+ // Custom binaries
48
+ const customApps = await this.customManager.findCustomInstalledApps();
49
+ allApps.push(...customApps);
50
+
51
+ // Remove duplicates by name
52
+ const seen = new Set<string>();
53
+ return allApps.filter((app) => {
54
+ if (seen.has(app.name)) return false;
55
+ seen.add(app.name);
56
+ return true;
57
+ });
58
+ }
59
+
60
+ async searchApps(options: SearchOptions): Promise<InstalledApp[]> {
61
+ const allApps = await this.detectAllApps();
62
+
63
+ let filtered = allApps;
64
+
65
+ // Filter by query
66
+ if (options.query) {
67
+ const query = options.query.toLowerCase();
68
+ filtered = filtered.filter((app) =>
69
+ app.name.toLowerCase().includes(query)
70
+ );
71
+ }
72
+
73
+ // Filter by install method
74
+ if (options.installMethod) {
75
+ filtered = filtered.filter((app) => app.installMethod === options.installMethod);
76
+ }
77
+
78
+ // Sort
79
+ switch (options.sortBy) {
80
+ case 'name':
81
+ filtered.sort((a, b) => a.name.localeCompare(b.name));
82
+ break;
83
+ case 'date':
84
+ filtered.sort(
85
+ (a, b) =>
86
+ (b.installedDate?.getTime() || 0) - (a.installedDate?.getTime() || 0)
87
+ );
88
+ break;
89
+ case 'size':
90
+ filtered.sort((a, b) => (b.size || 0) - (a.size || 0));
91
+ break;
92
+ }
93
+
94
+ return filtered;
95
+ }
96
+
97
+ async findArtifacts(appName: string, installMethod: string): Promise<ArtifactPath[]> {
98
+ const artifacts: ArtifactPath[] = [];
99
+
100
+ switch (installMethod) {
101
+ case 'npm':
102
+ artifacts.push(...(await this.npmManager.findArtifacts(appName)));
103
+ break;
104
+ case 'brew':
105
+ if (this.brewManager) {
106
+ artifacts.push(...(await this.brewManager.findArtifacts(appName)));
107
+ }
108
+ break;
109
+ case 'apt':
110
+ case 'yum':
111
+ case 'dnf':
112
+ if (this.linuxManager) {
113
+ artifacts.push(...(await this.linuxManager.findArtifacts(appName)));
114
+ }
115
+ break;
116
+ case 'custom':
117
+ artifacts.push(...(await this.customManager.findArtifacts(appName)));
118
+ break;
119
+ }
120
+
121
+ // Calculate sizes
122
+ for (const artifact of artifacts) {
123
+ artifact.size = getFileSize(artifact.path);
124
+ }
125
+
126
+ return artifacts;
127
+ }
128
+
129
+ async estimateSpaceFreed(appName: string, installMethod: string): Promise<number> {
130
+ const artifacts = await this.findArtifacts(appName, installMethod);
131
+ return artifacts.reduce((total, artifact) => total + artifact.size, 0);
132
+ }
133
+ }