@colmbus72/yeehaw 0.6.0 → 0.6.1
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/components/Panel.js
CHANGED
|
@@ -27,5 +27,5 @@ function renderHints(hints) {
|
|
|
27
27
|
return parts;
|
|
28
28
|
}
|
|
29
29
|
export function Panel({ title, children, focused = false, width, hints }) {
|
|
30
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: focused ? BRAND_COLOR : 'gray', width: width, children: [_jsx(Box, { paddingX: 1, marginBottom: 0, children: _jsx(Text, { bold: true, color: focused ? BRAND_COLOR : 'gray', children: title }) }), _jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: children }), _jsx(Box, { paddingX: 1, justifyContent: "flex-end", height: 1, children: focused && hints ? (_jsx(Text, { children: renderHints(hints) })) : (_jsx(Text, { children: " " })) })] }));
|
|
30
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: focused ? BRAND_COLOR : 'gray', width: width, flexShrink: 1, overflow: "hidden", children: [_jsx(Box, { paddingX: 1, marginBottom: 0, flexShrink: 0, children: _jsx(Text, { bold: true, color: focused ? BRAND_COLOR : 'gray', children: title }) }), _jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, flexShrink: 1, overflow: "hidden", children: children }), _jsx(Box, { paddingX: 1, justifyContent: "flex-end", height: 1, flexShrink: 0, children: focused && hints ? (_jsx(Text, { children: renderHints(hints) })) : (_jsx(Text, { children: " " })) })] }));
|
|
31
31
|
}
|
|
@@ -52,5 +52,5 @@ export function ScrollableMarkdown({ children, focused = false, height = 20, })
|
|
|
52
52
|
// Get visible slice of lines
|
|
53
53
|
const displayLines = lines.slice(scrollOffset, scrollOffset + visibleLines);
|
|
54
54
|
const showScrollIndicator = totalLines > visibleLines;
|
|
55
|
-
return (_jsxs(Box, { flexDirection: "column",
|
|
55
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflow: "hidden", children: [_jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: displayLines.map((line, idx) => (_jsx(Text, { wrap: "truncate", children: line || ' ' }, scrollOffset + idx))) }), showScrollIndicator && (_jsx(Box, { justifyContent: "flex-end", flexShrink: 0, children: _jsxs(Text, { dimColor: true, children: ["[", scrollOffset + 1, "-", Math.min(scrollOffset + visibleLines, totalLines), "/", totalLines, "]", focused ? ' (j/k to scroll)' : ''] }) }))] }));
|
|
56
56
|
}
|
package/dist/lib/tmux.js
CHANGED
|
@@ -170,6 +170,8 @@ function setWindowType(windowIndex, type) {
|
|
|
170
170
|
]);
|
|
171
171
|
}
|
|
172
172
|
export function createClaudeWindow(workingDir, windowName) {
|
|
173
|
+
// Ensure workingDir is valid, fallback to current working directory
|
|
174
|
+
const effectiveWorkingDir = workingDir || process.cwd();
|
|
173
175
|
// Build MCP config for yeehaw server
|
|
174
176
|
const mcpConfig = JSON.stringify({
|
|
175
177
|
mcpServers: {
|
|
@@ -190,7 +192,7 @@ export function createClaudeWindow(workingDir, windowName) {
|
|
|
190
192
|
'-a',
|
|
191
193
|
'-t', YEEHAW_SESSION,
|
|
192
194
|
'-n', windowName,
|
|
193
|
-
'-c',
|
|
195
|
+
'-c', effectiveWorkingDir,
|
|
194
196
|
claudeCmd,
|
|
195
197
|
]);
|
|
196
198
|
// Get the window index we just created (new window is now current)
|
|
@@ -203,6 +205,8 @@ export function createClaudeWindow(workingDir, windowName) {
|
|
|
203
205
|
return windowIndex;
|
|
204
206
|
}
|
|
205
207
|
export function createClaudeWindowWithPrompt(workingDir, windowName, systemPrompt) {
|
|
208
|
+
// Ensure workingDir is valid, fallback to current working directory
|
|
209
|
+
const effectiveWorkingDir = workingDir || process.cwd();
|
|
206
210
|
// Build MCP config for yeehaw server
|
|
207
211
|
const mcpConfig = JSON.stringify({
|
|
208
212
|
mcpServers: {
|
|
@@ -223,7 +227,7 @@ export function createClaudeWindowWithPrompt(workingDir, windowName, systemPromp
|
|
|
223
227
|
'-a',
|
|
224
228
|
'-t', YEEHAW_SESSION,
|
|
225
229
|
'-n', windowName,
|
|
226
|
-
'-c',
|
|
230
|
+
'-c', effectiveWorkingDir,
|
|
227
231
|
claudeCmd,
|
|
228
232
|
]);
|
|
229
233
|
// Get the window index we just created
|
package/dist/views/IssuesView.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// src/views/IssuesView.tsx
|
|
3
3
|
import { useState, useEffect, useCallback } from 'react';
|
|
4
|
-
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { Box, Text, useInput, useStdout } from 'ink';
|
|
5
5
|
import TextInput from 'ink-text-input';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { homedir } from 'os';
|
|
6
8
|
import { Header } from '../components/Header.js';
|
|
7
9
|
import { Panel } from '../components/Panel.js';
|
|
8
10
|
import { List } from '../components/List.js';
|
|
@@ -12,6 +14,13 @@ import { saveProject } from '../lib/config.js';
|
|
|
12
14
|
import { buildProjectContext } from '../lib/context.js';
|
|
13
15
|
import { openIssueInBrowser } from '../lib/github.js';
|
|
14
16
|
import { saveLinearApiKey, validateLinearApiKey, LINEAR_API_KEY_URL, clearLinearToken } from '../lib/auth/linear.js';
|
|
17
|
+
// Expand ~ in paths to home directory
|
|
18
|
+
function expandPath(path) {
|
|
19
|
+
if (path.startsWith('~/')) {
|
|
20
|
+
return join(homedir(), path.slice(2));
|
|
21
|
+
}
|
|
22
|
+
return path;
|
|
23
|
+
}
|
|
15
24
|
// Status indicator based on Linear state type
|
|
16
25
|
function getStatusIndicator(stateType) {
|
|
17
26
|
switch (stateType) {
|
|
@@ -51,15 +60,21 @@ function getInitials(name) {
|
|
|
51
60
|
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
52
61
|
}
|
|
53
62
|
export function IssuesView({ project, onBack, onOpenClaude }) {
|
|
63
|
+
const { stdout } = useStdout();
|
|
54
64
|
const [focusedPanel, setFocusedPanel] = useState('list');
|
|
55
65
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
56
66
|
const [viewState, setViewState] = useState({ type: 'loading' });
|
|
57
67
|
const [linearProvider, setLinearProvider] = useState(null);
|
|
58
68
|
const [apiKeyInput, setApiKeyInput] = useState('');
|
|
69
|
+
// Calculate dynamic height for ScrollableMarkdown based on terminal size
|
|
70
|
+
// Layout: Header(3) + FilterInfo(1) + PanelBorders(2) + PanelTitle(1) + PanelHints(1) + Padding(2) = ~10 lines overhead
|
|
71
|
+
const terminalHeight = stdout?.rows ?? 24;
|
|
72
|
+
const panelContentHeight = Math.max(8, terminalHeight - 10);
|
|
59
73
|
// Filter state for Linear
|
|
60
74
|
const [cycles, setCycles] = useState([]);
|
|
61
75
|
const [assignees, setAssignees] = useState([]);
|
|
62
76
|
const [currentFilter, setCurrentFilter] = useState({
|
|
77
|
+
stateType: ['backlog', 'unstarted', 'started'], // Default to open issues
|
|
63
78
|
sortBy: 'priority',
|
|
64
79
|
sortDirection: 'desc',
|
|
65
80
|
});
|
|
@@ -71,6 +86,9 @@ export function IssuesView({ project, onBack, onOpenClaude }) {
|
|
|
71
86
|
const [teamName, setTeamName] = useState();
|
|
72
87
|
// Cached current user ID for filter
|
|
73
88
|
const [currentUserId, setCurrentUserId] = useState(null);
|
|
89
|
+
// Track if user explicitly selected "Me" filter (separate from actual ID)
|
|
90
|
+
// Default to true since we want "assigned to me" as the default filter
|
|
91
|
+
const [filterByMe, setFilterByMe] = useState(true);
|
|
74
92
|
const loadIssues = useCallback(async (filter) => {
|
|
75
93
|
setViewState({ type: 'loading' });
|
|
76
94
|
const provider = getProvider(project);
|
|
@@ -116,14 +134,23 @@ export function IssuesView({ project, onBack, onOpenClaude }) {
|
|
|
116
134
|
setCycles(fetchedCycles);
|
|
117
135
|
setAssignees(fetchedAssignees);
|
|
118
136
|
setCurrentUserId(userId);
|
|
119
|
-
// Set default filter: current cycle, assigned to me
|
|
137
|
+
// Set default filter: current cycle, assigned to me, open issues
|
|
120
138
|
const activeCycleId = linearProv.getActiveCycleId();
|
|
121
139
|
const defaultFilter = {
|
|
122
140
|
cycleId: activeCycleId,
|
|
123
|
-
|
|
141
|
+
stateType: ['backlog', 'unstarted', 'started'],
|
|
124
142
|
sortBy: 'priority',
|
|
125
143
|
sortDirection: 'desc',
|
|
126
144
|
};
|
|
145
|
+
// Only add assigneeId if we have a valid user ID
|
|
146
|
+
if (userId) {
|
|
147
|
+
defaultFilter.assigneeId = userId;
|
|
148
|
+
setFilterByMe(true);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Couldn't get user ID, so can't filter by "me"
|
|
152
|
+
setFilterByMe(false);
|
|
153
|
+
}
|
|
127
154
|
setCurrentFilter(defaultFilter);
|
|
128
155
|
setFilterInitialized(true);
|
|
129
156
|
filter = defaultFilter;
|
|
@@ -189,14 +216,23 @@ export function IssuesView({ project, onBack, onOpenClaude }) {
|
|
|
189
216
|
setCycles(fetchedCycles);
|
|
190
217
|
setAssignees(fetchedAssignees);
|
|
191
218
|
setCurrentUserId(userId);
|
|
192
|
-
// Set default filter
|
|
219
|
+
// Set default filter: current cycle, assigned to me, open issues
|
|
193
220
|
const activeCycleId = linearProvider.getActiveCycleId();
|
|
194
221
|
const defaultFilter = {
|
|
195
222
|
cycleId: activeCycleId,
|
|
196
|
-
|
|
223
|
+
stateType: ['backlog', 'unstarted', 'started'],
|
|
197
224
|
sortBy: 'priority',
|
|
198
225
|
sortDirection: 'desc',
|
|
199
226
|
};
|
|
227
|
+
// Only add assigneeId if we have a valid user ID
|
|
228
|
+
if (userId) {
|
|
229
|
+
defaultFilter.assigneeId = userId;
|
|
230
|
+
setFilterByMe(true);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
// Couldn't get user ID, so can't filter by "me"
|
|
234
|
+
setFilterByMe(false);
|
|
235
|
+
}
|
|
200
236
|
setCurrentFilter(defaultFilter);
|
|
201
237
|
setFilterInitialized(true);
|
|
202
238
|
// Fetch issues
|
|
@@ -400,16 +436,23 @@ export function IssuesView({ project, onBack, onOpenClaude }) {
|
|
|
400
436
|
switch (field) {
|
|
401
437
|
case 'assignee':
|
|
402
438
|
if (option.id === '__me__') {
|
|
403
|
-
|
|
439
|
+
// Only set if we have a valid user ID
|
|
440
|
+
if (currentUserId) {
|
|
441
|
+
newFilter.assigneeId = currentUserId;
|
|
442
|
+
}
|
|
443
|
+
setFilterByMe(true);
|
|
404
444
|
}
|
|
405
445
|
else if (option.id === '__any__') {
|
|
406
446
|
delete newFilter.assigneeId;
|
|
447
|
+
setFilterByMe(false);
|
|
407
448
|
}
|
|
408
449
|
else if (option.id === '__none__') {
|
|
409
450
|
newFilter.assigneeId = null;
|
|
451
|
+
setFilterByMe(false);
|
|
410
452
|
}
|
|
411
453
|
else {
|
|
412
454
|
newFilter.assigneeId = option.id;
|
|
455
|
+
setFilterByMe(false);
|
|
413
456
|
}
|
|
414
457
|
break;
|
|
415
458
|
case 'cycle':
|
|
@@ -459,17 +502,25 @@ export function IssuesView({ project, onBack, onOpenClaude }) {
|
|
|
459
502
|
switch (field) {
|
|
460
503
|
case 'assignee':
|
|
461
504
|
if (option.id === '__me__') {
|
|
462
|
-
|
|
463
|
-
|
|
505
|
+
// Use cached currentUserId or fetch it
|
|
506
|
+
const userId = currentUserId || await linearProvider?.getCurrentUserId();
|
|
507
|
+
if (userId) {
|
|
508
|
+
newFilter.assigneeId = userId;
|
|
509
|
+
setCurrentUserId(userId); // Cache it if we just fetched
|
|
510
|
+
}
|
|
511
|
+
setFilterByMe(true);
|
|
464
512
|
}
|
|
465
513
|
else if (option.id === '__any__') {
|
|
466
514
|
delete newFilter.assigneeId;
|
|
515
|
+
setFilterByMe(false);
|
|
467
516
|
}
|
|
468
517
|
else if (option.id === '__none__') {
|
|
469
518
|
newFilter.assigneeId = null;
|
|
519
|
+
setFilterByMe(false);
|
|
470
520
|
}
|
|
471
521
|
else {
|
|
472
522
|
newFilter.assigneeId = option.id;
|
|
523
|
+
setFilterByMe(false);
|
|
473
524
|
}
|
|
474
525
|
break;
|
|
475
526
|
case 'cycle':
|
|
@@ -519,6 +570,13 @@ export function IssuesView({ project, onBack, onOpenClaude }) {
|
|
|
519
570
|
else {
|
|
520
571
|
workingDir = project.path;
|
|
521
572
|
}
|
|
573
|
+
// Expand ~ to home directory and fallback to cwd if empty
|
|
574
|
+
if (workingDir) {
|
|
575
|
+
workingDir = expandPath(workingDir);
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
workingDir = process.cwd();
|
|
579
|
+
}
|
|
522
580
|
const context = buildClaudeContext(issue);
|
|
523
581
|
onOpenClaude(workingDir, context);
|
|
524
582
|
};
|
|
@@ -692,21 +750,72 @@ export function IssuesView({ project, onBack, onOpenClaude }) {
|
|
|
692
750
|
if (!isLinear)
|
|
693
751
|
return '';
|
|
694
752
|
const parts = [];
|
|
695
|
-
|
|
753
|
+
// Assignee - check explicitly for the different states
|
|
754
|
+
// assigneeId can be: string (specific user), null (unassigned), or undefined (anyone)
|
|
755
|
+
if (currentFilter.assigneeId === null) {
|
|
756
|
+
parts.push('Unassigned');
|
|
757
|
+
}
|
|
758
|
+
else if (filterByMe) {
|
|
759
|
+
// User explicitly selected "Me" filter
|
|
760
|
+
parts.push('Me');
|
|
761
|
+
}
|
|
762
|
+
else if (currentFilter.assigneeId !== undefined && currentFilter.assigneeId !== '') {
|
|
763
|
+
// Has a specific assignee ID (not "me")
|
|
696
764
|
const assignee = assignees.find((a) => a.id === currentFilter.assigneeId);
|
|
697
|
-
parts.push(assignee
|
|
765
|
+
parts.push(assignee?.name ?? 'Assigned');
|
|
698
766
|
}
|
|
699
|
-
else
|
|
700
|
-
|
|
767
|
+
else {
|
|
768
|
+
// undefined or empty string means anyone
|
|
769
|
+
parts.push('Anyone');
|
|
701
770
|
}
|
|
771
|
+
// Cycle
|
|
702
772
|
if (currentFilter.cycleId) {
|
|
703
773
|
const cycle = cycles.find((c) => c.id === currentFilter.cycleId);
|
|
704
774
|
parts.push(cycle?.name ?? 'Cycle');
|
|
705
775
|
}
|
|
706
|
-
|
|
776
|
+
else {
|
|
777
|
+
parts.push('Any cycle');
|
|
778
|
+
}
|
|
779
|
+
// Status
|
|
780
|
+
if (currentFilter.stateType) {
|
|
781
|
+
const stateType = currentFilter.stateType;
|
|
782
|
+
if (Array.isArray(stateType)) {
|
|
783
|
+
// Check if it's the "open" preset
|
|
784
|
+
const isOpenPreset = stateType.length === 3 &&
|
|
785
|
+
stateType.includes('backlog') &&
|
|
786
|
+
stateType.includes('unstarted') &&
|
|
787
|
+
stateType.includes('started');
|
|
788
|
+
parts.push(isOpenPreset ? 'Open' : stateType.join(', '));
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
// Single status
|
|
792
|
+
const statusLabels = {
|
|
793
|
+
backlog: 'Backlog',
|
|
794
|
+
unstarted: 'Todo',
|
|
795
|
+
started: 'In Progress',
|
|
796
|
+
completed: 'Done',
|
|
797
|
+
canceled: 'Canceled',
|
|
798
|
+
};
|
|
799
|
+
parts.push(statusLabels[stateType] ?? stateType);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
parts.push('All statuses');
|
|
804
|
+
}
|
|
805
|
+
// Sort
|
|
806
|
+
if (currentFilter.sortBy === 'priority') {
|
|
807
|
+
parts.push(currentFilter.sortDirection === 'asc' ? '↑Priority' : '↓Priority');
|
|
808
|
+
}
|
|
809
|
+
else if (currentFilter.sortBy === 'createdAt') {
|
|
810
|
+
parts.push('Recent');
|
|
811
|
+
}
|
|
812
|
+
else if (currentFilter.sortBy === 'updatedAt') {
|
|
813
|
+
parts.push('Updated');
|
|
814
|
+
}
|
|
815
|
+
return parts.join(' • ');
|
|
707
816
|
};
|
|
708
817
|
const detailsHints = 'j/k scroll';
|
|
709
|
-
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: getSubtitle(), color: project.color }), isLinear && (_jsxs(Box, { paddingX: 2, children: [_jsx(Text, { dimColor: true, children: "Showing: " }), _jsx(Text, { color: "cyan", children: getFilterDescription() }), _jsxs(Text, { dimColor: true, children: [" (", viewState.issues.length, ")"] })] })), _jsxs(Box, { flexGrow: 1, paddingX: 1, gap: 2, children: [_jsx(Panel, { title: "Issues", focused: focusedPanel === 'list', width: "45%", children: issueItems.length > 0 ? (_jsx(List, { items: issueItems, focused: focusedPanel === 'list', selectedIndex: selectedIndex, onSelectionChange: setSelectedIndex, onAction: (item, actionKey) => {
|
|
818
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: getSubtitle(), color: project.color }), isLinear && (_jsxs(Box, { paddingX: 2, children: [_jsx(Text, { dimColor: true, children: "Showing: " }), _jsx(Text, { color: "cyan", children: getFilterDescription() }), _jsxs(Text, { dimColor: true, children: [" (", viewState.issues.length, ")"] })] })), _jsxs(Box, { flexGrow: 1, flexShrink: 1, paddingX: 1, gap: 2, overflow: "hidden", children: [_jsx(Panel, { title: "Issues", focused: focusedPanel === 'list', width: "45%", children: issueItems.length > 0 ? (_jsx(List, { items: issueItems, focused: focusedPanel === 'list', selectedIndex: selectedIndex, onSelectionChange: setSelectedIndex, onAction: (item, actionKey) => {
|
|
710
819
|
const issue = viewState.issues[parseInt(item.id, 10)];
|
|
711
820
|
if (!issue)
|
|
712
821
|
return;
|
|
@@ -716,5 +825,5 @@ export function IssuesView({ project, onBack, onOpenClaude }) {
|
|
|
716
825
|
else if (actionKey === 'o') {
|
|
717
826
|
openIssueInBrowser(issue.url);
|
|
718
827
|
}
|
|
719
|
-
} })) : (_jsx(Text, { dimColor: true, children: "No issues match the current filter" })) }), _jsx(Panel, { title: selectedIssue ? `${selectedIssue.identifier} ${truncate(selectedIssue.title, 30)}` : 'Details', focused: focusedPanel === 'details', width: "55%", hints: detailsHints, children: selectedIssue ? (_jsx(ScrollableMarkdown, { focused: focusedPanel === 'details', height:
|
|
828
|
+
} })) : (_jsx(Text, { dimColor: true, children: "No issues match the current filter" })) }), _jsx(Panel, { title: selectedIssue ? `${selectedIssue.identifier} ${truncate(selectedIssue.title, 30)}` : 'Details', focused: focusedPanel === 'details', width: "55%", hints: detailsHints, children: selectedIssue ? (_jsx(ScrollableMarkdown, { focused: focusedPanel === 'details', height: panelContentHeight, children: buildIssueDetails(selectedIssue) })) : (_jsx(Text, { dimColor: true, children: "Select an issue to view details" })) })] })] }));
|
|
720
829
|
}
|