@colmbus72/yeehaw 0.1.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/LICENSE +21 -0
- package/README.md +124 -0
- package/dist/app.d.ts +1 -0
- package/dist/app.js +414 -0
- package/dist/components/BarnHeader.d.ts +6 -0
- package/dist/components/BarnHeader.js +21 -0
- package/dist/components/BottomBar.d.ts +16 -0
- package/dist/components/BottomBar.js +7 -0
- package/dist/components/Header.d.ts +8 -0
- package/dist/components/Header.js +83 -0
- package/dist/components/HelpOverlay.d.ts +7 -0
- package/dist/components/HelpOverlay.js +17 -0
- package/dist/components/List.d.ts +17 -0
- package/dist/components/List.js +53 -0
- package/dist/components/Markdown.d.ts +8 -0
- package/dist/components/Markdown.js +23 -0
- package/dist/components/Panel.d.ts +10 -0
- package/dist/components/Panel.js +5 -0
- package/dist/components/PathInput.d.ts +9 -0
- package/dist/components/PathInput.js +141 -0
- package/dist/components/ScrollableMarkdown.d.ts +11 -0
- package/dist/components/ScrollableMarkdown.js +56 -0
- package/dist/components/StatusBar.d.ts +5 -0
- package/dist/components/StatusBar.js +20 -0
- package/dist/components/TextArea.d.ts +17 -0
- package/dist/components/TextArea.js +140 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.js +5 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/useConfig.d.ts +11 -0
- package/dist/hooks/useConfig.js +36 -0
- package/dist/hooks/useRemoteYeehaw.d.ts +13 -0
- package/dist/hooks/useRemoteYeehaw.js +49 -0
- package/dist/hooks/useSessions.d.ts +11 -0
- package/dist/hooks/useSessions.js +46 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +34 -0
- package/dist/lib/config.d.ts +27 -0
- package/dist/lib/config.js +150 -0
- package/dist/lib/detection.d.ts +16 -0
- package/dist/lib/detection.js +41 -0
- package/dist/lib/editor.d.ts +5 -0
- package/dist/lib/editor.js +35 -0
- package/dist/lib/errors.d.ts +28 -0
- package/dist/lib/errors.js +48 -0
- package/dist/lib/git.d.ts +11 -0
- package/dist/lib/git.js +73 -0
- package/dist/lib/github.d.ts +43 -0
- package/dist/lib/github.js +111 -0
- package/dist/lib/hotkeys.d.ts +27 -0
- package/dist/lib/hotkeys.js +92 -0
- package/dist/lib/index.d.ts +10 -0
- package/dist/lib/index.js +10 -0
- package/dist/lib/livestock.d.ts +51 -0
- package/dist/lib/livestock.js +233 -0
- package/dist/lib/mcp-validation.d.ts +33 -0
- package/dist/lib/mcp-validation.js +62 -0
- package/dist/lib/paths.d.ts +8 -0
- package/dist/lib/paths.js +28 -0
- package/dist/lib/shell.d.ts +34 -0
- package/dist/lib/shell.js +61 -0
- package/dist/lib/ssh.d.ts +15 -0
- package/dist/lib/ssh.js +77 -0
- package/dist/lib/tmux-config.d.ts +3 -0
- package/dist/lib/tmux-config.js +42 -0
- package/dist/lib/tmux.d.ts +32 -0
- package/dist/lib/tmux.js +397 -0
- package/dist/mcp-server.d.ts +23 -0
- package/dist/mcp-server.js +825 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.js +2 -0
- package/dist/views/BarnContext.d.ts +22 -0
- package/dist/views/BarnContext.js +252 -0
- package/dist/views/GlobalDashboard.d.ts +16 -0
- package/dist/views/GlobalDashboard.js +253 -0
- package/dist/views/Home.d.ts +11 -0
- package/dist/views/Home.js +27 -0
- package/dist/views/IssuesView.d.ts +7 -0
- package/dist/views/IssuesView.js +157 -0
- package/dist/views/LivestockDetailView.d.ts +11 -0
- package/dist/views/LivestockDetailView.js +140 -0
- package/dist/views/LogsView.d.ts +8 -0
- package/dist/views/LogsView.js +84 -0
- package/dist/views/NightSkyView.d.ts +5 -0
- package/dist/views/NightSkyView.js +441 -0
- package/dist/views/ProjectContext.d.ts +18 -0
- package/dist/views/ProjectContext.js +333 -0
- package/dist/views/Projects.d.ts +8 -0
- package/dist/views/Projects.js +20 -0
- package/dist/views/WikiView.d.ts +8 -0
- package/dist/views/WikiView.js +138 -0
- package/dist/views/index.d.ts +2 -0
- package/dist/views/index.js +2 -0
- package/package.json +65 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export interface Config {
|
|
2
|
+
version: number;
|
|
3
|
+
default_project: string | null;
|
|
4
|
+
editor: string;
|
|
5
|
+
theme: 'dark' | 'light';
|
|
6
|
+
show_activity: boolean;
|
|
7
|
+
claude: ClaudeConfig;
|
|
8
|
+
tmux: TmuxConfig;
|
|
9
|
+
}
|
|
10
|
+
export interface ClaudeConfig {
|
|
11
|
+
model: string;
|
|
12
|
+
auto_attach: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface TmuxConfig {
|
|
15
|
+
session_prefix: string;
|
|
16
|
+
default_shell: string;
|
|
17
|
+
}
|
|
18
|
+
export interface Project {
|
|
19
|
+
name: string;
|
|
20
|
+
path: string;
|
|
21
|
+
summary?: string;
|
|
22
|
+
color?: string;
|
|
23
|
+
livestock?: Livestock[];
|
|
24
|
+
wiki?: WikiSection[];
|
|
25
|
+
}
|
|
26
|
+
export interface WikiSection {
|
|
27
|
+
title: string;
|
|
28
|
+
content: string;
|
|
29
|
+
}
|
|
30
|
+
export interface Livestock {
|
|
31
|
+
name: string;
|
|
32
|
+
path: string;
|
|
33
|
+
barn?: string;
|
|
34
|
+
repo?: string;
|
|
35
|
+
branch?: string;
|
|
36
|
+
log_path?: string;
|
|
37
|
+
env_path?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface Critter {
|
|
40
|
+
name: string;
|
|
41
|
+
type: string;
|
|
42
|
+
status?: 'running' | 'stopped' | 'unknown';
|
|
43
|
+
}
|
|
44
|
+
export interface Barn {
|
|
45
|
+
name: string;
|
|
46
|
+
host?: string;
|
|
47
|
+
user?: string;
|
|
48
|
+
port?: number;
|
|
49
|
+
identity_file?: string;
|
|
50
|
+
critters?: Critter[];
|
|
51
|
+
}
|
|
52
|
+
export interface Session {
|
|
53
|
+
id: string;
|
|
54
|
+
type: 'claude' | 'shell';
|
|
55
|
+
project: string | null;
|
|
56
|
+
livestock: string | null;
|
|
57
|
+
barn: string | null;
|
|
58
|
+
tmux_session: string;
|
|
59
|
+
tmux_window: number | null;
|
|
60
|
+
started_at: string;
|
|
61
|
+
working_directory: string;
|
|
62
|
+
notes: string;
|
|
63
|
+
status: 'active' | 'detached' | 'ended';
|
|
64
|
+
}
|
|
65
|
+
export type AppView = {
|
|
66
|
+
type: 'global';
|
|
67
|
+
} | {
|
|
68
|
+
type: 'project';
|
|
69
|
+
project: Project;
|
|
70
|
+
} | {
|
|
71
|
+
type: 'barn';
|
|
72
|
+
barn: Barn;
|
|
73
|
+
} | {
|
|
74
|
+
type: 'wiki';
|
|
75
|
+
project: Project;
|
|
76
|
+
} | {
|
|
77
|
+
type: 'issues';
|
|
78
|
+
project: Project;
|
|
79
|
+
} | {
|
|
80
|
+
type: 'livestock';
|
|
81
|
+
project: Project;
|
|
82
|
+
livestock: Livestock;
|
|
83
|
+
} | {
|
|
84
|
+
type: 'logs';
|
|
85
|
+
project: Project;
|
|
86
|
+
livestock: Livestock;
|
|
87
|
+
} | {
|
|
88
|
+
type: 'night-sky';
|
|
89
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Barn, Project, Livestock } from '../types.js';
|
|
2
|
+
import type { TmuxWindow } from '../lib/tmux.js';
|
|
3
|
+
interface LivestockWithProject {
|
|
4
|
+
project: Project;
|
|
5
|
+
livestock: Livestock;
|
|
6
|
+
}
|
|
7
|
+
interface BarnContextProps {
|
|
8
|
+
barn: Barn;
|
|
9
|
+
livestock: LivestockWithProject[];
|
|
10
|
+
projects: Project[];
|
|
11
|
+
windows: TmuxWindow[];
|
|
12
|
+
onBack: () => void;
|
|
13
|
+
onSshToBarn: () => void;
|
|
14
|
+
onSelectLivestock: (project: Project, livestock: Livestock) => void;
|
|
15
|
+
onOpenLivestockSession: (project: Project, livestock: Livestock) => void;
|
|
16
|
+
onUpdateBarn: (barn: Barn) => void;
|
|
17
|
+
onDeleteBarn: (barnName: string) => void;
|
|
18
|
+
onAddLivestock: (project: Project, livestock: Livestock) => void;
|
|
19
|
+
onRemoveLivestock: (project: Project, livestockName: string) => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function BarnContext({ barn, livestock, projects, windows, onBack, onSshToBarn, onSelectLivestock, onOpenLivestockSession, onUpdateBarn, onDeleteBarn, onAddLivestock, onRemoveLivestock, }: BarnContextProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import TextInput from 'ink-text-input';
|
|
5
|
+
import { BarnHeader } from '../components/BarnHeader.js';
|
|
6
|
+
import { Panel } from '../components/Panel.js';
|
|
7
|
+
import { List } from '../components/List.js';
|
|
8
|
+
import { PathInput } from '../components/PathInput.js';
|
|
9
|
+
import { detectRemoteGitInfo, detectGitInfo } from '../lib/git.js';
|
|
10
|
+
import { isLocalBarn } from '../lib/config.js';
|
|
11
|
+
import { parseGitHubUrl } from '../lib/github.js';
|
|
12
|
+
export function BarnContext({ barn, livestock, projects, windows, onBack, onSshToBarn, onSelectLivestock, onOpenLivestockSession, onUpdateBarn, onDeleteBarn, onAddLivestock, onRemoveLivestock, }) {
|
|
13
|
+
const [focusedPanel, setFocusedPanel] = useState('livestock');
|
|
14
|
+
const [mode, setMode] = useState('normal');
|
|
15
|
+
// Check if this is the local barn
|
|
16
|
+
const isLocal = isLocalBarn(barn);
|
|
17
|
+
// Edit form state (only used for remote barns)
|
|
18
|
+
const [editHost, setEditHost] = useState(barn.host || '');
|
|
19
|
+
const [editUser, setEditUser] = useState(barn.user || '');
|
|
20
|
+
const [editPort, setEditPort] = useState(String(barn.port || 22));
|
|
21
|
+
// Delete confirmation
|
|
22
|
+
const [deleteConfirmInput, setDeleteConfirmInput] = useState('');
|
|
23
|
+
// Add livestock state (new flow: path → detect git → match/pick project → name)
|
|
24
|
+
const [newLivestockPath, setNewLivestockPath] = useState('');
|
|
25
|
+
const [detectedGit, setDetectedGit] = useState(null);
|
|
26
|
+
const [selectedProject, setSelectedProject] = useState(null);
|
|
27
|
+
const [newLivestockName, setNewLivestockName] = useState('');
|
|
28
|
+
// Delete livestock state
|
|
29
|
+
const [selectedLivestockIndex, setSelectedLivestockIndex] = useState(0);
|
|
30
|
+
const [deleteLivestockTarget, setDeleteLivestockTarget] = useState(null);
|
|
31
|
+
// Filter windows that are barn sessions
|
|
32
|
+
const barnWindows = windows.filter((w) => w.index > 0 && w.name.startsWith(`barn-${barn.name}`));
|
|
33
|
+
const startEdit = () => {
|
|
34
|
+
if (isLocal)
|
|
35
|
+
return; // Cannot edit local barn
|
|
36
|
+
setEditHost(barn.host || '');
|
|
37
|
+
setEditUser(barn.user || '');
|
|
38
|
+
setEditPort(String(barn.port || 22));
|
|
39
|
+
setMode('edit-host');
|
|
40
|
+
};
|
|
41
|
+
const cancelEdit = () => {
|
|
42
|
+
setMode('normal');
|
|
43
|
+
};
|
|
44
|
+
const saveAndNext = (nextMode) => {
|
|
45
|
+
if (nextMode === 'done') {
|
|
46
|
+
onUpdateBarn({
|
|
47
|
+
...barn,
|
|
48
|
+
host: editHost,
|
|
49
|
+
user: editUser,
|
|
50
|
+
port: parseInt(editPort, 10) || 22,
|
|
51
|
+
});
|
|
52
|
+
setMode('normal');
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
setMode(nextMode);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
useInput((input, key) => {
|
|
59
|
+
// Handle escape
|
|
60
|
+
if (key.escape) {
|
|
61
|
+
if (mode !== 'normal') {
|
|
62
|
+
cancelEdit();
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
onBack();
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Handle delete livestock confirmation
|
|
70
|
+
if (mode === 'delete-livestock-confirm' && deleteLivestockTarget) {
|
|
71
|
+
if (input === 'y') {
|
|
72
|
+
onRemoveLivestock(deleteLivestockTarget.project, deleteLivestockTarget.livestock.name);
|
|
73
|
+
setDeleteLivestockTarget(null);
|
|
74
|
+
setSelectedLivestockIndex(Math.max(0, selectedLivestockIndex - 1));
|
|
75
|
+
setMode('normal');
|
|
76
|
+
}
|
|
77
|
+
else if (input === 'n') {
|
|
78
|
+
setDeleteLivestockTarget(null);
|
|
79
|
+
setMode('normal');
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (mode !== 'normal')
|
|
84
|
+
return;
|
|
85
|
+
if (input === 'q') {
|
|
86
|
+
onBack();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (key.tab) {
|
|
90
|
+
setFocusedPanel((p) => (p === 'livestock' ? 'critters' : 'livestock'));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (input === 's') {
|
|
94
|
+
// Context-aware shell: livestock session if focused on livestock, otherwise SSH to barn
|
|
95
|
+
if (focusedPanel === 'livestock' && livestock.length > 0) {
|
|
96
|
+
const target = livestock[selectedLivestockIndex];
|
|
97
|
+
if (target) {
|
|
98
|
+
onOpenLivestockSession(target.project, target.livestock);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
onSshToBarn();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (input === 'e' && !isLocal) {
|
|
106
|
+
startEdit();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (input === 'D' && !isLocal) {
|
|
110
|
+
setDeleteConfirmInput('');
|
|
111
|
+
setMode('delete-barn-confirm');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (input === 'n' && focusedPanel === 'livestock') {
|
|
115
|
+
// Start add livestock flow with path first
|
|
116
|
+
setNewLivestockPath('');
|
|
117
|
+
setDetectedGit(null);
|
|
118
|
+
setSelectedProject(null);
|
|
119
|
+
setNewLivestockName('');
|
|
120
|
+
setMode('add-livestock-path');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (input === 'd' && focusedPanel === 'livestock' && livestock.length > 0) {
|
|
124
|
+
// Delete selected livestock
|
|
125
|
+
const target = livestock[selectedLivestockIndex];
|
|
126
|
+
if (target) {
|
|
127
|
+
setDeleteLivestockTarget(target);
|
|
128
|
+
setMode('delete-livestock-confirm');
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// Edit mode screens
|
|
134
|
+
if (mode === 'edit-host') {
|
|
135
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(BarnHeader, { name: barn.name, subtitle: "Editing barn" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "yellow", children: "Edit Barn" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Host: " }), _jsx(TextInput, { value: editHost, onChange: setEditHost, onSubmit: () => saveAndNext('edit-user') })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel" }) })] })] }));
|
|
136
|
+
}
|
|
137
|
+
if (mode === 'edit-user') {
|
|
138
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(BarnHeader, { name: barn.name, subtitle: "Editing barn" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["Edit Barn: ", barn.name] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "User: " }), _jsx(TextInput, { value: editUser, onChange: setEditUser, onSubmit: () => saveAndNext('edit-port') })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next field, Esc: cancel" }) })] })] }));
|
|
139
|
+
}
|
|
140
|
+
if (mode === 'edit-port') {
|
|
141
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(BarnHeader, { name: barn.name, subtitle: "Editing barn" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["Edit Barn: ", barn.name] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Port: " }), _jsx(TextInput, { value: editPort, onChange: setEditPort, onSubmit: () => saveAndNext('done') })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: save barn, Esc: cancel" }) })] })] }));
|
|
142
|
+
}
|
|
143
|
+
if (mode === 'delete-barn-confirm') {
|
|
144
|
+
const handleDeleteConfirm = () => {
|
|
145
|
+
if (deleteConfirmInput === barn.name) {
|
|
146
|
+
onDeleteBarn(barn.name);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const hasLivestock = livestock.length > 0;
|
|
150
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(BarnHeader, { name: barn.name, subtitle: "DELETE BARN" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "red", children: "\u26A0\uFE0F Delete Barn" }), hasLivestock ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "red", children: "Cannot delete barn - livestock still deployed:" }), livestock.map((l) => (_jsxs(Text, { dimColor: true, children: ["\u2022 ", l.project.name, "/", l.livestock.name] }, `${l.project.name}-${l.livestock.name}`))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Remove livestock from projects first, then delete the barn." }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "This will permanently delete the barn configuration." }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Type the barn name to confirm: " }), _jsx(Text, { bold: true, color: "yellow", children: barn.name })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Confirm: " }), _jsx(TextInput, { value: deleteConfirmInput, onChange: setDeleteConfirmInput, onSubmit: handleDeleteConfirm })] }), deleteConfirmInput && deleteConfirmInput !== barn.name && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", children: "Name does not match" }) }))] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Esc: cancel" }) })] })] }));
|
|
151
|
+
}
|
|
152
|
+
// Add livestock flow: path → detect git → match/pick project → name
|
|
153
|
+
// Step 1: Enter path (with remote tab completion)
|
|
154
|
+
if (mode === 'add-livestock-path') {
|
|
155
|
+
const handlePathSubmit = () => {
|
|
156
|
+
if (!newLivestockPath.trim())
|
|
157
|
+
return;
|
|
158
|
+
// Detect git info from path (local or remote)
|
|
159
|
+
const gitInfo = isLocal
|
|
160
|
+
? detectGitInfo(newLivestockPath)
|
|
161
|
+
: detectRemoteGitInfo(newLivestockPath, barn);
|
|
162
|
+
setDetectedGit(gitInfo);
|
|
163
|
+
// Try to match project by repo URL
|
|
164
|
+
if (gitInfo.isGitRepo && gitInfo.remoteUrl) {
|
|
165
|
+
const parsed = parseGitHubUrl(gitInfo.remoteUrl);
|
|
166
|
+
if (parsed) {
|
|
167
|
+
// Find project with matching repo in any livestock
|
|
168
|
+
const matchedProject = projects.find((p) => (p.livestock || []).some((l) => {
|
|
169
|
+
if (!l.repo)
|
|
170
|
+
return false;
|
|
171
|
+
const lParsed = parseGitHubUrl(l.repo);
|
|
172
|
+
return lParsed && lParsed.owner === parsed.owner && lParsed.repo === parsed.repo;
|
|
173
|
+
}));
|
|
174
|
+
if (matchedProject) {
|
|
175
|
+
setSelectedProject(matchedProject);
|
|
176
|
+
setMode('add-livestock-name');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// No match found - let user pick project
|
|
182
|
+
if (projects.length === 0) {
|
|
183
|
+
// No projects available
|
|
184
|
+
setMode('normal');
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
setMode('add-livestock-project');
|
|
188
|
+
};
|
|
189
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(BarnHeader, { name: barn.name, subtitle: "Adding livestock" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add Livestock to ", barn.name] }), _jsxs(Text, { dimColor: true, children: ["Enter the ", isLocal ? 'local' : `path on ${barn.host}`, " path"] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Path: " }), _jsx(PathInput, { value: newLivestockPath, onChange: setNewLivestockPath, onSubmit: handlePathSubmit, barn: isLocal ? undefined : barn })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Tab: autocomplete, Enter: next (auto-detects git), Esc: cancel" }) })] })] }));
|
|
190
|
+
}
|
|
191
|
+
// Step 2 (if no auto-match): Select project
|
|
192
|
+
if (mode === 'add-livestock-project') {
|
|
193
|
+
const projectOptions = projects.map((p) => ({
|
|
194
|
+
id: p.name,
|
|
195
|
+
label: p.name,
|
|
196
|
+
status: 'active',
|
|
197
|
+
meta: p.path,
|
|
198
|
+
}));
|
|
199
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(BarnHeader, { name: barn.name, subtitle: "Adding livestock" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "green", children: "Add Livestock to Barn" }), detectedGit?.isGitRepo ? (_jsxs(Text, { dimColor: true, children: ["Repo detected: ", detectedGit.remoteUrl, " (no matching project found)"] })) : (_jsx(Text, { dimColor: true, children: "No git repo detected. Which project does this belong to?" })), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(List, { items: projectOptions, focused: true, onSelect: (item) => {
|
|
200
|
+
const project = projects.find((p) => p.name === item.id);
|
|
201
|
+
if (project) {
|
|
202
|
+
setSelectedProject(project);
|
|
203
|
+
setMode('add-livestock-name');
|
|
204
|
+
}
|
|
205
|
+
} }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: select, Esc: cancel" }) })] })] }));
|
|
206
|
+
}
|
|
207
|
+
// Step 3: Enter name
|
|
208
|
+
if (mode === 'add-livestock-name') {
|
|
209
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(BarnHeader, { name: barn.name, subtitle: "Adding livestock" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsxs(Text, { bold: true, color: "green", children: ["Add Livestock: ", selectedProject?.name] }), detectedGit?.isGitRepo && (_jsxs(Text, { color: "cyan", children: ["Git detected: ", detectedGit.branch || 'unknown branch'] })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Name: " }), _jsx(TextInput, { value: newLivestockName, onChange: setNewLivestockName, onSubmit: () => {
|
|
210
|
+
if (newLivestockName.trim() && selectedProject) {
|
|
211
|
+
const newLivestock = {
|
|
212
|
+
name: newLivestockName,
|
|
213
|
+
path: newLivestockPath,
|
|
214
|
+
barn: isLocal ? undefined : barn.name, // Don't set barn for local livestock
|
|
215
|
+
repo: detectedGit?.remoteUrl,
|
|
216
|
+
branch: detectedGit?.branch,
|
|
217
|
+
};
|
|
218
|
+
onAddLivestock(selectedProject, newLivestock);
|
|
219
|
+
setMode('normal');
|
|
220
|
+
}
|
|
221
|
+
}, placeholder: "production, staging, etc." })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: save, Esc: cancel" }) })] })] }));
|
|
222
|
+
}
|
|
223
|
+
// Delete livestock confirmation
|
|
224
|
+
if (mode === 'delete-livestock-confirm' && deleteLivestockTarget) {
|
|
225
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(BarnHeader, { name: barn.name, subtitle: "Remove livestock" }), _jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "red", children: "Remove Livestock" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["Remove \"", deleteLivestockTarget.livestock.name, "\" from ", deleteLivestockTarget.project.name, "?"] }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Path: ", deleteLivestockTarget.livestock.path] }) }), _jsxs(Box, { marginTop: 1, gap: 2, children: [_jsx(Text, { color: "red", bold: true, children: "[y] Yes, remove" }), _jsx(Text, { dimColor: true, children: "[n/Esc] Cancel" })] })] })] }));
|
|
226
|
+
}
|
|
227
|
+
// Build livestock items
|
|
228
|
+
const livestockItems = livestock.map((l) => ({
|
|
229
|
+
id: `${l.project.name}/${l.livestock.name}`,
|
|
230
|
+
label: `${l.project.name}/${l.livestock.name}`,
|
|
231
|
+
status: 'active',
|
|
232
|
+
meta: l.livestock.path,
|
|
233
|
+
}));
|
|
234
|
+
// Build critter items
|
|
235
|
+
const critterItems = (barn.critters || []).map((c) => ({
|
|
236
|
+
id: c.name,
|
|
237
|
+
label: c.name,
|
|
238
|
+
status: c.status === 'running' ? 'active' : c.status === 'stopped' ? 'inactive' : 'inactive',
|
|
239
|
+
meta: c.type,
|
|
240
|
+
}));
|
|
241
|
+
// Panel-specific hints (page-level hotkeys like s are in BottomBar)
|
|
242
|
+
const livestockHints = '[s] shell [n] new [d] delete';
|
|
243
|
+
const crittersHints = '';
|
|
244
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(BarnHeader, { name: barn.name, subtitle: isLocal ? 'Local machine' : `${barn.user}@${barn.host}:${barn.port}` }), !isLocal && (_jsx(Box, { paddingX: 2, marginY: 1, children: _jsxs(Box, { gap: 4, children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "Host:" }), " ", barn.host] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "User:" }), " ", barn.user] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "Port:" }), " ", barn.port] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "Key:" }), " ", barn.identity_file] })] }) })), _jsxs(Box, { flexGrow: 1, marginY: 1, paddingX: 1, gap: 2, children: [_jsx(Panel, { title: "Livestock", focused: focusedPanel === 'livestock', width: "50%", hints: livestockHints, children: livestockItems.length > 0 ? (_jsx(List, { items: livestockItems, focused: focusedPanel === 'livestock', selectedIndex: selectedLivestockIndex, onSelectionChange: setSelectedLivestockIndex, onSelect: (item) => {
|
|
245
|
+
const found = livestock.find((l) => `${l.project.name}/${l.livestock.name}` === item.id);
|
|
246
|
+
if (found) {
|
|
247
|
+
onSelectLivestock(found.project, found.livestock);
|
|
248
|
+
}
|
|
249
|
+
} })) : (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { dimColor: true, children: "No livestock deployed to this barn" }) })) }), _jsx(Panel, { title: "Critters", focused: focusedPanel === 'critters', width: "50%", hints: crittersHints, children: critterItems.length > 0 ? (_jsx(List, { items: critterItems, focused: focusedPanel === 'critters', onSelect: () => {
|
|
250
|
+
// TODO: Could show critter details or manage service
|
|
251
|
+
} })) : (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "No critters configured" }), _jsx(Text, { dimColor: true, italic: true, children: "Critters are system services (nginx, mysql, etc.)" })] })) })] })] }));
|
|
252
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Project, Barn } from '../types.js';
|
|
2
|
+
import { type TmuxWindow } from '../lib/tmux.js';
|
|
3
|
+
interface GlobalDashboardProps {
|
|
4
|
+
projects: Project[];
|
|
5
|
+
barns: Barn[];
|
|
6
|
+
windows: TmuxWindow[];
|
|
7
|
+
onSelectProject: (project: Project) => void;
|
|
8
|
+
onSelectBarn: (barn: Barn) => void;
|
|
9
|
+
onSelectWindow: (window: TmuxWindow) => void;
|
|
10
|
+
onNewClaude: () => void;
|
|
11
|
+
onCreateProject: (name: string, path: string) => void;
|
|
12
|
+
onCreateBarn: (barn: Barn) => void;
|
|
13
|
+
onSshToBarn: (barn: Barn) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function GlobalDashboard({ projects, barns, windows, onSelectProject, onSelectBarn, onSelectWindow, onNewClaude, onCreateProject, onCreateBarn, onSshToBarn, }: GlobalDashboardProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useMemo } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import TextInput from 'ink-text-input';
|
|
5
|
+
import { Header } from '../components/Header.js';
|
|
6
|
+
import { Panel } from '../components/Panel.js';
|
|
7
|
+
import { List } from '../components/List.js';
|
|
8
|
+
import { PathInput } from '../components/PathInput.js';
|
|
9
|
+
import { parseSshConfig } from '../lib/ssh.js';
|
|
10
|
+
import { getWindowStatus } from '../lib/tmux.js';
|
|
11
|
+
function countSessionsForProject(projectName, windows) {
|
|
12
|
+
return windows.filter((w) => w.name.startsWith(projectName)).length;
|
|
13
|
+
}
|
|
14
|
+
export function GlobalDashboard({ projects, barns, windows, onSelectProject, onSelectBarn, onSelectWindow, onNewClaude, onCreateProject, onCreateBarn, onSshToBarn, }) {
|
|
15
|
+
const [focusedPanel, setFocusedPanel] = useState('projects');
|
|
16
|
+
const [mode, setMode] = useState('normal');
|
|
17
|
+
// New project form state
|
|
18
|
+
const [newProjectName, setNewProjectName] = useState('');
|
|
19
|
+
const [newProjectPath, setNewProjectPath] = useState('');
|
|
20
|
+
// New barn form state
|
|
21
|
+
const [newBarnName, setNewBarnName] = useState('');
|
|
22
|
+
const [newBarnHost, setNewBarnHost] = useState('');
|
|
23
|
+
const [newBarnUser, setNewBarnUser] = useState('');
|
|
24
|
+
const [newBarnPort, setNewBarnPort] = useState('22');
|
|
25
|
+
const [newBarnKey, setNewBarnKey] = useState('');
|
|
26
|
+
// SSH hosts from config
|
|
27
|
+
const sshHosts = useMemo(() => parseSshConfig(), []);
|
|
28
|
+
// Filter out window 0 (yeehaw TUI)
|
|
29
|
+
const sessionWindows = windows.filter((w) => w.index > 0);
|
|
30
|
+
// Create a map from display number (1-9) to window for quick access
|
|
31
|
+
const windowsByDisplayNum = new Map();
|
|
32
|
+
sessionWindows.forEach((w, i) => {
|
|
33
|
+
if (i < 9) {
|
|
34
|
+
windowsByDisplayNum.set(i + 1, w);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const resetNewProject = () => {
|
|
38
|
+
setNewProjectName('');
|
|
39
|
+
setNewProjectPath('');
|
|
40
|
+
};
|
|
41
|
+
const resetNewBarn = () => {
|
|
42
|
+
setNewBarnName('');
|
|
43
|
+
setNewBarnHost('');
|
|
44
|
+
setNewBarnUser('');
|
|
45
|
+
setNewBarnPort('22');
|
|
46
|
+
setNewBarnKey('');
|
|
47
|
+
};
|
|
48
|
+
useInput((input, key) => {
|
|
49
|
+
// Handle escape to cancel creation
|
|
50
|
+
if (key.escape && mode !== 'normal') {
|
|
51
|
+
setMode('normal');
|
|
52
|
+
resetNewProject();
|
|
53
|
+
resetNewBarn();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// Only process these in normal mode
|
|
57
|
+
if (mode !== 'normal')
|
|
58
|
+
return;
|
|
59
|
+
// Tab to cycle panels
|
|
60
|
+
if (key.tab) {
|
|
61
|
+
setFocusedPanel((p) => {
|
|
62
|
+
if (p === 'projects')
|
|
63
|
+
return 'sessions';
|
|
64
|
+
if (p === 'sessions')
|
|
65
|
+
return 'barns';
|
|
66
|
+
return 'projects';
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (input === 'c') {
|
|
71
|
+
onNewClaude();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (input === 'n') {
|
|
75
|
+
if (focusedPanel === 'projects') {
|
|
76
|
+
setMode('new-project-name');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (focusedPanel === 'barns') {
|
|
80
|
+
// If we have SSH hosts, show selection first
|
|
81
|
+
if (sshHosts.length > 0) {
|
|
82
|
+
setMode('new-barn-select-ssh');
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
setMode('new-barn-name');
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (input === 's' && focusedPanel === 'barns') {
|
|
91
|
+
// SSH to selected barn - handled by list selection
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// Number hotkeys 1-9 for quick session switching
|
|
95
|
+
const num = parseInt(input, 10);
|
|
96
|
+
if (num >= 1 && num <= 9) {
|
|
97
|
+
const window = windowsByDisplayNum.get(num);
|
|
98
|
+
if (window) {
|
|
99
|
+
onSelectWindow(window);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
const handleProjectNameSubmit = (name) => {
|
|
105
|
+
if (name.trim()) {
|
|
106
|
+
setNewProjectName(name.trim());
|
|
107
|
+
setNewProjectPath('');
|
|
108
|
+
setMode('new-project-path');
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const handleProjectPathSubmit = (path) => {
|
|
112
|
+
if (path.trim() && newProjectName) {
|
|
113
|
+
onCreateProject(newProjectName, path.trim());
|
|
114
|
+
setMode('normal');
|
|
115
|
+
resetNewProject();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const handleBarnNameSubmit = (name) => {
|
|
119
|
+
if (name.trim()) {
|
|
120
|
+
setNewBarnName(name.trim());
|
|
121
|
+
setMode('new-barn-host');
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const handleBarnHostSubmit = (host) => {
|
|
125
|
+
if (host.trim()) {
|
|
126
|
+
setNewBarnHost(host.trim());
|
|
127
|
+
setMode('new-barn-user');
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const handleBarnUserSubmit = (user) => {
|
|
131
|
+
if (user.trim()) {
|
|
132
|
+
setNewBarnUser(user.trim());
|
|
133
|
+
setMode('new-barn-port');
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const handleBarnPortSubmit = (port) => {
|
|
137
|
+
setNewBarnPort(port.trim() || '22');
|
|
138
|
+
setMode('new-barn-key');
|
|
139
|
+
};
|
|
140
|
+
const handleBarnKeySubmit = (key) => {
|
|
141
|
+
if (key.trim()) {
|
|
142
|
+
const barn = {
|
|
143
|
+
name: newBarnName,
|
|
144
|
+
host: newBarnHost,
|
|
145
|
+
user: newBarnUser,
|
|
146
|
+
port: parseInt(newBarnPort, 10) || 22,
|
|
147
|
+
identity_file: key.trim(),
|
|
148
|
+
critters: [],
|
|
149
|
+
};
|
|
150
|
+
onCreateBarn(barn);
|
|
151
|
+
setMode('normal');
|
|
152
|
+
resetNewBarn();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const handleSshHostSelect = (host) => {
|
|
156
|
+
// Pre-fill from SSH config
|
|
157
|
+
setNewBarnName(host.name);
|
|
158
|
+
setNewBarnHost(host.hostname || host.name);
|
|
159
|
+
setNewBarnUser(host.user || 'root');
|
|
160
|
+
setNewBarnPort(String(host.port || 22));
|
|
161
|
+
setNewBarnKey(host.identityFile || '');
|
|
162
|
+
setMode('new-barn-name');
|
|
163
|
+
};
|
|
164
|
+
const projectItems = projects.map((p) => {
|
|
165
|
+
const sessionCount = countSessionsForProject(p.name, windows);
|
|
166
|
+
return {
|
|
167
|
+
id: p.name,
|
|
168
|
+
label: p.name,
|
|
169
|
+
status: sessionCount > 0 ? 'active' : 'inactive',
|
|
170
|
+
meta: sessionCount > 0 ? `${sessionCount} session${sessionCount > 1 ? 's' : ''}` : undefined,
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
// Use display numbers (1-9) instead of window index
|
|
174
|
+
const sessionItems = sessionWindows.map((w, i) => ({
|
|
175
|
+
id: String(w.index),
|
|
176
|
+
label: `[${i + 1}] ${w.name}`,
|
|
177
|
+
status: w.active ? 'active' : 'inactive',
|
|
178
|
+
meta: getWindowStatus(w),
|
|
179
|
+
}));
|
|
180
|
+
const barnItems = barns.map((b) => ({
|
|
181
|
+
id: b.name,
|
|
182
|
+
label: b.name,
|
|
183
|
+
status: 'active',
|
|
184
|
+
meta: `${b.user}@${b.host}`,
|
|
185
|
+
}));
|
|
186
|
+
// New project modals
|
|
187
|
+
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" }) })] })] }));
|
|
189
|
+
}
|
|
190
|
+
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" }) })] })] }));
|
|
192
|
+
}
|
|
193
|
+
// SSH host selection for new barn
|
|
194
|
+
if (mode === 'new-barn-select-ssh') {
|
|
195
|
+
const sshHostItems = [
|
|
196
|
+
{ id: '__manual__', label: 'Enter manually...', status: 'inactive' },
|
|
197
|
+
...sshHosts.map((h) => ({
|
|
198
|
+
id: h.name,
|
|
199
|
+
label: h.name,
|
|
200
|
+
status: 'active',
|
|
201
|
+
meta: h.hostname ? `${h.user || 'root'}@${h.hostname}` : undefined,
|
|
202
|
+
})),
|
|
203
|
+
];
|
|
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) => {
|
|
205
|
+
if (item.id === '__manual__') {
|
|
206
|
+
setMode('new-barn-name');
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
const host = sshHosts.find((h) => h.name === item.id);
|
|
210
|
+
if (host)
|
|
211
|
+
handleSshHostSelect(host);
|
|
212
|
+
}
|
|
213
|
+
} }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: select, Esc: cancel" }) })] })] }));
|
|
214
|
+
}
|
|
215
|
+
// New barn modals
|
|
216
|
+
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" }) })] })] }));
|
|
218
|
+
}
|
|
219
|
+
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" }) })] })] }));
|
|
221
|
+
}
|
|
222
|
+
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" }) })] })] }));
|
|
224
|
+
}
|
|
225
|
+
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" }) })] })] }));
|
|
227
|
+
}
|
|
228
|
+
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" }) })] })] }));
|
|
230
|
+
}
|
|
231
|
+
// Panel-specific hints (page-level hotkeys like c are in BottomBar)
|
|
232
|
+
const projectHints = '[n] new';
|
|
233
|
+
const sessionHints = '1-9 switch';
|
|
234
|
+
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', width: "40%", hints: projectHints, children: projectItems.length > 0 ? (_jsx(List, { items: projectItems, focused: focusedPanel === 'projects', onSelect: (item) => {
|
|
236
|
+
const project = projects.find((p) => p.name === item.id);
|
|
237
|
+
if (project)
|
|
238
|
+
onSelectProject(project);
|
|
239
|
+
} })) : (_jsx(Text, { dimColor: true, children: "No projects yet" })) }), _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
|
+
const window = sessionWindows.find((w) => String(w.index) === item.id);
|
|
241
|
+
if (window)
|
|
242
|
+
onSelectWindow(window);
|
|
243
|
+
} })) : (_jsx(Text, { dimColor: true, children: "No active sessions" })) })] }), _jsx(Box, { paddingX: 1, marginBottom: 1, children: _jsx(Panel, { title: "Barns", focused: focusedPanel === 'barns', hints: barnHints, children: barnItems.length > 0 ? (_jsx(Box, { flexDirection: "column", children: _jsx(List, { items: barnItems, focused: focusedPanel === 'barns', onSelect: (item) => {
|
|
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" })] })) }) })] }));
|
|
253
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Barn } from '../types.js';
|
|
2
|
+
import type { TmuxSession } from '../lib/tmux.js';
|
|
3
|
+
interface HomeProps {
|
|
4
|
+
barns: Barn[];
|
|
5
|
+
sessions: TmuxSession[];
|
|
6
|
+
focusedPanel: 'barns' | 'sessions';
|
|
7
|
+
onSelectBarn: (barn: Barn) => void;
|
|
8
|
+
onSelectSession: (session: TmuxSession) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function Home({ barns, sessions, focusedPanel, onSelectBarn, onSelectSession }: HomeProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|