@colmbus72/yeehaw 0.2.0 → 0.4.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/claude-plugin/.claude-plugin/plugin.json +7 -0
- package/claude-plugin/skills/yeehaw-project-setup/SKILL.md +129 -0
- package/claude-plugin/skills/yeehaw-project-setup/references/color-discovery.md +170 -0
- package/claude-plugin/skills/yeehaw-project-setup/references/wiki-templates.md +266 -0
- package/dist/app.js +66 -56
- package/dist/components/Header.d.ts +8 -1
- package/dist/components/Header.js +33 -23
- package/dist/components/LivestockHeader.d.ts +7 -0
- package/dist/components/LivestockHeader.js +122 -0
- package/dist/components/PathInput.js +81 -19
- package/dist/components/SplashScreen.d.ts +5 -0
- package/dist/components/SplashScreen.js +178 -0
- package/dist/index.js +2 -5
- package/dist/lib/tmux-config.js +11 -0
- package/dist/lib/tmux.d.ts +5 -0
- package/dist/lib/tmux.js +57 -7
- package/dist/lib/update-check.d.ts +8 -0
- package/dist/lib/update-check.js +12 -0
- package/dist/mcp-server.js +0 -0
- package/dist/types.d.ts +6 -0
- package/dist/views/BarnContext.js +0 -4
- package/dist/views/GlobalDashboard.d.ts +5 -1
- package/dist/views/GlobalDashboard.js +59 -32
- package/dist/views/IssuesView.js +1 -1
- package/dist/views/LivestockDetailView.d.ts +8 -3
- package/dist/views/LivestockDetailView.js +56 -54
- package/dist/views/LogsView.js +1 -1
- package/dist/views/ProjectContext.js +142 -24
- package/dist/views/WikiView.js +0 -4
- package/package.json +2 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
3
|
+
import { Box, Text, useStdout } from 'ink';
|
|
4
|
+
import figlet from 'figlet';
|
|
5
|
+
// Tumbleweed ASCII art - same as in Header.tsx
|
|
6
|
+
const TUMBLEWEED = [
|
|
7
|
+
' ░ ░▒░ ░▒░',
|
|
8
|
+
'░▒ · ‿ · ▒░',
|
|
9
|
+
'▒░ ▒░▒░ ░▒',
|
|
10
|
+
' ░▒░ ░▒░ ░',
|
|
11
|
+
];
|
|
12
|
+
const TUMBLEWEED_COLOR = '#b8860b';
|
|
13
|
+
const BRAND_COLOR = '#f0c040';
|
|
14
|
+
// Match Header.tsx positioning exactly
|
|
15
|
+
const HEADER_PADDING_TOP = 1;
|
|
16
|
+
const TUMBLEWEED_TOP_PADDING = 1;
|
|
17
|
+
const HEADER_PADDING_LEFT = 2;
|
|
18
|
+
const TUMBLEWEED_WIDTH = 11;
|
|
19
|
+
const TITLE_OFFSET_LEFT = HEADER_PADDING_LEFT + TUMBLEWEED_WIDTH + 2;
|
|
20
|
+
// Gradient color helpers (matching Header.tsx)
|
|
21
|
+
function hexToRgb(hex) {
|
|
22
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
23
|
+
return result
|
|
24
|
+
? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) }
|
|
25
|
+
: null;
|
|
26
|
+
}
|
|
27
|
+
function interpolateColor(c1, c2, factor) {
|
|
28
|
+
const r = Math.round(c1.r + (c2.r - c1.r) * factor);
|
|
29
|
+
const g = Math.round(c1.g + (c2.g - c1.g) * factor);
|
|
30
|
+
const b = Math.round(c1.b + (c2.b - c1.b) * factor);
|
|
31
|
+
return `rgb(${r},${g},${b})`;
|
|
32
|
+
}
|
|
33
|
+
function getGradientColor(row, totalRows) {
|
|
34
|
+
const rgb = hexToRgb(BRAND_COLOR);
|
|
35
|
+
if (!rgb)
|
|
36
|
+
return BRAND_COLOR;
|
|
37
|
+
const startRgb = rgb;
|
|
38
|
+
const endRgb = { r: Math.round(rgb.r * 0.3), g: Math.round(rgb.g * 0.3), b: Math.round(rgb.b * 0.3) };
|
|
39
|
+
const factor = row / Math.max(totalRows - 1, 1);
|
|
40
|
+
return interpolateColor(startRgb, endRgb, factor);
|
|
41
|
+
}
|
|
42
|
+
export function SplashScreen({ onComplete }) {
|
|
43
|
+
const { stdout } = useStdout();
|
|
44
|
+
const terminalHeight = stdout?.rows || 24;
|
|
45
|
+
const terminalWidth = stdout?.columns || 80;
|
|
46
|
+
const [phase, setPhase] = useState('build');
|
|
47
|
+
const [visibleCount, setVisibleCount] = useState(0);
|
|
48
|
+
const [waveDistance, setWaveDistance] = useState(0);
|
|
49
|
+
const waveOriginRow = HEADER_PADDING_TOP + TUMBLEWEED_TOP_PADDING + 2;
|
|
50
|
+
const waveOriginCol = HEADER_PADDING_LEFT + 5;
|
|
51
|
+
// Generate title dots from figlet
|
|
52
|
+
const allDots = useMemo(() => {
|
|
53
|
+
const dots = [];
|
|
54
|
+
try {
|
|
55
|
+
const ascii = figlet.textSync('YEEHAW', { font: 'ANSI Shadow' });
|
|
56
|
+
const lines = ascii.split('\n').filter(line => line.trim() !== '');
|
|
57
|
+
const totalRows = lines.length;
|
|
58
|
+
lines.forEach((line, row) => {
|
|
59
|
+
for (let col = 0; col < line.length; col++) {
|
|
60
|
+
const char = line[col];
|
|
61
|
+
if (char !== ' ') {
|
|
62
|
+
const screenRow = HEADER_PADDING_TOP + row;
|
|
63
|
+
const screenCol = TITLE_OFFSET_LEFT + col;
|
|
64
|
+
const distance = Math.sqrt(Math.pow(screenRow - waveOriginRow, 2) +
|
|
65
|
+
Math.pow((screenCol - waveOriginCol) * 0.5, 2));
|
|
66
|
+
dots.push({ row: screenRow, col: screenCol, distance, gradientRow: row, totalRows });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Fallback if figlet fails
|
|
73
|
+
}
|
|
74
|
+
return dots;
|
|
75
|
+
}, [waveOriginRow, waveOriginCol]);
|
|
76
|
+
const maxDistance = useMemo(() => {
|
|
77
|
+
if (allDots.length === 0)
|
|
78
|
+
return 100;
|
|
79
|
+
return Math.max(...allDots.map((d) => d.distance)) + 5;
|
|
80
|
+
}, [allDots]);
|
|
81
|
+
// Flatten tumbleweed into array of { char, row, col } for animation
|
|
82
|
+
const allChars = useMemo(() => {
|
|
83
|
+
const chars = [];
|
|
84
|
+
TUMBLEWEED.forEach((line, row) => {
|
|
85
|
+
for (let col = 0; col < line.length; col++) {
|
|
86
|
+
const char = line[col];
|
|
87
|
+
if (char !== ' ') {
|
|
88
|
+
chars.push({ char, row, col });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
return chars;
|
|
93
|
+
}, []);
|
|
94
|
+
// Shuffle the characters for random build order
|
|
95
|
+
const shuffledChars = useMemo(() => [...allChars].sort(() => Math.random() - 0.5), [allChars]);
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (phase === 'build') {
|
|
98
|
+
if (visibleCount < shuffledChars.length) {
|
|
99
|
+
const timer = setTimeout(() => {
|
|
100
|
+
setVisibleCount((c) => Math.min(c + 2, shuffledChars.length));
|
|
101
|
+
}, 30);
|
|
102
|
+
return () => clearTimeout(timer);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Tumbleweed complete, start pulse
|
|
106
|
+
const timer = setTimeout(() => setPhase('pulse'), 100);
|
|
107
|
+
return () => clearTimeout(timer);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (phase === 'pulse') {
|
|
111
|
+
if (waveDistance < maxDistance) {
|
|
112
|
+
// Advance the wave
|
|
113
|
+
const timer = setTimeout(() => {
|
|
114
|
+
setWaveDistance((d) => d + 3);
|
|
115
|
+
}, 20);
|
|
116
|
+
return () => clearTimeout(timer);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// Wave complete
|
|
120
|
+
const timer = setTimeout(onComplete, 200);
|
|
121
|
+
return () => clearTimeout(timer);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}, [phase, visibleCount, waveDistance, shuffledChars.length, maxDistance, onComplete]);
|
|
125
|
+
// Build the current visible state of the tumbleweed
|
|
126
|
+
const visibleSet = new Set(shuffledChars.slice(0, visibleCount).map((c) => `${c.row},${c.col}`));
|
|
127
|
+
const renderedTumbleweed = TUMBLEWEED.map((line, row) => {
|
|
128
|
+
let result = '';
|
|
129
|
+
for (let col = 0; col < line.length; col++) {
|
|
130
|
+
const char = line[col];
|
|
131
|
+
if (char === ' ' || visibleSet.has(`${row},${col}`)) {
|
|
132
|
+
result += char;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
result += ' ';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
});
|
|
140
|
+
const topPadding = HEADER_PADDING_TOP + TUMBLEWEED_TOP_PADDING;
|
|
141
|
+
// Render the wave revealing dots
|
|
142
|
+
const renderWave = () => {
|
|
143
|
+
const lines = [];
|
|
144
|
+
const waveWidth = 8;
|
|
145
|
+
// Group dots by row
|
|
146
|
+
const dotsByRow = new Map();
|
|
147
|
+
allDots.forEach((dot) => {
|
|
148
|
+
if (!dotsByRow.has(dot.row)) {
|
|
149
|
+
dotsByRow.set(dot.row, []);
|
|
150
|
+
}
|
|
151
|
+
dotsByRow.get(dot.row).push(dot);
|
|
152
|
+
});
|
|
153
|
+
for (let row = 0; row < terminalHeight; row++) {
|
|
154
|
+
const rowDots = dotsByRow.get(row) || [];
|
|
155
|
+
if (rowDots.length === 0) {
|
|
156
|
+
lines.push(_jsx(Text, { children: ' '.repeat(terminalWidth) }, row));
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
rowDots.sort((a, b) => a.col - b.col);
|
|
160
|
+
const segments = [];
|
|
161
|
+
let lastCol = 0;
|
|
162
|
+
for (const dot of rowDots) {
|
|
163
|
+
if (dot.distance > waveDistance)
|
|
164
|
+
continue;
|
|
165
|
+
if (dot.col > lastCol) {
|
|
166
|
+
segments.push(_jsx(Text, { children: ' '.repeat(dot.col - lastCol) }, `space-${lastCol}`));
|
|
167
|
+
}
|
|
168
|
+
const atWaveFront = dot.distance >= waveDistance - waveWidth;
|
|
169
|
+
const color = getGradientColor(dot.gradientRow, dot.totalRows);
|
|
170
|
+
segments.push(_jsx(Text, { color: color, bold: atWaveFront, children: "\u00B7" }, `dot-${dot.col}`));
|
|
171
|
+
lastCol = dot.col + 1;
|
|
172
|
+
}
|
|
173
|
+
lines.push(_jsx(Box, { children: segments }, row));
|
|
174
|
+
}
|
|
175
|
+
return lines;
|
|
176
|
+
};
|
|
177
|
+
return (_jsxs(Box, { flexDirection: "column", height: terminalHeight, children: [phase === 'pulse' && (_jsx(Box, { position: "absolute", flexDirection: "column", children: renderWave() })), _jsx(Box, { flexDirection: "column", paddingTop: topPadding, paddingLeft: HEADER_PADDING_LEFT, children: renderedTumbleweed.map((line, i) => (_jsx(Text, { color: TUMBLEWEED_COLOR, bold: true, children: line }, i))) })] }));
|
|
178
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { render } from 'ink';
|
|
4
|
-
import { execaSync } from 'execa';
|
|
5
4
|
import { App } from './app.js';
|
|
6
5
|
import { isInsideYeehawSession, yeehawSessionExists, createYeehawSession, attachToYeehaw, hasTmux, } from './lib/tmux.js';
|
|
7
6
|
import { ensureConfigDirs } from './lib/config.js';
|
|
@@ -33,11 +32,9 @@ function main() {
|
|
|
33
32
|
}
|
|
34
33
|
// We're not inside yeehaw session - need to create/attach
|
|
35
34
|
if (!yeehawSessionExists()) {
|
|
36
|
-
// Create new session with yeehaw running in window 0
|
|
35
|
+
// Create new session with yeehaw running directly in window 0
|
|
36
|
+
// (no shell intermediary - cleaner startup)
|
|
37
37
|
createYeehawSession();
|
|
38
|
-
// Now we need to run yeehaw inside window 0
|
|
39
|
-
// Send the command to the window
|
|
40
|
-
execaSync('tmux', ['send-keys', '-t', 'yeehaw:0', 'yeehaw', 'Enter']);
|
|
41
38
|
}
|
|
42
39
|
// Attach to the yeehaw session
|
|
43
40
|
// This will exec into tmux and not return
|
package/dist/lib/tmux-config.js
CHANGED
|
@@ -11,6 +11,17 @@ export function generateTmuxConfig() {
|
|
|
11
11
|
set -g mouse on
|
|
12
12
|
set -g history-limit 50000
|
|
13
13
|
|
|
14
|
+
# macOS clipboard support
|
|
15
|
+
# Enable copying to system clipboard when selecting with mouse
|
|
16
|
+
set -g set-clipboard on
|
|
17
|
+
bind-key -T copy-mode MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"
|
|
18
|
+
bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"
|
|
19
|
+
# Also support keyboard-based copy (Enter key in copy mode)
|
|
20
|
+
bind-key -T copy-mode Enter send-keys -X copy-pipe-and-cancel "pbcopy"
|
|
21
|
+
bind-key -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "pbcopy"
|
|
22
|
+
# Use y to yank in vi mode
|
|
23
|
+
bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "pbcopy"
|
|
24
|
+
|
|
14
25
|
# Yeehaw keybindings
|
|
15
26
|
bind-key -n C-y select-window -t :0 # Return to dashboard
|
|
16
27
|
bind-key -n C-h previous-window # Go left one window
|
package/dist/lib/tmux.d.ts
CHANGED
|
@@ -12,6 +12,11 @@ export declare function isInsideYeehawSession(): boolean;
|
|
|
12
12
|
export declare function yeehawSessionExists(): boolean;
|
|
13
13
|
export declare function createYeehawSession(): void;
|
|
14
14
|
export declare function setupStatusBarHooks(): void;
|
|
15
|
+
/**
|
|
16
|
+
* Force check and correct the status bar visibility based on current window.
|
|
17
|
+
* Call this when you suspect the status bar might be in the wrong state.
|
|
18
|
+
*/
|
|
19
|
+
export declare function ensureCorrectStatusBar(): void;
|
|
15
20
|
export declare function attachToYeehaw(): void;
|
|
16
21
|
export declare function createClaudeWindow(workingDir: string, windowName: string): number;
|
|
17
22
|
export declare function createShellWindow(workingDir: string, windowName: string, shell?: string): number;
|
package/dist/lib/tmux.js
CHANGED
|
@@ -8,6 +8,8 @@ import { shellEscape } from './shell.js';
|
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
9
|
const __dirname = dirname(__filename);
|
|
10
10
|
const MCP_SERVER_PATH = join(__dirname, '..', 'mcp-server.js');
|
|
11
|
+
// Get the path to the bundled Claude plugin (at package root, sibling to dist/)
|
|
12
|
+
const CLAUDE_PLUGIN_PATH = join(__dirname, '..', '..', 'claude-plugin');
|
|
11
13
|
export const YEEHAW_SESSION = 'yeehaw';
|
|
12
14
|
// Remote mode state tracking
|
|
13
15
|
let remoteWindowIndex = null;
|
|
@@ -47,12 +49,14 @@ export function yeehawSessionExists() {
|
|
|
47
49
|
export function createYeehawSession() {
|
|
48
50
|
// Write the tmux config
|
|
49
51
|
writeTmuxConfig();
|
|
50
|
-
// Create the session with window 0 named "yeehaw"
|
|
52
|
+
// Create the session with window 0 named "yeehaw", running yeehaw directly
|
|
53
|
+
// This avoids the visible shell spawn - yeehaw runs immediately in the session
|
|
51
54
|
execaSync('tmux', [
|
|
52
55
|
'new-session',
|
|
53
56
|
'-d',
|
|
54
57
|
'-s', YEEHAW_SESSION,
|
|
55
58
|
'-n', 'yeehaw',
|
|
59
|
+
'yeehaw', // Run yeehaw directly instead of spawning a shell first
|
|
56
60
|
]);
|
|
57
61
|
// Source the config
|
|
58
62
|
execaSync('tmux', ['source-file', TMUX_CONFIG_PATH]);
|
|
@@ -61,6 +65,7 @@ export function createYeehawSession() {
|
|
|
61
65
|
}
|
|
62
66
|
export function setupStatusBarHooks() {
|
|
63
67
|
// Hide status bar when in window 0, show in other windows
|
|
68
|
+
const statusCheck = 'if-shell -F "#{==:#{window_index},0}" "set status off" "set status on"';
|
|
64
69
|
try {
|
|
65
70
|
// Start with status off (we begin in window 0)
|
|
66
71
|
execaSync('tmux', ['set', '-t', YEEHAW_SESSION, 'status', 'off']);
|
|
@@ -68,15 +73,56 @@ export function setupStatusBarHooks() {
|
|
|
68
73
|
execaSync('tmux', [
|
|
69
74
|
'set-hook', '-t', YEEHAW_SESSION,
|
|
70
75
|
'after-select-window',
|
|
71
|
-
|
|
76
|
+
statusCheck
|
|
77
|
+
]);
|
|
78
|
+
// Hook for when a window is killed (we might land back on window 0)
|
|
79
|
+
execaSync('tmux', [
|
|
80
|
+
'set-hook', '-t', YEEHAW_SESSION,
|
|
81
|
+
'window-unlinked',
|
|
82
|
+
statusCheck
|
|
83
|
+
]);
|
|
84
|
+
// Hook for when pane focus changes (covers edge cases)
|
|
85
|
+
execaSync('tmux', [
|
|
86
|
+
'set-hook', '-t', YEEHAW_SESSION,
|
|
87
|
+
'pane-focus-in',
|
|
88
|
+
statusCheck
|
|
89
|
+
]);
|
|
90
|
+
// Hook for client attachment (ensure status is correct when reattaching)
|
|
91
|
+
execaSync('tmux', [
|
|
92
|
+
'set-hook', '-t', YEEHAW_SESSION,
|
|
93
|
+
'client-attached',
|
|
94
|
+
statusCheck
|
|
72
95
|
]);
|
|
73
96
|
}
|
|
74
97
|
catch {
|
|
75
98
|
// Hooks might fail on older tmux versions, not critical
|
|
76
99
|
}
|
|
77
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Force check and correct the status bar visibility based on current window.
|
|
103
|
+
* Call this when you suspect the status bar might be in the wrong state.
|
|
104
|
+
*/
|
|
105
|
+
export function ensureCorrectStatusBar() {
|
|
106
|
+
try {
|
|
107
|
+
// Get current window index
|
|
108
|
+
const result = execaSync('tmux', ['display-message', '-p', '#{window_index}']);
|
|
109
|
+
const windowIndex = parseInt(result.stdout.trim(), 10);
|
|
110
|
+
// Set status based on window
|
|
111
|
+
if (windowIndex === 0) {
|
|
112
|
+
execaSync('tmux', ['set', '-t', YEEHAW_SESSION, 'status', 'off']);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
execaSync('tmux', ['set', '-t', YEEHAW_SESSION, 'status', 'on']);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Ignore errors
|
|
120
|
+
}
|
|
121
|
+
}
|
|
78
122
|
export function attachToYeehaw() {
|
|
79
|
-
//
|
|
123
|
+
// Attach to the existing yeehaw session
|
|
124
|
+
// Note: Multiple terminals attaching to the same session will share the same view
|
|
125
|
+
// (this is a tmux limitation - they'll see the same window selections)
|
|
80
126
|
spawnSync('tmux', ['attach-session', '-t', YEEHAW_SESSION], {
|
|
81
127
|
stdio: 'inherit',
|
|
82
128
|
});
|
|
@@ -115,7 +161,8 @@ export function createClaudeWindow(workingDir, windowName) {
|
|
|
115
161
|
const allowedTools = YEEHAW_MCP_TOOLS.join(',');
|
|
116
162
|
// Create new window running claude with yeehaw MCP server (-a appends after current window)
|
|
117
163
|
// Use shell escaping to safely handle special characters in JSON
|
|
118
|
-
|
|
164
|
+
// Include the bundled plugin directory for Yeehaw-specific skills
|
|
165
|
+
const claudeCmd = `claude --mcp-config ${shellEscape(mcpConfig)} --allowedTools ${shellEscape(allowedTools)} --plugin-dir ${shellEscape(CLAUDE_PLUGIN_PATH)}`;
|
|
119
166
|
execaSync('tmux', [
|
|
120
167
|
'new-window',
|
|
121
168
|
'-a',
|
|
@@ -130,15 +177,18 @@ export function createClaudeWindow(workingDir, windowName) {
|
|
|
130
177
|
]);
|
|
131
178
|
return parseInt(result.stdout.trim(), 10);
|
|
132
179
|
}
|
|
133
|
-
export function createShellWindow(workingDir, windowName, shell
|
|
134
|
-
//
|
|
180
|
+
export function createShellWindow(workingDir, windowName, shell) {
|
|
181
|
+
// Use the user's configured shell from $SHELL, fallback to /bin/bash
|
|
182
|
+
const userShell = shell || process.env.SHELL || '/bin/bash';
|
|
183
|
+
// Create new window running shell as a login shell (-l) so it loads .bashrc/.bash_profile/.zshrc etc.
|
|
184
|
+
// This ensures PS1 and other environment customizations are loaded
|
|
135
185
|
execaSync('tmux', [
|
|
136
186
|
'new-window',
|
|
137
187
|
'-a',
|
|
138
188
|
'-t', YEEHAW_SESSION,
|
|
139
189
|
'-n', windowName,
|
|
140
190
|
'-c', workingDir,
|
|
141
|
-
|
|
191
|
+
`${userShell} -l`,
|
|
142
192
|
]);
|
|
143
193
|
// Get the window index we just created (new window is now current)
|
|
144
194
|
const result = execaSync('tmux', [
|
|
@@ -3,6 +3,14 @@ export interface UpdateInfo {
|
|
|
3
3
|
currentVersion: string;
|
|
4
4
|
latestVersion: string;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Get version information synchronously using cached data.
|
|
8
|
+
* Safe to call from React components.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getVersionInfo(): {
|
|
11
|
+
current: string;
|
|
12
|
+
latest: string | null;
|
|
13
|
+
};
|
|
6
14
|
/**
|
|
7
15
|
* Check for updates. Returns immediately with cached data if available,
|
|
8
16
|
* otherwise fetches from npm (with 5s timeout).
|
package/dist/lib/update-check.js
CHANGED
|
@@ -76,6 +76,18 @@ function compareVersions(current, latest) {
|
|
|
76
76
|
return -1;
|
|
77
77
|
return 0;
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Get version information synchronously using cached data.
|
|
81
|
+
* Safe to call from React components.
|
|
82
|
+
*/
|
|
83
|
+
export function getVersionInfo() {
|
|
84
|
+
const current = getCurrentVersion();
|
|
85
|
+
const cached = readCache();
|
|
86
|
+
return {
|
|
87
|
+
current,
|
|
88
|
+
latest: cached?.latestVersion || null,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
79
91
|
/**
|
|
80
92
|
* Check for updates. Returns immediately with cached data if available,
|
|
81
93
|
* otherwise fetches from npm (with 5s timeout).
|
package/dist/mcp-server.js
CHANGED
|
File without changes
|
package/dist/types.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface Project {
|
|
|
20
20
|
path: string;
|
|
21
21
|
summary?: string;
|
|
22
22
|
color?: string;
|
|
23
|
+
gradientSpread?: number;
|
|
24
|
+
gradientInverted?: boolean;
|
|
23
25
|
livestock?: Livestock[];
|
|
24
26
|
wiki?: WikiSection[];
|
|
25
27
|
}
|
|
@@ -80,10 +82,14 @@ export type AppView = {
|
|
|
80
82
|
type: 'livestock';
|
|
81
83
|
project: Project;
|
|
82
84
|
livestock: Livestock;
|
|
85
|
+
source: 'project' | 'barn';
|
|
86
|
+
sourceBarn?: Barn;
|
|
83
87
|
} | {
|
|
84
88
|
type: 'logs';
|
|
85
89
|
project: Project;
|
|
86
90
|
livestock: Livestock;
|
|
91
|
+
source: 'project' | 'barn';
|
|
92
|
+
sourceBarn?: Barn;
|
|
87
93
|
} | {
|
|
88
94
|
type: 'night-sky';
|
|
89
95
|
};
|
|
@@ -82,10 +82,6 @@ export function BarnContext({ barn, livestock, projects, windows, onBack, onSshT
|
|
|
82
82
|
}
|
|
83
83
|
if (mode !== 'normal')
|
|
84
84
|
return;
|
|
85
|
-
if (input === 'q') {
|
|
86
|
-
onBack();
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
85
|
if (key.tab) {
|
|
90
86
|
setFocusedPanel((p) => (p === 'livestock' ? 'critters' : 'livestock'));
|
|
91
87
|
return;
|
|
@@ -4,6 +4,10 @@ interface GlobalDashboardProps {
|
|
|
4
4
|
projects: Project[];
|
|
5
5
|
barns: Barn[];
|
|
6
6
|
windows: TmuxWindow[];
|
|
7
|
+
versionInfo?: {
|
|
8
|
+
current: string;
|
|
9
|
+
latest: string | null;
|
|
10
|
+
};
|
|
7
11
|
onSelectProject: (project: Project) => void;
|
|
8
12
|
onSelectBarn: (barn: Barn) => void;
|
|
9
13
|
onSelectWindow: (window: TmuxWindow) => void;
|
|
@@ -12,5 +16,5 @@ interface GlobalDashboardProps {
|
|
|
12
16
|
onCreateBarn: (barn: Barn) => void;
|
|
13
17
|
onSshToBarn: (barn: Barn) => void;
|
|
14
18
|
}
|
|
15
|
-
export declare function GlobalDashboard({ projects, barns, windows, onSelectProject, onSelectBarn, onSelectWindow, onNewClaude, onCreateProject, onCreateBarn, onSshToBarn, }: GlobalDashboardProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export declare function GlobalDashboard({ projects, barns, windows, versionInfo, onSelectProject, onSelectBarn, onSelectWindow, onNewClaude, onCreateProject, onCreateBarn, onSshToBarn, }: GlobalDashboardProps): import("react/jsx-runtime").JSX.Element;
|
|
16
20
|
export {};
|
|
@@ -8,10 +8,11 @@ import { List } from '../components/List.js';
|
|
|
8
8
|
import { PathInput } from '../components/PathInput.js';
|
|
9
9
|
import { parseSshConfig } from '../lib/ssh.js';
|
|
10
10
|
import { getWindowStatus } from '../lib/tmux.js';
|
|
11
|
+
import { isLocalBarn } from '../lib/config.js';
|
|
11
12
|
function countSessionsForProject(projectName, windows) {
|
|
12
13
|
return windows.filter((w) => w.name.startsWith(projectName)).length;
|
|
13
14
|
}
|
|
14
|
-
export function GlobalDashboard({ projects, barns, windows, onSelectProject, onSelectBarn, onSelectWindow, onNewClaude, onCreateProject, onCreateBarn, onSshToBarn, }) {
|
|
15
|
+
export function GlobalDashboard({ projects, barns, windows, versionInfo, onSelectProject, onSelectBarn, onSelectWindow, onNewClaude, onCreateProject, onCreateBarn, onSshToBarn, }) {
|
|
15
16
|
const [focusedPanel, setFocusedPanel] = useState('projects');
|
|
16
17
|
const [mode, setMode] = useState('normal');
|
|
17
18
|
// New project form state
|
|
@@ -170,25 +171,51 @@ export function GlobalDashboard({ projects, barns, windows, onSelectProject, onS
|
|
|
170
171
|
meta: sessionCount > 0 ? `${sessionCount} session${sessionCount > 1 ? 's' : ''}` : undefined,
|
|
171
172
|
};
|
|
172
173
|
});
|
|
174
|
+
// Parse window name to show clearer labels
|
|
175
|
+
const formatSessionLabel = (name) => {
|
|
176
|
+
// Remote yeehaw connection
|
|
177
|
+
if (name.startsWith('remote:')) {
|
|
178
|
+
return { label: name.replace('remote:', ''), typeHint: 'remote' };
|
|
179
|
+
}
|
|
180
|
+
// Barn shell session
|
|
181
|
+
if (name.startsWith('barn-')) {
|
|
182
|
+
return { label: name.replace('barn-', ''), typeHint: 'barn' };
|
|
183
|
+
}
|
|
184
|
+
// Claude session
|
|
185
|
+
if (name.endsWith('-claude')) {
|
|
186
|
+
return { label: name.replace('-claude', ''), typeHint: 'claude' };
|
|
187
|
+
}
|
|
188
|
+
// Livestock session (project-livestock format)
|
|
189
|
+
const parts = name.split('-');
|
|
190
|
+
if (parts.length >= 2) {
|
|
191
|
+
const projectName = parts.slice(0, -1).join('-');
|
|
192
|
+
const livestockName = parts[parts.length - 1];
|
|
193
|
+
return { label: `${projectName} · ${livestockName}`, typeHint: 'shell' };
|
|
194
|
+
}
|
|
195
|
+
return { label: name, typeHint: '' };
|
|
196
|
+
};
|
|
173
197
|
// Use display numbers (1-9) instead of window index
|
|
174
|
-
const sessionItems = sessionWindows.map((w, i) =>
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
198
|
+
const sessionItems = sessionWindows.map((w, i) => {
|
|
199
|
+
const { label, typeHint } = formatSessionLabel(w.name);
|
|
200
|
+
return {
|
|
201
|
+
id: String(w.index),
|
|
202
|
+
label: `[${i + 1}] ${label}`,
|
|
203
|
+
status: w.active ? 'active' : 'inactive',
|
|
204
|
+
meta: typeHint ? `${typeHint} · ${getWindowStatus(w)}` : getWindowStatus(w),
|
|
205
|
+
};
|
|
206
|
+
});
|
|
180
207
|
const barnItems = barns.map((b) => ({
|
|
181
208
|
id: b.name,
|
|
182
|
-
label: b.name,
|
|
209
|
+
label: isLocalBarn(b) ? 'local' : b.name,
|
|
183
210
|
status: 'active',
|
|
184
|
-
meta: `${b.user}@${b.host}`,
|
|
211
|
+
meta: isLocalBarn(b) ? 'this machine' : `${b.user}@${b.host}`,
|
|
185
212
|
}));
|
|
186
213
|
// New project modals
|
|
187
214
|
if (mode === 'new-project-name') {
|
|
188
|
-
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Create New Project" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Name: " }), _jsx(TextInput, { value: newProjectName, onChange: setNewProjectName, onSubmit: handleProjectNameSubmit })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter to continue, Esc to cancel" }) })] })] }));
|
|
215
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW", versionInfo: versionInfo }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Create New Project" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Name: " }), _jsx(TextInput, { value: newProjectName, onChange: setNewProjectName, onSubmit: handleProjectNameSubmit })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter to continue, Esc to cancel" }) })] })] }));
|
|
189
216
|
}
|
|
190
217
|
if (mode === 'new-project-path') {
|
|
191
|
-
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["Create New Project: ", newProjectName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Path: " }), _jsx(PathInput, { value: newProjectPath, onChange: setNewProjectPath, onSubmit: handleProjectPathSubmit })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Tab to autocomplete, Enter to create, Esc to cancel" }) })] })] }));
|
|
218
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW", versionInfo: versionInfo }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["Create New Project: ", newProjectName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Path: " }), _jsx(PathInput, { value: newProjectPath, onChange: setNewProjectPath, onSubmit: handleProjectPathSubmit })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Tab to autocomplete, Enter to create, Esc to cancel" }) })] })] }));
|
|
192
219
|
}
|
|
193
220
|
// SSH host selection for new barn
|
|
194
221
|
if (mode === 'new-barn-select-ssh') {
|
|
@@ -201,7 +228,7 @@ export function GlobalDashboard({ projects, barns, windows, onSelectProject, onS
|
|
|
201
228
|
meta: h.hostname ? `${h.user || 'root'}@${h.hostname}` : undefined,
|
|
202
229
|
})),
|
|
203
230
|
];
|
|
204
|
-
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "green", children: "Add New Barn" }), _jsx(Text, { dimColor: true, children: "Select from SSH config or enter manually" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(List, { items: sshHostItems, focused: true, onSelect: (item) => {
|
|
231
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW", versionInfo: versionInfo }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "green", children: "Add New Barn" }), _jsx(Text, { dimColor: true, children: "Select from SSH config or enter manually" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(List, { items: sshHostItems, focused: true, onSelect: (item) => {
|
|
205
232
|
if (item.id === '__manual__') {
|
|
206
233
|
setMode('new-barn-name');
|
|
207
234
|
}
|
|
@@ -214,40 +241,40 @@ export function GlobalDashboard({ projects, barns, windows, onSelectProject, onS
|
|
|
214
241
|
}
|
|
215
242
|
// New barn modals
|
|
216
243
|
if (mode === 'new-barn-name') {
|
|
217
|
-
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "green", children: "Add New Barn" }), _jsx(Text, { dimColor: true, children: "A barn is a server you manage" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Name: " }), _jsx(TextInput, { value: newBarnName, onChange: setNewBarnName, onSubmit: handleBarnNameSubmit, placeholder: "my-server" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel" }) })] })] }));
|
|
244
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW", versionInfo: versionInfo }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "green", children: "Add New Barn" }), _jsx(Text, { dimColor: true, children: "A barn is a server you manage" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Name: " }), _jsx(TextInput, { value: newBarnName, onChange: setNewBarnName, onSubmit: handleBarnNameSubmit, placeholder: "my-server" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel" }) })] })] }));
|
|
218
245
|
}
|
|
219
246
|
if (mode === 'new-barn-host') {
|
|
220
|
-
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add New Barn: ", newBarnName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Host: " }), _jsx(TextInput, { value: newBarnHost, onChange: setNewBarnHost, onSubmit: handleBarnHostSubmit, placeholder: "192.168.1.100 or server.example.com" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel" }) })] })] }));
|
|
247
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW", versionInfo: versionInfo }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add New Barn: ", newBarnName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Host: " }), _jsx(TextInput, { value: newBarnHost, onChange: setNewBarnHost, onSubmit: handleBarnHostSubmit, placeholder: "192.168.1.100 or server.example.com" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel" }) })] })] }));
|
|
221
248
|
}
|
|
222
249
|
if (mode === 'new-barn-user') {
|
|
223
|
-
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add New Barn: ", newBarnName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "SSH User: " }), _jsx(TextInput, { value: newBarnUser, onChange: setNewBarnUser, onSubmit: handleBarnUserSubmit, placeholder: "root" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel" }) })] })] }));
|
|
250
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW", versionInfo: versionInfo }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add New Barn: ", newBarnName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "SSH User: " }), _jsx(TextInput, { value: newBarnUser, onChange: setNewBarnUser, onSubmit: handleBarnUserSubmit, placeholder: "root" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel" }) })] })] }));
|
|
224
251
|
}
|
|
225
252
|
if (mode === 'new-barn-port') {
|
|
226
|
-
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add New Barn: ", newBarnName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "SSH Port: " }), _jsx(TextInput, { value: newBarnPort, onChange: setNewBarnPort, onSubmit: handleBarnPortSubmit, placeholder: "22" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field (leave blank for 22), Esc: cancel" }) })] })] }));
|
|
253
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW", versionInfo: versionInfo }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add New Barn: ", newBarnName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "SSH Port: " }), _jsx(TextInput, { value: newBarnPort, onChange: setNewBarnPort, onSubmit: handleBarnPortSubmit, placeholder: "22" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field (leave blank for 22), Esc: cancel" }) })] })] }));
|
|
227
254
|
}
|
|
228
255
|
if (mode === 'new-barn-key') {
|
|
229
|
-
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add New Barn: ", newBarnName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "SSH Key Path: " }), _jsx(PathInput, { value: newBarnKey, onChange: setNewBarnKey, onSubmit: handleBarnKeySubmit })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Tab: autocomplete, Enter: create barn, Esc: cancel" }) })] })] }));
|
|
256
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW", versionInfo: versionInfo }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add New Barn: ", newBarnName] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "SSH Key Path: " }), _jsx(PathInput, { value: newBarnKey, onChange: setNewBarnKey, onSubmit: handleBarnKeySubmit })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Tab: autocomplete, Enter: create barn, Esc: cancel" }) })] })] }));
|
|
230
257
|
}
|
|
231
258
|
// Panel-specific hints (page-level hotkeys like c are in BottomBar)
|
|
232
259
|
const projectHints = '[n] new';
|
|
233
260
|
const sessionHints = '1-9 switch';
|
|
234
261
|
const barnHints = '[n] new [s] shell';
|
|
235
|
-
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW" }), _jsxs(Box, { flexGrow: 1, marginY: 1, paddingX: 1, gap: 2, children: [_jsx(Panel, { title: "Projects", focused: focusedPanel === 'projects',
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
262
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: "YEEHAW", versionInfo: versionInfo }), _jsxs(Box, { flexGrow: 1, marginY: 1, paddingX: 1, gap: 2, children: [_jsxs(Box, { flexDirection: "column", width: "40%", gap: 1, children: [_jsx(Panel, { title: "Projects", focused: focusedPanel === 'projects', hints: projectHints, children: projectItems.length > 0 ? (_jsx(List, { items: projectItems, focused: focusedPanel === 'projects', onSelect: (item) => {
|
|
263
|
+
const project = projects.find((p) => p.name === item.id);
|
|
264
|
+
if (project)
|
|
265
|
+
onSelectProject(project);
|
|
266
|
+
} })) : (_jsx(Text, { dimColor: true, children: "No projects yet" })) }), _jsx(Box, { flexGrow: 1, width: "100%", children: _jsx(Panel, { title: "Barns", focused: focusedPanel === 'barns', width: "100%", hints: barnHints, children: barnItems.length > 0 ? (_jsx(Box, { flexDirection: "column", children: _jsx(List, { items: barnItems, focused: focusedPanel === 'barns', onSelect: (item) => {
|
|
267
|
+
const barn = barns.find((b) => b.name === item.id);
|
|
268
|
+
if (barn)
|
|
269
|
+
onSelectBarn(barn);
|
|
270
|
+
}, onAction: (item) => {
|
|
271
|
+
// 's' key to SSH directly
|
|
272
|
+
const barn = barns.find((b) => b.name === item.id);
|
|
273
|
+
if (barn)
|
|
274
|
+
onSshToBarn(barn);
|
|
275
|
+
} }) })) : (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "No barns configured" }), _jsx(Text, { dimColor: true, italic: true, children: "Barns are servers you manage" })] })) }) })] }), _jsx(Panel, { title: "Sessions", focused: focusedPanel === 'sessions', width: "60%", hints: sessionHints, children: sessionItems.length > 0 ? (_jsx(List, { items: sessionItems, focused: focusedPanel === 'sessions', onSelect: (item) => {
|
|
240
276
|
const window = sessionWindows.find((w) => String(w.index) === item.id);
|
|
241
277
|
if (window)
|
|
242
278
|
onSelectWindow(window);
|
|
243
|
-
} })) : (_jsx(Text, { dimColor: true, children: "No active sessions" })) })] })
|
|
244
|
-
const barn = barns.find((b) => b.name === item.id);
|
|
245
|
-
if (barn)
|
|
246
|
-
onSelectBarn(barn);
|
|
247
|
-
}, onAction: (item) => {
|
|
248
|
-
// 's' key to SSH directly
|
|
249
|
-
const barn = barns.find((b) => b.name === item.id);
|
|
250
|
-
if (barn)
|
|
251
|
-
onSshToBarn(barn);
|
|
252
|
-
} }) })) : (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "No barns configured" }), _jsx(Text, { dimColor: true, italic: true, children: "Barns are servers you manage" })] })) }) })] }));
|
|
279
|
+
} })) : (_jsx(Text, { dimColor: true, children: "No active sessions" })) })] })] }));
|
|
253
280
|
}
|
package/dist/views/IssuesView.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type TmuxWindow } from '../lib/tmux.js';
|
|
2
|
+
import type { Project, Livestock, Barn } from '../types.js';
|
|
2
3
|
interface LivestockDetailViewProps {
|
|
3
4
|
project: Project;
|
|
4
5
|
livestock: Livestock;
|
|
6
|
+
source: 'project' | 'barn';
|
|
7
|
+
sourceBarn?: Barn;
|
|
8
|
+
windows: TmuxWindow[];
|
|
5
9
|
onBack: () => void;
|
|
6
10
|
onOpenLogs: () => void;
|
|
7
11
|
onOpenSession: () => void;
|
|
8
|
-
|
|
12
|
+
onSelectWindow: (window: TmuxWindow) => void;
|
|
13
|
+
onUpdateLivestock: (originalLivestock: Livestock, updatedLivestock: Livestock) => void;
|
|
9
14
|
}
|
|
10
|
-
export declare function LivestockDetailView({ project, livestock, onBack, onOpenLogs, onOpenSession, onUpdateLivestock, }: LivestockDetailViewProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export declare function LivestockDetailView({ project, livestock, source, sourceBarn, windows, onBack, onOpenLogs, onOpenSession, onSelectWindow, onUpdateLivestock, }: LivestockDetailViewProps): import("react/jsx-runtime").JSX.Element;
|
|
11
16
|
export {};
|