projax 3.3.59 → 3.3.63

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.
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' http://localhost:* ws://localhost:*;" />
7
7
  <title>projax</title>
8
- <script type="module" crossorigin src="./assets/index-Cj4QON0s.js"></script>
8
+ <script type="module" crossorigin src="./assets/index-CmtZriN5.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="./assets/index-DfocdjIj.css">
10
10
  </head>
11
11
  <body>
package/dist/index.js CHANGED
@@ -433,15 +433,11 @@ function displayLogo() {
433
433
  const program = new commander_1.Command();
434
434
  program
435
435
  .name('prx')
436
- .description('Project management dashboard CLI')
436
+ .description('Project management dashboard - launches interactive TUI by default. Use --help for CLI commands.')
437
437
  .version(packageJson.version)
438
438
  .addHelpText('beforeAll', displayLogo());
439
- // Interactive terminal UI command (hidden - in beta)
440
- program
441
- .command('prxi', { hidden: true }) // Hide from help output while in beta
442
- .alias('i')
443
- .description('Launch interactive terminal UI (beta)')
444
- .action(async () => {
439
+ // Launch the interactive TUI
440
+ async function launchTUI() {
445
441
  try {
446
442
  await ensureAPIServerRunning(true);
447
443
  // Find the prxi source file - use CLI's embedded prxi.tsx
@@ -503,10 +499,16 @@ program
503
499
  }
504
500
  }
505
501
  catch (error) {
506
- console.error('Error launching prxi:', error instanceof Error ? error.message : error);
502
+ console.error('Error launching TUI:', error instanceof Error ? error.message : error);
507
503
  process.exit(1);
508
504
  }
509
- });
505
+ }
506
+ // Interactive terminal UI command (kept for backwards compatibility)
507
+ program
508
+ .command('prxi', { hidden: true })
509
+ .alias('i')
510
+ .description('Launch interactive terminal UI')
511
+ .action(launchTUI);
510
512
  // Add project command
511
513
  program
512
514
  .command('add')
@@ -2275,6 +2277,8 @@ program
2275
2277
  }
2276
2278
  }
2277
2279
  }
2280
+ // Default action: launch TUI when no command is provided
2281
+ program.action(launchTUI);
2278
2282
  // If we get here, proceed with normal command parsing
2279
2283
  // Don't show logo twice - it's already in addHelpText
2280
2284
  program.parse();
package/dist/prxi.js CHANGED
@@ -305,113 +305,6 @@ const AddProjectModal = ({ onAdd, onCancel }) => {
305
305
  react_1.default.createElement(ink_1.Text, null, " "),
306
306
  react_1.default.createElement(ink_1.Text, { color: colors.textSecondary }, "Enter: add project | Esc: cancel")))));
307
307
  };
308
- const SettingsModal = ({ onClose }) => {
309
- const [settings, setSettings] = (0, react_1.useState)({
310
- editor: { type: 'vscode' },
311
- browser: { type: 'chrome' },
312
- });
313
- const [selectedSection, setSelectedSection] = (0, react_1.useState)('editor');
314
- const [selectedOptionIndex, setSelectedOptionIndex] = (0, react_1.useState)(0);
315
- const editorOptions = [
316
- 'vscode',
317
- 'cursor',
318
- 'windsurf',
319
- 'zed',
320
- 'custom',
321
- ];
322
- const browserOptions = [
323
- 'chrome',
324
- 'firefox',
325
- 'safari',
326
- 'edge',
327
- 'custom',
328
- ];
329
- (0, react_1.useEffect)(() => {
330
- // Load settings
331
- try {
332
- const settingsPath = path.join(os.homedir(), '.projax', 'settings.json');
333
- if (fs.existsSync(settingsPath)) {
334
- const data = fs.readFileSync(settingsPath, 'utf-8');
335
- setSettings(JSON.parse(data));
336
- }
337
- }
338
- catch {
339
- // Use defaults
340
- }
341
- }, []);
342
- const saveSettings = () => {
343
- try {
344
- const settingsDir = path.join(os.homedir(), '.projax');
345
- if (!fs.existsSync(settingsDir)) {
346
- fs.mkdirSync(settingsDir, { recursive: true });
347
- }
348
- const settingsPath = path.join(settingsDir, 'settings.json');
349
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
350
- onClose();
351
- }
352
- catch {
353
- // Ignore save errors
354
- }
355
- };
356
- (0, ink_1.useInput)((input, key) => {
357
- if (key.escape || input === 'q') {
358
- onClose();
359
- return;
360
- }
361
- if (key.return) {
362
- saveSettings();
363
- return;
364
- }
365
- if (key.tab) {
366
- setSelectedSection((prev) => (prev === 'editor' ? 'browser' : 'editor'));
367
- setSelectedOptionIndex(0);
368
- return;
369
- }
370
- if (key.upArrow || input === 'k') {
371
- setSelectedOptionIndex((prev) => Math.max(0, prev - 1));
372
- return;
373
- }
374
- if (key.downArrow || input === 'j') {
375
- const maxIndex = selectedSection === 'editor' ? editorOptions.length - 1 : browserOptions.length - 1;
376
- setSelectedOptionIndex((prev) => Math.min(maxIndex, prev + 1));
377
- return;
378
- }
379
- if (input === ' ' || key.return) {
380
- if (selectedSection === 'editor') {
381
- setSettings({
382
- ...settings,
383
- editor: { type: editorOptions[selectedOptionIndex] },
384
- });
385
- }
386
- else {
387
- setSettings({
388
- ...settings,
389
- browser: { type: browserOptions[selectedOptionIndex] },
390
- });
391
- }
392
- }
393
- });
394
- const currentOptions = selectedSection === 'editor' ? editorOptions : browserOptions;
395
- const currentValue = selectedSection === 'editor' ? settings.editor.type : settings.browser.type;
396
- return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", borderStyle: "round", borderColor: colors.accentCyan, padding: 1, width: 60 },
397
- react_1.default.createElement(ink_1.Text, { bold: true, color: colors.accentCyan }, "Settings"),
398
- react_1.default.createElement(ink_1.Text, null, " "),
399
- react_1.default.createElement(ink_1.Box, null,
400
- react_1.default.createElement(ink_1.Text, { color: selectedSection === 'editor' ? colors.accentCyan : colors.textTertiary, bold: selectedSection === 'editor' }, "[Editor]"),
401
- react_1.default.createElement(ink_1.Text, null, " "),
402
- react_1.default.createElement(ink_1.Text, { color: selectedSection === 'browser' ? colors.accentCyan : colors.textTertiary, bold: selectedSection === 'browser' }, "[Browser]")),
403
- react_1.default.createElement(ink_1.Text, null, " "),
404
- currentOptions.map((option, index) => {
405
- const isSelected = index === selectedOptionIndex;
406
- const isActive = option === currentValue;
407
- return (react_1.default.createElement(ink_1.Text, { key: option, color: isSelected ? colors.accentCyan : colors.textPrimary, bold: isSelected },
408
- isSelected ? '▶ ' : ' ',
409
- isActive ? '● ' : '○ ',
410
- option.charAt(0).toUpperCase() + option.slice(1)));
411
- }),
412
- react_1.default.createElement(ink_1.Text, null, " "),
413
- react_1.default.createElement(ink_1.Text, { color: colors.textSecondary }, "Tab: switch section | \u2191\u2193: select | Space: choose | Enter: save | Esc: close")));
414
- };
415
308
  const ErrorModal = ({ message, onClose }) => {
416
309
  (0, ink_1.useInput)((input, key) => {
417
310
  if (key.escape || key.return) {
@@ -921,7 +814,18 @@ const App = () => {
921
814
  const [terminalLogs, setTerminalLogs] = (0, react_1.useState)([]);
922
815
  const [selectedProcessPid, setSelectedProcessPid] = (0, react_1.useState)(null);
923
816
  // Settings state
924
- const [showSettings, setShowSettings] = (0, react_1.useState)(false);
817
+ const [settings, setSettings] = (0, react_1.useState)({
818
+ editor: { type: 'vscode' },
819
+ browser: { type: 'chrome' },
820
+ });
821
+ const [settingsSection, setSettingsSection] = (0, react_1.useState)('editor');
822
+ const [settingsOptionIndex, setSettingsOptionIndex] = (0, react_1.useState)(0);
823
+ const settingsEditorOptions = [
824
+ 'vscode', 'cursor', 'windsurf', 'zed', 'custom',
825
+ ];
826
+ const settingsBrowserOptions = [
827
+ 'chrome', 'firefox', 'safari', 'edge', 'custom',
828
+ ];
925
829
  // Get terminal dimensions
926
830
  const terminalHeight = process.stdout.rows || 24;
927
831
  const availableHeight = terminalHeight - 4; // Subtract status bar (increased for view indicator)
@@ -929,6 +833,17 @@ const App = () => {
929
833
  loadProjects();
930
834
  loadRunningProcesses();
931
835
  loadAllTags();
836
+ // Load settings
837
+ try {
838
+ const settingsPath = path.join(os.homedir(), '.projax', 'settings.json');
839
+ if (fs.existsSync(settingsPath)) {
840
+ const data = fs.readFileSync(settingsPath, 'utf-8');
841
+ setSettings(JSON.parse(data));
842
+ }
843
+ }
844
+ catch {
845
+ // Use defaults
846
+ }
932
847
  // Refresh running processes and git branches every 5 seconds
933
848
  const interval = setInterval(() => {
934
849
  loadRunningProcesses();
@@ -1282,6 +1197,67 @@ const App = () => {
1282
1197
  setSortType(SORT_TYPES[nextIndex]);
1283
1198
  };
1284
1199
  (0, ink_1.useInput)((input, key) => {
1200
+ if (key.mouse) {
1201
+ const { x, y, wheelDown, wheelUp, left } = key.mouse;
1202
+ const { columns: width } = process.stdout;
1203
+ // Assuming project list is on the left 35% and details on the right.
1204
+ const projectListWidth = Math.floor(width * 0.35);
1205
+ if (x < projectListWidth) {
1206
+ // Mouse is over the project list
1207
+ if (wheelUp) {
1208
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
1209
+ return;
1210
+ }
1211
+ if (wheelDown) {
1212
+ setSelectedIndex((prev) => Math.min(projects.length - 1, prev + 1));
1213
+ return;
1214
+ }
1215
+ if (left) {
1216
+ // It's a click, so we need to calculate which item was clicked.
1217
+ // This is an approximation based on the known layout of the ProjectListComponent.
1218
+ const listTopBorder = 1;
1219
+ const listPadding = 1;
1220
+ const listHeaderHeight = 2; // "Projects (...)" + "Filter | Sort"
1221
+ const listStartY = listTopBorder + listPadding + listHeaderHeight;
1222
+ const scrollIndicatorHeight = listScrollOffset > 0 ? 1 : 0;
1223
+ const firstItemY = listStartY + scrollIndicatorHeight;
1224
+ const clickYInList = y - firstItemY;
1225
+ if (clickYInList >= 0) {
1226
+ let cumulativeHeight = 0;
1227
+ const visibleProjects = projects.slice(listScrollOffset);
1228
+ for (let i = 0; i < visibleProjects.length; i++) {
1229
+ const project = visibleProjects[i];
1230
+ const branch = gitBranches.get(project.id);
1231
+ const itemHeight = branch ? 2 : 1;
1232
+ if (clickYInList >= cumulativeHeight && clickYInList < cumulativeHeight + itemHeight) {
1233
+ const newIndex = listScrollOffset + i;
1234
+ if (newIndex < projects.length) {
1235
+ setSelectedIndex(newIndex);
1236
+ setFocusedPanel('list');
1237
+ }
1238
+ break;
1239
+ }
1240
+ cumulativeHeight += itemHeight;
1241
+ if (cumulativeHeight > availableHeight) {
1242
+ break;
1243
+ }
1244
+ }
1245
+ }
1246
+ return;
1247
+ }
1248
+ }
1249
+ else {
1250
+ // Mouse is over the details panel
1251
+ if (wheelUp) {
1252
+ setDetailsScrollOffset(prev => Math.max(0, prev - 1));
1253
+ return;
1254
+ }
1255
+ if (wheelDown) {
1256
+ setDetailsScrollOffset(prev => prev + 1);
1257
+ return;
1258
+ }
1259
+ }
1260
+ }
1285
1261
  // Handle search mode
1286
1262
  if (showSearch) {
1287
1263
  if (key.escape) {
@@ -1309,7 +1285,7 @@ const App = () => {
1309
1285
  return;
1310
1286
  }
1311
1287
  // Don't process input if modal is showing
1312
- if (showHelp || isLoading || error || showUrls || showScriptModal || showAddProjectModal || showConfirmDelete || showSettings) {
1288
+ if (showHelp || isLoading || error || showUrls || showScriptModal || showAddProjectModal || showConfirmDelete) {
1313
1289
  // Handle URLs modal
1314
1290
  if (showUrls && (key.escape || key.return || input === 'q' || input === 'u')) {
1315
1291
  setShowUrls(false);
@@ -1350,6 +1326,47 @@ const App = () => {
1350
1326
  return;
1351
1327
  }
1352
1328
  }
1329
+ // Handle navigation in settings view
1330
+ if (currentView === 'settings') {
1331
+ if (key.tab) {
1332
+ setSettingsSection((prev) => (prev === 'editor' ? 'browser' : 'editor'));
1333
+ setSettingsOptionIndex(0);
1334
+ return;
1335
+ }
1336
+ if (key.upArrow || input === 'k') {
1337
+ setSettingsOptionIndex((prev) => Math.max(0, prev - 1));
1338
+ return;
1339
+ }
1340
+ if (key.downArrow || input === 'j') {
1341
+ const maxIndex = settingsSection === 'editor' ? settingsEditorOptions.length - 1 : settingsBrowserOptions.length - 1;
1342
+ setSettingsOptionIndex((prev) => Math.min(maxIndex, prev + 1));
1343
+ return;
1344
+ }
1345
+ if (input === ' ' || key.return) {
1346
+ // Select option and save
1347
+ const newSettings = { ...settings };
1348
+ if (settingsSection === 'editor') {
1349
+ newSettings.editor = { type: settingsEditorOptions[settingsOptionIndex] };
1350
+ }
1351
+ else {
1352
+ newSettings.browser = { type: settingsBrowserOptions[settingsOptionIndex] };
1353
+ }
1354
+ setSettings(newSettings);
1355
+ // Save to file
1356
+ try {
1357
+ const settingsDir = path.join(os.homedir(), '.projax');
1358
+ if (!fs.existsSync(settingsDir)) {
1359
+ fs.mkdirSync(settingsDir, { recursive: true });
1360
+ }
1361
+ const settingsPath = path.join(settingsDir, 'settings.json');
1362
+ fs.writeFileSync(settingsPath, JSON.stringify(newSettings, null, 2));
1363
+ }
1364
+ catch {
1365
+ // Ignore save errors
1366
+ }
1367
+ return;
1368
+ }
1369
+ }
1353
1370
  // Global navigation - number keys for view switching
1354
1371
  if (input === '1') {
1355
1372
  setCurrentView('projects');
@@ -1365,7 +1382,6 @@ const App = () => {
1365
1382
  }
1366
1383
  if (input === '4') {
1367
1384
  setCurrentView('settings');
1368
- setShowSettings(true);
1369
1385
  return;
1370
1386
  }
1371
1387
  // Terminal panel toggle
@@ -1654,13 +1670,6 @@ const App = () => {
1654
1670
  return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", padding: 1 },
1655
1671
  react_1.default.createElement(HelpModal, { onClose: () => setShowHelp(false) })));
1656
1672
  }
1657
- if (showSettings) {
1658
- return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", padding: 1 },
1659
- react_1.default.createElement(SettingsModal, { onClose: () => {
1660
- setShowSettings(false);
1661
- setCurrentView('projects');
1662
- } })));
1663
- }
1664
1673
  if (isLoading) {
1665
1674
  return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", padding: 1 },
1666
1675
  react_1.default.createElement(LoadingModal, { message: loadingMessage })));
@@ -1819,6 +1828,29 @@ const App = () => {
1819
1828
  })),
1820
1829
  react_1.default.createElement(ink_1.Text, null, " "),
1821
1830
  react_1.default.createElement(ink_1.Text, { color: colors.textTertiary }, "Press 1 to return to Projects")));
1831
+ // Render Settings view
1832
+ const renderSettingsView = () => {
1833
+ const currentOptions = settingsSection === 'editor' ? settingsEditorOptions : settingsBrowserOptions;
1834
+ const currentValue = settingsSection === 'editor' ? settings.editor.type : settings.browser.type;
1835
+ return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", padding: 2 },
1836
+ react_1.default.createElement(ink_1.Text, { bold: true, color: colors.accentCyan }, "Settings"),
1837
+ react_1.default.createElement(ink_1.Text, null, " "),
1838
+ react_1.default.createElement(ink_1.Box, null,
1839
+ react_1.default.createElement(ink_1.Text, { color: settingsSection === 'editor' ? colors.accentCyan : colors.textTertiary, bold: settingsSection === 'editor' }, "[Editor]"),
1840
+ react_1.default.createElement(ink_1.Text, null, " "),
1841
+ react_1.default.createElement(ink_1.Text, { color: settingsSection === 'browser' ? colors.accentCyan : colors.textTertiary, bold: settingsSection === 'browser' }, "[Browser]")),
1842
+ react_1.default.createElement(ink_1.Text, null, " "),
1843
+ currentOptions.map((option, index) => {
1844
+ const isSelected = index === settingsOptionIndex;
1845
+ const isActive = option === currentValue;
1846
+ return (react_1.default.createElement(ink_1.Text, { key: option, color: isSelected ? colors.accentCyan : colors.textPrimary, bold: isSelected },
1847
+ isSelected ? '▶ ' : ' ',
1848
+ isActive ? '● ' : '○ ',
1849
+ option.charAt(0).toUpperCase() + option.slice(1)));
1850
+ }),
1851
+ react_1.default.createElement(ink_1.Text, null, " "),
1852
+ react_1.default.createElement(ink_1.Text, { color: colors.textTertiary }, "Tab: switch section | \u2191\u2193/jk: select | Space/Enter: choose")));
1853
+ };
1822
1854
  return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", height: terminalHeight },
1823
1855
  react_1.default.createElement(ink_1.Box, { paddingX: 1, height: 1 },
1824
1856
  react_1.default.createElement(ink_1.Text, { color: currentView === 'projects' ? colors.accentCyan : colors.textTertiary }, "[1] Projects"),
@@ -1834,6 +1866,7 @@ const App = () => {
1834
1866
  currentView === 'projects' && renderProjectsView(),
1835
1867
  currentView === 'workspaces' && renderWorkspacesView(),
1836
1868
  currentView === 'processes' && renderProcessesView(),
1869
+ currentView === 'settings' && renderSettingsView(),
1837
1870
  react_1.default.createElement(ink_1.Box, { paddingX: 1, borderStyle: "single", borderColor: colors.borderColor, flexShrink: 0, height: 3 },
1838
1871
  react_1.default.createElement(StatusBar, { focusedPanel: focusedPanel, selectedProject: selectedProject }))));
1839
1872
  };