@dacsar/prview 1.0.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 ADDED
@@ -0,0 +1,67 @@
1
+ # prview
2
+
3
+ A terminal dashboard for your GitHub pull requests.
4
+
5
+ `prview` lets you monitor pull requests across all your GitHub repositories from a single TUI, powered by React and Ink.
6
+
7
+ ## Features
8
+
9
+ - View pull requests across multiple repositories in one place
10
+ - Two tabs: **Review Requested** and **My PRs**
11
+ - Real-time filtering and sorting
12
+ - Auto-refresh every 60 seconds
13
+ - Status badges (Approved, Changes Requested, Draft, etc.)
14
+ - Press Enter to open a PR in your browser
15
+
16
+ ## Prerequisites
17
+
18
+ [GitHub CLI](https://cli.github.com/) (`gh`) must be installed and authenticated:
19
+
20
+ ```sh
21
+ gh auth login
22
+ ```
23
+
24
+ ## Installation
25
+
26
+ ```sh
27
+ npm install -g @dacsar/prview
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```sh
33
+ pv
34
+ ```
35
+
36
+ ## Keybindings
37
+
38
+ | Key | Action |
39
+ | --- | --- |
40
+ | `Tab` | Switch between Review Requested / My PRs |
41
+ | `j` / `Down` | Move selection down |
42
+ | `k` / `Up` | Move selection up |
43
+ | `Enter` | Open selected PR in browser |
44
+ | `/` | Enter filter mode |
45
+ | `Esc` | Exit filter mode / Clear filter |
46
+ | `r` | Refresh |
47
+ | `q` | Quit |
48
+
49
+ ## Tech Stack
50
+
51
+ - [React](https://react.dev/) + [Ink](https://github.com/vadimdemedes/ink) — Terminal UI
52
+ - [TypeScript](https://www.typescriptlang.org/)
53
+ - [esbuild](https://esbuild.github.io/) — Bundler
54
+ - [Biome](https://biomejs.dev/) — Linter & Formatter
55
+
56
+ ## Development
57
+
58
+ ```sh
59
+ git clone https://github.com/your-username/prview.git
60
+ cd prview
61
+ make setup
62
+ npm run dev
63
+ ```
64
+
65
+ ## License
66
+
67
+ MIT
package/dist/app.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function App(): import("react/jsx-runtime").JSX.Element;
package/dist/app.js ADDED
@@ -0,0 +1,81 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useCallback } from 'react';
3
+ import { Box, Text, useApp, useInput } from 'ink';
4
+ import { usePullRequests } from './hooks/use-pull-requests.js';
5
+ import { useFilterSort } from './hooks/use-filter-sort.js';
6
+ import { openUrl } from './utils/open-url.js';
7
+ import { TabBar } from './components/tab-bar.js';
8
+ import { PrTable } from './components/pr-table.js';
9
+ import { FilterBar } from './components/filter-bar.js';
10
+ import { HelpBar } from './components/help-bar.js';
11
+ import { Loading } from './components/loading.js';
12
+ export function App() {
13
+ const { exit } = useApp();
14
+ const { reviewRequested, myPRs, loading, error, refresh } = usePullRequests();
15
+ const [activeTab, setActiveTab] = useState('review-requested');
16
+ const [selectedIndex, setSelectedIndex] = useState(0);
17
+ const [filter, setFilter] = useState('');
18
+ const [isFilterMode, setIsFilterMode] = useState(false);
19
+ const [sort, setSort] = useState({ key: 'time', direction: 'desc' });
20
+ const currentPRs = activeTab === 'review-requested' ? reviewRequested : myPRs;
21
+ const filteredPRs = useFilterSort(currentPRs, filter, sort);
22
+ const clampIndex = useCallback((index) => Math.max(0, Math.min(index, filteredPRs.length - 1)), [filteredPRs.length]);
23
+ useInput((_input, key) => {
24
+ if (key.escape) {
25
+ setIsFilterMode(false);
26
+ return;
27
+ }
28
+ }, { isActive: isFilterMode });
29
+ useInput((input, key) => {
30
+ if (input === 'q') {
31
+ exit();
32
+ return;
33
+ }
34
+ if (key.tab) {
35
+ setActiveTab(prev => prev === 'review-requested' ? 'my-prs' : 'review-requested');
36
+ setSelectedIndex(0);
37
+ return;
38
+ }
39
+ if (input === 'j' || key.downArrow) {
40
+ setSelectedIndex(prev => clampIndex(prev + 1));
41
+ return;
42
+ }
43
+ if (input === 'k' || key.upArrow) {
44
+ setSelectedIndex(prev => clampIndex(prev - 1));
45
+ return;
46
+ }
47
+ if (key.return) {
48
+ const pr = filteredPRs[selectedIndex];
49
+ if (pr) {
50
+ openUrl(pr.url);
51
+ }
52
+ return;
53
+ }
54
+ if (input === 's') {
55
+ setSort(prev => {
56
+ if (prev.key === 'title') {
57
+ return { key: 'time', direction: prev.direction };
58
+ }
59
+ if (prev.key === 'time' && prev.direction === 'desc') {
60
+ return { key: 'time', direction: 'asc' };
61
+ }
62
+ return { key: 'title', direction: 'asc' };
63
+ });
64
+ return;
65
+ }
66
+ if (input === '/') {
67
+ setIsFilterMode(true);
68
+ return;
69
+ }
70
+ if (key.escape) {
71
+ setFilter('');
72
+ setIsFilterMode(false);
73
+ return;
74
+ }
75
+ if (input === 'r') {
76
+ refresh();
77
+ return;
78
+ }
79
+ }, { isActive: !isFilterMode });
80
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(TabBar, { activeTab: activeTab, reviewCount: reviewRequested.length, myCount: myPRs.length }), error && (_jsx(Box, { paddingX: 2, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })), loading && filteredPRs.length === 0 ? (_jsx(Loading, { error: error })) : (_jsx(PrTable, { prs: filteredPRs, selectedIndex: selectedIndex })), _jsx(FilterBar, { isActive: isFilterMode, filter: filter, onFilterChange: setFilter }), _jsx(Box, { paddingX: 1, children: _jsxs(Text, { dimColor: true, children: ["Sort: ", sort.key, " (", sort.direction, ")"] }) }), _jsx(HelpBar, { isFilterMode: isFilterMode })] }));
81
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env -S node --no-warnings
2
+ export {};