projax 3.3.59 → 3.3.64

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/dist/prxi.tsx CHANGED
@@ -403,155 +403,6 @@ const AddProjectModal: React.FC<AddProjectModalProps> = ({ onAdd, onCancel }) =>
403
403
  );
404
404
  };
405
405
 
406
- // Settings Modal
407
- interface SettingsModalProps {
408
- onClose: () => void;
409
- }
410
-
411
- const SettingsModal: React.FC<SettingsModalProps> = ({ onClose }) => {
412
- const [settings, setSettings] = useState<AppSettings>({
413
- editor: { type: 'vscode' },
414
- browser: { type: 'chrome' },
415
- });
416
- const [selectedSection, setSelectedSection] = useState<'editor' | 'browser'>('editor');
417
- const [selectedOptionIndex, setSelectedOptionIndex] = useState(0);
418
-
419
- const editorOptions: Array<'vscode' | 'cursor' | 'windsurf' | 'zed' | 'custom'> = [
420
- 'vscode',
421
- 'cursor',
422
- 'windsurf',
423
- 'zed',
424
- 'custom',
425
- ];
426
- const browserOptions: Array<'chrome' | 'firefox' | 'safari' | 'edge' | 'custom'> = [
427
- 'chrome',
428
- 'firefox',
429
- 'safari',
430
- 'edge',
431
- 'custom',
432
- ];
433
-
434
- useEffect(() => {
435
- // Load settings
436
- try {
437
- const settingsPath = path.join(os.homedir(), '.projax', 'settings.json');
438
- if (fs.existsSync(settingsPath)) {
439
- const data = fs.readFileSync(settingsPath, 'utf-8');
440
- setSettings(JSON.parse(data));
441
- }
442
- } catch {
443
- // Use defaults
444
- }
445
- }, []);
446
-
447
- const saveSettings = () => {
448
- try {
449
- const settingsDir = path.join(os.homedir(), '.projax');
450
- if (!fs.existsSync(settingsDir)) {
451
- fs.mkdirSync(settingsDir, { recursive: true });
452
- }
453
- const settingsPath = path.join(settingsDir, 'settings.json');
454
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
455
- onClose();
456
- } catch {
457
- // Ignore save errors
458
- }
459
- };
460
-
461
- useInput((input: string, key: any) => {
462
- if (key.escape || input === 'q') {
463
- onClose();
464
- return;
465
- }
466
-
467
- if (key.return) {
468
- saveSettings();
469
- return;
470
- }
471
-
472
- if (key.tab) {
473
- setSelectedSection((prev) => (prev === 'editor' ? 'browser' : 'editor'));
474
- setSelectedOptionIndex(0);
475
- return;
476
- }
477
-
478
- if (key.upArrow || input === 'k') {
479
- setSelectedOptionIndex((prev) => Math.max(0, prev - 1));
480
- return;
481
- }
482
-
483
- if (key.downArrow || input === 'j') {
484
- const maxIndex = selectedSection === 'editor' ? editorOptions.length - 1 : browserOptions.length - 1;
485
- setSelectedOptionIndex((prev) => Math.min(maxIndex, prev + 1));
486
- return;
487
- }
488
-
489
- if (input === ' ' || key.return) {
490
- if (selectedSection === 'editor') {
491
- setSettings({
492
- ...settings,
493
- editor: { type: editorOptions[selectedOptionIndex] },
494
- });
495
- } else {
496
- setSettings({
497
- ...settings,
498
- browser: { type: browserOptions[selectedOptionIndex] },
499
- });
500
- }
501
- }
502
- });
503
-
504
- const currentOptions = selectedSection === 'editor' ? editorOptions : browserOptions;
505
- const currentValue = selectedSection === 'editor' ? settings.editor.type : settings.browser.type;
506
-
507
- return (
508
- <Box
509
- flexDirection="column"
510
- borderStyle="round"
511
- borderColor={colors.accentCyan}
512
- padding={1}
513
- width={60}
514
- >
515
- <Text bold color={colors.accentCyan}>Settings</Text>
516
- <Text> </Text>
517
-
518
- {/* Section tabs */}
519
- <Box>
520
- <Text
521
- color={selectedSection === 'editor' ? colors.accentCyan : colors.textTertiary}
522
- bold={selectedSection === 'editor'}
523
- >
524
- [Editor]
525
- </Text>
526
- <Text> </Text>
527
- <Text
528
- color={selectedSection === 'browser' ? colors.accentCyan : colors.textTertiary}
529
- bold={selectedSection === 'browser'}
530
- >
531
- [Browser]
532
- </Text>
533
- </Box>
534
- <Text> </Text>
535
-
536
- {/* Options */}
537
- {currentOptions.map((option, index) => {
538
- const isSelected = index === selectedOptionIndex;
539
- const isActive = option === currentValue;
540
- return (
541
- <Text key={option} color={isSelected ? colors.accentCyan : colors.textPrimary} bold={isSelected}>
542
- {isSelected ? '▶ ' : ' '}
543
- {isActive ? '● ' : '○ '}
544
- {option.charAt(0).toUpperCase() + option.slice(1)}
545
- </Text>
546
- );
547
- })}
548
-
549
- <Text> </Text>
550
- <Text color={colors.textSecondary}>Tab: switch section | ↑↓: select | Space: choose | Enter: save | Esc: close</Text>
551
- </Box>
552
- );
553
- };
554
-
555
406
  interface ErrorModalProps {
556
407
  message: string;
557
408
  onClose: () => void;
@@ -1337,7 +1188,18 @@ const App: React.FC = () => {
1337
1188
  const [selectedProcessPid, setSelectedProcessPid] = useState<number | null>(null);
1338
1189
 
1339
1190
  // Settings state
1340
- const [showSettings, setShowSettings] = useState(false);
1191
+ const [settings, setSettings] = useState<AppSettings>({
1192
+ editor: { type: 'vscode' },
1193
+ browser: { type: 'chrome' },
1194
+ });
1195
+ const [settingsSection, setSettingsSection] = useState<'editor' | 'browser'>('editor');
1196
+ const [settingsOptionIndex, setSettingsOptionIndex] = useState(0);
1197
+ const settingsEditorOptions: Array<'vscode' | 'cursor' | 'windsurf' | 'zed' | 'custom'> = [
1198
+ 'vscode', 'cursor', 'windsurf', 'zed', 'custom',
1199
+ ];
1200
+ const settingsBrowserOptions: Array<'chrome' | 'firefox' | 'safari' | 'edge' | 'custom'> = [
1201
+ 'chrome', 'firefox', 'safari', 'edge', 'custom',
1202
+ ];
1341
1203
 
1342
1204
  // Get terminal dimensions
1343
1205
  const terminalHeight = process.stdout.rows || 24;
@@ -1348,6 +1210,17 @@ const App: React.FC = () => {
1348
1210
  loadRunningProcesses();
1349
1211
  loadAllTags();
1350
1212
 
1213
+ // Load settings
1214
+ try {
1215
+ const settingsPath = path.join(os.homedir(), '.projax', 'settings.json');
1216
+ if (fs.existsSync(settingsPath)) {
1217
+ const data = fs.readFileSync(settingsPath, 'utf-8');
1218
+ setSettings(JSON.parse(data));
1219
+ }
1220
+ } catch {
1221
+ // Use defaults
1222
+ }
1223
+
1351
1224
  // Refresh running processes and git branches every 5 seconds
1352
1225
  const interval = setInterval(() => {
1353
1226
  loadRunningProcesses();
@@ -1732,6 +1605,73 @@ const App: React.FC = () => {
1732
1605
  };
1733
1606
 
1734
1607
  useInput((input: string, key: any) => {
1608
+ if (key.mouse) {
1609
+ const { x, y, wheelDown, wheelUp, left } = key.mouse;
1610
+ const { columns: width } = process.stdout;
1611
+
1612
+ // Assuming project list is on the left 35% and details on the right.
1613
+ const projectListWidth = Math.floor(width * 0.35);
1614
+
1615
+ if (x < projectListWidth) {
1616
+ // Mouse is over the project list
1617
+ if (wheelUp) {
1618
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
1619
+ return;
1620
+ }
1621
+ if (wheelDown) {
1622
+ setSelectedIndex((prev) => Math.min(projects.length - 1, prev + 1));
1623
+ return;
1624
+ }
1625
+ if (left) {
1626
+ // It's a click, so we need to calculate which item was clicked.
1627
+ // This is an approximation based on the known layout of the ProjectListComponent.
1628
+ const listTopBorder = 1;
1629
+ const listPadding = 1;
1630
+ const listHeaderHeight = 2; // "Projects (...)" + "Filter | Sort"
1631
+ const listStartY = listTopBorder + listPadding + listHeaderHeight;
1632
+
1633
+ const scrollIndicatorHeight = listScrollOffset > 0 ? 1 : 0;
1634
+ const firstItemY = listStartY + scrollIndicatorHeight;
1635
+
1636
+ const clickYInList = y - firstItemY;
1637
+
1638
+ if (clickYInList >= 0) {
1639
+ let cumulativeHeight = 0;
1640
+ const visibleProjects = projects.slice(listScrollOffset);
1641
+
1642
+ for (let i = 0; i < visibleProjects.length; i++) {
1643
+ const project = visibleProjects[i];
1644
+ const branch = gitBranches.get(project.id);
1645
+ const itemHeight = branch ? 2 : 1;
1646
+
1647
+ if (clickYInList >= cumulativeHeight && clickYInList < cumulativeHeight + itemHeight) {
1648
+ const newIndex = listScrollOffset + i;
1649
+ if (newIndex < projects.length) {
1650
+ setSelectedIndex(newIndex);
1651
+ setFocusedPanel('list');
1652
+ }
1653
+ break;
1654
+ }
1655
+ cumulativeHeight += itemHeight;
1656
+ if (cumulativeHeight > availableHeight) {
1657
+ break;
1658
+ }
1659
+ }
1660
+ }
1661
+ return;
1662
+ }
1663
+ } else {
1664
+ // Mouse is over the details panel
1665
+ if (wheelUp) {
1666
+ setDetailsScrollOffset(prev => Math.max(0, prev - 1));
1667
+ return;
1668
+ }
1669
+ if (wheelDown) {
1670
+ setDetailsScrollOffset(prev => prev + 1);
1671
+ return;
1672
+ }
1673
+ }
1674
+ }
1735
1675
  // Handle search mode
1736
1676
  if (showSearch) {
1737
1677
  if (key.escape) {
@@ -1764,7 +1704,7 @@ const App: React.FC = () => {
1764
1704
  }
1765
1705
 
1766
1706
  // Don't process input if modal is showing
1767
- if (showHelp || isLoading || error || showUrls || showScriptModal || showAddProjectModal || showConfirmDelete || showSettings) {
1707
+ if (showHelp || isLoading || error || showUrls || showScriptModal || showAddProjectModal || showConfirmDelete) {
1768
1708
  // Handle URLs modal
1769
1709
  if (showUrls && (key.escape || key.return || input === 'q' || input === 'u')) {
1770
1710
  setShowUrls(false);
@@ -1807,6 +1747,46 @@ const App: React.FC = () => {
1807
1747
  }
1808
1748
  }
1809
1749
 
1750
+ // Handle navigation in settings view
1751
+ if (currentView === 'settings') {
1752
+ if (key.tab) {
1753
+ setSettingsSection((prev) => (prev === 'editor' ? 'browser' : 'editor'));
1754
+ setSettingsOptionIndex(0);
1755
+ return;
1756
+ }
1757
+ if (key.upArrow || input === 'k') {
1758
+ setSettingsOptionIndex((prev) => Math.max(0, prev - 1));
1759
+ return;
1760
+ }
1761
+ if (key.downArrow || input === 'j') {
1762
+ const maxIndex = settingsSection === 'editor' ? settingsEditorOptions.length - 1 : settingsBrowserOptions.length - 1;
1763
+ setSettingsOptionIndex((prev) => Math.min(maxIndex, prev + 1));
1764
+ return;
1765
+ }
1766
+ if (input === ' ' || key.return) {
1767
+ // Select option and save
1768
+ const newSettings = { ...settings };
1769
+ if (settingsSection === 'editor') {
1770
+ newSettings.editor = { type: settingsEditorOptions[settingsOptionIndex] };
1771
+ } else {
1772
+ newSettings.browser = { type: settingsBrowserOptions[settingsOptionIndex] };
1773
+ }
1774
+ setSettings(newSettings);
1775
+ // Save to file
1776
+ try {
1777
+ const settingsDir = path.join(os.homedir(), '.projax');
1778
+ if (!fs.existsSync(settingsDir)) {
1779
+ fs.mkdirSync(settingsDir, { recursive: true });
1780
+ }
1781
+ const settingsPath = path.join(settingsDir, 'settings.json');
1782
+ fs.writeFileSync(settingsPath, JSON.stringify(newSettings, null, 2));
1783
+ } catch {
1784
+ // Ignore save errors
1785
+ }
1786
+ return;
1787
+ }
1788
+ }
1789
+
1810
1790
  // Global navigation - number keys for view switching
1811
1791
  if (input === '1') {
1812
1792
  setCurrentView('projects');
@@ -1822,7 +1802,6 @@ const App: React.FC = () => {
1822
1802
  }
1823
1803
  if (input === '4') {
1824
1804
  setCurrentView('settings');
1825
- setShowSettings(true);
1826
1805
  return;
1827
1806
  }
1828
1807
 
@@ -2143,16 +2122,6 @@ const App: React.FC = () => {
2143
2122
  );
2144
2123
  }
2145
2124
 
2146
- if (showSettings) {
2147
- return (
2148
- <Box flexDirection="column" padding={1}>
2149
- <SettingsModal onClose={() => {
2150
- setShowSettings(false);
2151
- setCurrentView('projects');
2152
- }} />
2153
- </Box>
2154
- );
2155
- }
2156
2125
 
2157
2126
  if (isLoading) {
2158
2127
  return (
@@ -2452,6 +2421,53 @@ const App: React.FC = () => {
2452
2421
  </Box>
2453
2422
  );
2454
2423
 
2424
+ // Render Settings view
2425
+ const renderSettingsView = () => {
2426
+ const currentOptions = settingsSection === 'editor' ? settingsEditorOptions : settingsBrowserOptions;
2427
+ const currentValue = settingsSection === 'editor' ? settings.editor.type : settings.browser.type;
2428
+
2429
+ return (
2430
+ <Box flexDirection="column" padding={2}>
2431
+ <Text bold color={colors.accentCyan}>Settings</Text>
2432
+ <Text> </Text>
2433
+
2434
+ {/* Section tabs */}
2435
+ <Box>
2436
+ <Text
2437
+ color={settingsSection === 'editor' ? colors.accentCyan : colors.textTertiary}
2438
+ bold={settingsSection === 'editor'}
2439
+ >
2440
+ [Editor]
2441
+ </Text>
2442
+ <Text> </Text>
2443
+ <Text
2444
+ color={settingsSection === 'browser' ? colors.accentCyan : colors.textTertiary}
2445
+ bold={settingsSection === 'browser'}
2446
+ >
2447
+ [Browser]
2448
+ </Text>
2449
+ </Box>
2450
+ <Text> </Text>
2451
+
2452
+ {/* Options */}
2453
+ {currentOptions.map((option, index) => {
2454
+ const isSelected = index === settingsOptionIndex;
2455
+ const isActive = option === currentValue;
2456
+ return (
2457
+ <Text key={option} color={isSelected ? colors.accentCyan : colors.textPrimary} bold={isSelected}>
2458
+ {isSelected ? '▶ ' : ' '}
2459
+ {isActive ? '● ' : '○ '}
2460
+ {option.charAt(0).toUpperCase() + option.slice(1)}
2461
+ </Text>
2462
+ );
2463
+ })}
2464
+
2465
+ <Text> </Text>
2466
+ <Text color={colors.textTertiary}>Tab: switch section | ↑↓/jk: select | Space/Enter: choose</Text>
2467
+ </Box>
2468
+ );
2469
+ };
2470
+
2455
2471
  return (
2456
2472
  <Box flexDirection="column" height={terminalHeight}>
2457
2473
  {/* View indicator bar */}
@@ -2483,6 +2499,7 @@ const App: React.FC = () => {
2483
2499
  {currentView === 'projects' && renderProjectsView()}
2484
2500
  {currentView === 'workspaces' && renderWorkspacesView()}
2485
2501
  {currentView === 'processes' && renderProcessesView()}
2502
+ {currentView === 'settings' && renderSettingsView()}
2486
2503
 
2487
2504
  {/* Status bar */}
2488
2505
  <Box paddingX={1} borderStyle="single" borderColor={colors.borderColor} flexShrink={0} height={3}>
package/package.json CHANGED
@@ -1,28 +1,11 @@
1
1
  {
2
2
  "name": "projax",
3
- "version": "3.3.59",
3
+ "version": "3.3.64",
4
4
  "description": "Cross-platform project management dashboard for tracking local development projects. Features CLI, Terminal UI, Desktop app, REST API, and built-in tools for test detection, port management, and script execution.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "prx": "./dist/index.js"
8
8
  },
9
- "scripts": {
10
- "build": "tsc",
11
- "typecheck": "echo \"Skipping cli typecheck (legacy)\"",
12
- "lint": "echo \"Skipping cli lint (not configured)\"",
13
- "lint:fix": "echo \"Skipping cli lint (not configured)\"",
14
- "build:electron": "cd ../desktop && pnpm run build",
15
- "copy:electron": "mkdir -p dist/electron && cp -r ../desktop/dist/* dist/electron/ && cp dist/script-runner.* dist/electron/ && cp dist/port-*.* dist/electron/ && cp -R dist/core dist/electron/",
16
- "copy:api": "mkdir -p dist/api && cp -r ../api/dist/* dist/api/ && cp ../api/package.json dist/api/",
17
- "copy:core": "mkdir -p dist/core && cp -r ../core/dist/* dist/core/",
18
- "copy:prxi": "cp src/prxi.tsx dist/prxi.tsx",
19
- "build:all": "pnpm run build && pnpm run copy:core && pnpm run build:electron && pnpm run copy:electron && pnpm run copy:api && pnpm run copy:prxi",
20
- "clean": "rm -rf dist",
21
- "test": "jest",
22
- "test:watch": "jest --watch",
23
- "test:coverage": "jest --coverage",
24
- "prepublishOnly": "pnpm run build:all"
25
- },
26
9
  "dependencies": {
27
10
  "chokidar": "^3.6.0",
28
11
  "commander": "^11.1.0",
@@ -73,5 +56,21 @@
73
56
  },
74
57
  "homepage": "https://projax.dev",
75
58
  "author": "",
76
- "license": "MIT"
77
- }
59
+ "license": "MIT",
60
+ "scripts": {
61
+ "build": "tsc",
62
+ "typecheck": "echo \"Skipping cli typecheck (legacy)\"",
63
+ "lint": "echo \"Skipping cli lint (not configured)\"",
64
+ "lint:fix": "echo \"Skipping cli lint (not configured)\"",
65
+ "build:electron": "cd ../desktop && pnpm run build",
66
+ "copy:electron": "mkdir -p dist/electron && cp -r ../desktop/dist/* dist/electron/ && cp dist/script-runner.* dist/electron/ && cp dist/port-*.* dist/electron/ && cp -R dist/core dist/electron/",
67
+ "copy:api": "mkdir -p dist/api && cp -r ../api/dist/* dist/api/ && cp ../api/package.json dist/api/",
68
+ "copy:core": "mkdir -p dist/core && cp -r ../core/dist/* dist/core/",
69
+ "copy:prxi": "cp src/prxi.tsx dist/prxi.tsx",
70
+ "build:all": "pnpm run build && pnpm run copy:core && pnpm run build:electron && pnpm run copy:electron && pnpm run copy:api && pnpm run copy:prxi",
71
+ "clean": "rm -rf dist",
72
+ "test": "jest",
73
+ "test:watch": "jest --watch",
74
+ "test:coverage": "jest --coverage"
75
+ }
76
+ }