projxo 1.1.0 → 1.2.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.
package/Readme.md CHANGED
@@ -21,6 +21,12 @@ pxo
21
21
 
22
22
  # List all your projects
23
23
  pxo list
24
+
25
+ # Open a project quickly
26
+ pxo open my-app
27
+
28
+ # List all your recently accessed projects
29
+ pxo recent
24
30
  ```
25
31
 
26
32
  **That's it!** Pick your framework, name your project, and start coding.
@@ -46,6 +52,8 @@ You get:
46
52
  ```bash
47
53
  pxo # Create & track projects
48
54
  pxo list # See all your projects
55
+ pxo open my-app # Open instantly
56
+ pxo recent # See all recent projects
49
57
  ```
50
58
 
51
59
  **One command. Zero hassle.**
@@ -71,6 +79,8 @@ npm install -g projxo
71
79
  |---------|-------|-------------|
72
80
  | `pxo` | - | Create a new project with interactive setup |
73
81
  | `pxo list` | `pxo ls` | Browse and manage all tracked projects |
82
+ | `pxo recent [limit]` | - | Browse recently accessed projects |
83
+ | `pxo open <project-name>` | - | Quick open project by name |
74
84
  | `pxo --version` | `pxo -V` | Show version number |
75
85
  | `pxo --help` | `pxo -h` | Display help information |
76
86
 
@@ -155,6 +165,75 @@ Select a project and perform actions
155
165
  Use ↑↓ to navigate • Enter to select
156
166
  ```
157
167
 
168
+ ### Recent Projects
169
+
170
+ ```bash
171
+ pxo recent
172
+ # or with custom limit
173
+ pxo recent 5
174
+ ```
175
+
176
+ ***Shows your recently accessed projects (default: last 10)***
177
+
178
+ **Features:**
179
+
180
+ - Sorted by last accessed time (most recent first)
181
+ - Quick selection with arrow keys
182
+ - Select to open in your preferred IDE
183
+
184
+ **Example output:**
185
+
186
+ ``` bash
187
+ 🕐 Recent Projects (5)
188
+
189
+ Select a project to open:
190
+ ❯ 1. my-awesome-app React+Vite 2 hours ago
191
+ 2. client-dashboard Next.js 1 day ago
192
+ 3. mobile-game React Native 3 days ago
193
+ 4. api-server Next.js 5 days ago
194
+ 5. test-project React+Vite 1 week ago
195
+ ```
196
+
197
+ **Use case:** Perfect for quickly switching between active projects without browsing the full list.
198
+
199
+ ### Quick Open Project
200
+
201
+ ```bash
202
+ pxo open <project-name>
203
+ ```
204
+
205
+ **Instantly open a project by name** - the fastest way to access your work.
206
+
207
+ **Features:**
208
+
209
+ - Direct project opening by name
210
+ - Fuzzy search if exact match not found
211
+ - Opens in your preferred IDE
212
+ - Updates last accessed timestamp
213
+
214
+ **Example output:**
215
+
216
+ ``` bash
217
+ # Exact match
218
+ pxo open my-awesome-app
219
+
220
+ # Fuzzy match (finds "my-awesome-app")
221
+ pxo open awesome
222
+
223
+ # Multiple matches - shows selection menu
224
+ pxo open app
225
+ ```
226
+
227
+ ```bash
228
+ $ pxo open dashboard
229
+
230
+ Found similar project: client-dashboard
231
+ ✓ Opening client-dashboard in VS Code...
232
+ ✓ Opened client-dashboard
233
+ ```
234
+
235
+ **Use case:** When you know the project name, this is the fastest way to open it.
236
+
158
237
  ---
159
238
 
160
239
  ### Version & Help
@@ -209,10 +288,46 @@ $ pxo list
209
288
  # - View details
210
289
  ```
211
290
 
291
+ ### Example 3: Quick Access Workflow
292
+
293
+ ```bash
294
+ # Morning: See what you worked on recently
295
+ $ pxo recent
296
+ # → Select and open your active project
297
+
298
+ # Later: Quick open by name
299
+ $ pxo open client-dashboard
300
+ ✓ Opened client-dashboard
301
+
302
+ # End of day: Browse all projects
303
+ $ pxo list
304
+ # → Review and organize
305
+ ```
306
+
212
307
  ---
213
308
 
214
309
  ## 💡 Tips & Tricks
215
310
 
311
+ ### Use Recent for Active Work
312
+
313
+ ```bash
314
+ # Working on multiple projects?
315
+ pxo recent
316
+
317
+ # Shows only what you've touched recently
318
+ # Much faster than scrolling through all projects
319
+ ```
320
+
321
+ ### Quick Open for Speed
322
+
323
+ ```bash
324
+ # If you remember the name, use open
325
+ pxo open my-app
326
+
327
+ # Fuzzy search helps with partial names
328
+ pxo open dash # finds "client-dashboard"
329
+ ```
330
+
216
331
  ### Organize Your Projects
217
332
 
218
333
  ```bash
@@ -298,6 +413,18 @@ npm install -g projxo
298
413
 
299
414
  Only projects created after installing v1.1.0+ are automatically tracked.
300
415
 
416
+ ### Project Not Found (Open Command)
417
+
418
+ If `pxo open` can't find your project:
419
+
420
+ ```bash
421
+ # Use list to see exact names
422
+ pxo list
423
+
424
+ # Or try partial name (fuzzy search)
425
+ pxo open partial-name
426
+ ```
427
+
301
428
  ---
302
429
 
303
430
  ## 🤝 Contributing
@@ -365,6 +492,13 @@ pxo
365
492
  pxo list
366
493
  pxo ls
367
494
 
495
+ # Recent projects
496
+ pxo recent
497
+ pxo recent 5
498
+
499
+ # Quick open
500
+ pxo open <project-name>
501
+
368
502
  # Version
369
503
  pxo --version
370
504
  pxo -V
package/index.js CHANGED
@@ -6,12 +6,14 @@
6
6
  * Entry point for the CLI application
7
7
  * Handles command routing and argument parsing
8
8
  *
9
- * @version 1.1.0
9
+ * @version 1.2.0
10
10
  */
11
11
 
12
12
  const { program } = require('commander');
13
13
  const { run: createProject } = require('./src/cli');
14
14
  const { listCommand } = require('./src/commands/list');
15
+ const { recentCommand } = require('./src/commands/recent');
16
+ const { openCommand } = require('./src/commands/open');
15
17
  const logger = require('./src/utils/logger');
16
18
 
17
19
  // Package info
@@ -38,6 +40,23 @@ program
38
40
  listCommand();
39
41
  });
40
42
 
43
+ // Show recent projects
44
+ program
45
+ .command('recent')
46
+ .description('Show recently accessed projects')
47
+ .argument('[limit]', 'number of projects to show', '10')
48
+ .action((limit) => {
49
+ recentCommand(parseInt(limit));
50
+ });
51
+
52
+ // Open project
53
+ program
54
+ .command('open [project-name]')
55
+ .description('Open a project in IDE')
56
+ .action((projectName) => {
57
+ openCommand(projectName);
58
+ });
59
+
41
60
  // Handle errors
42
61
  program.exitOverride();
43
62
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projxo",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Cross-platform CLI tool to quickly create React, Next.js, Angular, and React Native projects with automatic IDE integration",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -67,11 +67,12 @@ async function listCommand() {
67
67
 
68
68
  // Create choices for inquirer
69
69
  const choices = projects.map(project => {
70
- const typeDisplay = getTypeDisplay(project.type);
71
- const timeAgo = formatRelativeTime(project.lastAccessed);
70
+ const typeDisplay = getTypeDisplay(project.type).padEnd(16);
71
+ const timeAgo = formatRelativeTime(project.lastAccessed).padEnd(15);
72
+ const nameDisplay = project.name.padEnd(30);
72
73
 
73
74
  return {
74
- name: `${project.name}${typeDisplay}${timeAgo}`,
75
+ name: `${nameDisplay} ${typeDisplay} ${timeAgo}`,
75
76
  value: project.id,
76
77
  short: project.name
77
78
  };
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Open command - Quick open project by name
3
+ * Usage: pxo open <project-name>
4
+ */
5
+
6
+ const inquirer = require('inquirer');
7
+ const { getProjectByName, touchProject, searchProjects } = require('../storage/projects');
8
+ const { openInIDE } = require('../handlers/ideOpener');
9
+ const { getIDEChoices } = require('../config/ides');
10
+ const logger = require('../utils/logger');
11
+
12
+ /**
13
+ * Execute open command
14
+ * @param {string} projectName - Project name to open
15
+ */
16
+ async function openCommand(projectName) {
17
+ try {
18
+ if (!projectName) {
19
+ logger.error('Please provide a project name');
20
+ logger.log('\nUsage:', 'dim');
21
+ logger.log(' pxo open <project-name>', 'cyan');
22
+ return;
23
+ }
24
+
25
+ // Try to find project
26
+ let project = getProjectByName(projectName);
27
+
28
+ // If not found, try fuzzy search
29
+ if (!project) {
30
+ const matches = searchProjects(projectName);
31
+
32
+ if (matches.length === 0) {
33
+ logger.error(`Project "${projectName}" not found`);
34
+ logger.log('\nList all projects with:', 'dim');
35
+ logger.log(' pxo list', 'cyan');
36
+ return;
37
+ }
38
+
39
+ if (matches.length === 1) {
40
+ project = matches[0];
41
+ logger.info(`Found similar project: ${project.name}`);
42
+ } else {
43
+ // Multiple matches, let user choose
44
+ project = await selectFromMatches(matches);
45
+ if (!project) return;
46
+ }
47
+ }
48
+
49
+ // Update last accessed time
50
+ touchProject(project.id);
51
+
52
+ // Determine which IDE to use
53
+ let ideKey = project.ide;
54
+
55
+ if (!ideKey || ideKey === 'skip') {
56
+ const { selectedIDE } = await inquirer.prompt([
57
+ {
58
+ type: 'list',
59
+ name: 'selectedIDE',
60
+ message: `Open "${project.name}" in:`,
61
+ choices: getIDEChoices()
62
+ }
63
+ ]);
64
+ ideKey = selectedIDE;
65
+ }
66
+
67
+ // Open in IDE
68
+ if (ideKey !== 'skip') {
69
+ const success = await openInIDE(project.path, ideKey);
70
+ if (success) {
71
+ logger.success(`Opened ${project.name}`);
72
+ }
73
+ } else {
74
+ logger.info(`Project path: ${project.path}`);
75
+ }
76
+
77
+ } catch (error) {
78
+ logger.error(`Failed to open project: ${error.message}`);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Let user select from multiple matches
84
+ * @param {Array} matches - Array of matching projects
85
+ * @returns {Promise<Object|null>} Selected project or null
86
+ */
87
+ async function selectFromMatches(matches) {
88
+ logger.info(`Found ${matches.length} matching projects:`);
89
+ logger.newLine();
90
+
91
+ const choices = matches.map(p => ({
92
+ name: `${p.name} (${p.type})`,
93
+ value: p.id,
94
+ short: p.name
95
+ }));
96
+
97
+ choices.push(
98
+ new inquirer.Separator(),
99
+ { name: '← Cancel', value: 'cancel' }
100
+ );
101
+
102
+ const { selectedId } = await inquirer.prompt([
103
+ {
104
+ type: 'list',
105
+ name: 'selectedId',
106
+ message: 'Which project did you mean?',
107
+ choices
108
+ }
109
+ ]);
110
+
111
+ if (selectedId === 'cancel') {
112
+ return null;
113
+ }
114
+
115
+ const { getProjectById } = require('../storage/projects');
116
+ return getProjectById(selectedId);
117
+ }
118
+
119
+ module.exports = { openCommand };
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Recent command - Show recently accessed projects
3
+ * Usage: pxo recent [limit]
4
+ */
5
+
6
+ const inquirer = require('inquirer');
7
+ const { getRecentProjects, touchProject } = require('../storage/projects');
8
+ const { openInIDE } = require('../handlers/ideOpener');
9
+ const logger = require('../utils/logger');
10
+
11
+ /**
12
+ * Format relative time (same as list.js)
13
+ * @param {string} isoDate - ISO date string
14
+ * @returns {string} Human-readable relative time
15
+ */
16
+ function formatRelativeTime(isoDate) {
17
+ const date = new Date(isoDate);
18
+ const now = new Date();
19
+ const diffMs = now - date;
20
+ const diffMins = Math.floor(diffMs / 60000);
21
+ const diffHours = Math.floor(diffMs / 3600000);
22
+ const diffDays = Math.floor(diffMs / 86400000);
23
+
24
+ if (diffMins < 1) return 'just now';
25
+ if (diffMins < 60) return `${diffMins} min${diffMins > 1 ? 's' : ''} ago`;
26
+ if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
27
+ if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
28
+ if (diffDays < 30) return `${Math.floor(diffDays / 7)} week${Math.floor(diffDays / 7) > 1 ? 's' : ''} ago`;
29
+ return `${Math.floor(diffDays / 30)} month${Math.floor(diffDays / 30) > 1 ? 's' : ''} ago`;
30
+ }
31
+
32
+ /**
33
+ * Get project type display name
34
+ * @param {string} type - Project type key
35
+ * @returns {string} Formatted display name
36
+ */
37
+ function getTypeDisplay(type) {
38
+ const typeMap = {
39
+ 'react-vite': 'React+Vite',
40
+ 'react-vite-ts': 'React+Vite(TS)',
41
+ 'nextjs': 'Next.js',
42
+ 'angular': 'Angular',
43
+ 'react-native': 'React Native'
44
+ };
45
+ return typeMap[type] || type;
46
+ }
47
+
48
+ /**
49
+ * Execute recent command
50
+ * @param {number} limit - Number of recent projects to show
51
+ */
52
+ async function recentCommand(limit = 10) {
53
+ try {
54
+ const projects = getRecentProjects(limit);
55
+
56
+ if (projects.length === 0) {
57
+ logger.info('No recent projects found');
58
+ logger.log('\nCreate your first project with:', 'dim');
59
+ logger.log(' pxo', 'cyan');
60
+ return;
61
+ }
62
+
63
+ logger.newLine();
64
+ logger.log(`🕐 Recent Projects (${projects.length})`, 'bright');
65
+ logger.newLine();
66
+
67
+ // Create choices for inquirer
68
+ const choices = projects.map((project, index) => {
69
+ const typeDisplay = getTypeDisplay(project.type).padEnd(16);
70
+ const timeAgo = formatRelativeTime(project.lastAccessed).padEnd(15);
71
+ const indexDisplay = `${index + 1}.`.padEnd(4);
72
+ const nameDisplay = project.name.padEnd(30);
73
+
74
+ return {
75
+ name: `${indexDisplay}${nameDisplay} ${typeDisplay} ${timeAgo}`,
76
+ value: project.id,
77
+ short: project.name
78
+ };
79
+ });
80
+
81
+ // Add separator and back option
82
+ choices.push(
83
+ new inquirer.Separator(),
84
+ { name: '← Cancel', value: 'cancel' }
85
+ );
86
+
87
+ const { selectedId } = await inquirer.prompt([
88
+ {
89
+ type: 'list',
90
+ name: 'selectedId',
91
+ message: 'Select a project to open:',
92
+ choices,
93
+ pageSize: 15
94
+ }
95
+ ]);
96
+
97
+ if (selectedId === 'cancel') {
98
+ return;
99
+ }
100
+
101
+ // Open selected project
102
+ await openSelectedProject(selectedId);
103
+
104
+ } catch (error) {
105
+ if (error.isTtyError) {
106
+ logger.error('This command requires an interactive terminal');
107
+ } else {
108
+ logger.error(`Failed to show recent projects: ${error.message}`);
109
+ }
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Open selected project in IDE
115
+ * @param {string} projectId - Project ID
116
+ */
117
+ async function openSelectedProject(projectId) {
118
+ const { getProjectById } = require('../storage/projects');
119
+ const { getIDEChoices } = require('../config/ides');
120
+
121
+ const project = getProjectById(projectId);
122
+
123
+ if (!project) {
124
+ logger.error('Project not found');
125
+ return;
126
+ }
127
+
128
+ // Update last accessed time
129
+ touchProject(project.id);
130
+
131
+ // Use project's preferred IDE or prompt
132
+ let ideKey = project.ide;
133
+
134
+ if (!ideKey || ideKey === 'skip') {
135
+ const { selectedIDE } = await inquirer.prompt([
136
+ {
137
+ type: 'list',
138
+ name: 'selectedIDE',
139
+ message: 'Select IDE:',
140
+ choices: getIDEChoices()
141
+ }
142
+ ]);
143
+ ideKey = selectedIDE;
144
+ }
145
+
146
+ if (ideKey !== 'skip') {
147
+ const success = await openInIDE(project.path, ideKey);
148
+ if (success) {
149
+ logger.success(`Opened ${project.name}`);
150
+ }
151
+ } else {
152
+ logger.info(`Project path: ${project.path}`);
153
+ }
154
+ }
155
+
156
+ module.exports = { recentCommand };