@colmbus72/yeehaw 0.4.1 → 0.5.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.
@@ -12,7 +12,7 @@ import { isLocalBarn } from '../lib/config.js';
12
12
  function countSessionsForProject(projectName, windows) {
13
13
  return windows.filter((w) => w.name.startsWith(projectName)).length;
14
14
  }
15
- export function GlobalDashboard({ projects, barns, windows, versionInfo, onSelectProject, onSelectBarn, onSelectWindow, onNewClaude, onCreateProject, onCreateBarn, onSshToBarn, onInputModeChange, }) {
15
+ export function GlobalDashboard({ projects, barns, windows, versionInfo, onSelectProject, onSelectBarn, onSelectWindow, onNewClaudeForProject, onCreateProject, onCreateBarn, onSshToBarn, onInputModeChange, }) {
16
16
  const [focusedPanel, setFocusedPanel] = useState('projects');
17
17
  const [mode, setModeInternal] = useState('normal');
18
18
  // Wrapper to notify parent when input mode changes
@@ -73,10 +73,6 @@ export function GlobalDashboard({ projects, barns, windows, versionInfo, onSelec
73
73
  });
74
74
  return;
75
75
  }
76
- if (input === 'c') {
77
- onNewClaude();
78
- return;
79
- }
80
76
  if (input === 'n') {
81
77
  if (focusedPanel === 'projects') {
82
78
  setMode('new-project-name');
@@ -93,10 +89,6 @@ export function GlobalDashboard({ projects, barns, windows, versionInfo, onSelec
93
89
  return;
94
90
  }
95
91
  }
96
- if (input === 's' && focusedPanel === 'barns') {
97
- // SSH to selected barn - handled by list selection
98
- return;
99
- }
100
92
  // Number hotkeys 1-9 for quick session switching
101
93
  const num = parseInt(input, 10);
102
94
  if (num >= 1 && num <= 9) {
@@ -174,6 +166,7 @@ export function GlobalDashboard({ projects, barns, windows, versionInfo, onSelec
174
166
  label: p.name,
175
167
  status: sessionCount > 0 ? 'active' : 'inactive',
176
168
  meta: sessionCount > 0 ? `${sessionCount} session${sessionCount > 1 ? 's' : ''}` : undefined,
169
+ actions: [{ key: 'c', label: 'claude' }],
177
170
  };
178
171
  });
179
172
  // Parse window name to show clearer labels
@@ -202,11 +195,13 @@ export function GlobalDashboard({ projects, barns, windows, versionInfo, onSelec
202
195
  // Use display numbers (1-9) instead of window index
203
196
  const sessionItems = sessionWindows.map((w, i) => {
204
197
  const { label, typeHint } = formatSessionLabel(w.name);
198
+ const statusInfo = getWindowStatus(w);
205
199
  return {
206
200
  id: String(w.index),
207
201
  label: `[${i + 1}] ${label}`,
208
202
  status: w.active ? 'active' : 'inactive',
209
- meta: typeHint ? `${typeHint} · ${getWindowStatus(w)}` : getWindowStatus(w),
203
+ meta: typeHint ? `${typeHint} · ${statusInfo.text}` : statusInfo.text,
204
+ sessionStatus: statusInfo.status,
210
205
  };
211
206
  });
212
207
  const barnItems = barns.map((b) => ({
@@ -214,6 +209,7 @@ export function GlobalDashboard({ projects, barns, windows, versionInfo, onSelec
214
209
  label: isLocalBarn(b) ? 'local' : b.name,
215
210
  status: 'active',
216
211
  meta: isLocalBarn(b) ? 'this machine' : `${b.user}@${b.host}`,
212
+ actions: [{ key: 's', label: 'shell' }],
217
213
  }));
218
214
  // New project modals
219
215
  if (mode === 'new-project-name') {
@@ -260,23 +256,29 @@ export function GlobalDashboard({ projects, barns, windows, versionInfo, onSelec
260
256
  if (mode === 'new-barn-key') {
261
257
  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" }) })] })] }));
262
258
  }
263
- // Panel-specific hints (page-level hotkeys like c are in BottomBar)
264
259
  const projectHints = '[n] new';
265
- const sessionHints = '1-9 switch';
266
- const barnHints = '[n] new [s] shell';
260
+ const sessionHints = '';
261
+ const barnHints = '[n] new';
267
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) => {
268
263
  const project = projects.find((p) => p.name === item.id);
269
264
  if (project)
270
265
  onSelectProject(project);
266
+ }, onAction: (item, actionKey) => {
267
+ if (actionKey === 'c') {
268
+ const project = projects.find((p) => p.name === item.id);
269
+ if (project)
270
+ onNewClaudeForProject(project);
271
+ }
271
272
  } })) : (_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) => {
272
273
  const barn = barns.find((b) => b.name === item.id);
273
274
  if (barn)
274
275
  onSelectBarn(barn);
275
- }, onAction: (item) => {
276
- // 's' key to SSH directly
277
- const barn = barns.find((b) => b.name === item.id);
278
- if (barn)
279
- onSshToBarn(barn);
276
+ }, onAction: (item, actionKey) => {
277
+ if (actionKey === 's') {
278
+ const barn = barns.find((b) => b.name === item.id);
279
+ if (barn)
280
+ onSshToBarn(barn);
281
+ }
280
282
  } }) })) : (_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) => {
281
283
  const window = sessionWindows.find((w) => String(w.index) === item.id);
282
284
  if (window)
@@ -127,14 +127,18 @@ export function LivestockDetailView({ project, livestock, source, sourceBarn, wi
127
127
  // Filter windows to this livestock (match pattern: projectname-livestockname)
128
128
  const livestockWindowName = `${project.name}-${livestock.name}`;
129
129
  const livestockWindows = windows.filter(w => w.name === livestockWindowName || w.name.startsWith(`${livestockWindowName}-`));
130
- const sessionItems = livestockWindows.map((w, i) => ({
131
- id: String(w.index),
132
- label: `[${i + 1}] shell`,
133
- status: w.active ? 'active' : 'inactive',
134
- meta: getWindowStatus(w),
135
- }));
130
+ const sessionItems = livestockWindows.map((w, i) => {
131
+ const statusInfo = getWindowStatus(w);
132
+ return {
133
+ id: String(w.index),
134
+ label: `[${i + 1}] shell`,
135
+ status: w.active ? 'active' : 'inactive',
136
+ meta: statusInfo.text,
137
+ sessionStatus: statusInfo.status,
138
+ };
139
+ });
136
140
  // Normal view - show livestock info inline with sessions
137
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(LivestockHeader, { project: project, livestock: livestock }), _jsxs(Box, { paddingX: 2, gap: 3, children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "path:" }), " ", livestock.path] }), barn && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "barn:" }), " ", barn.name, " ", _jsxs(Text, { dimColor: true, children: ["(", barn.host, ")"] })] }))] }), _jsxs(Box, { paddingX: 2, gap: 3, marginBottom: 1, children: [livestock.repo && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "repo:" }), " ", livestock.repo] })), livestock.log_path && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "logs:" }), " ", livestock.log_path] })), livestock.env_path && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "env:" }), " ", livestock.env_path] }))] }), _jsx(Box, { paddingX: 1, flexGrow: 1, children: _jsx(Panel, { title: "Sessions", focused: true, hints: `[s] shell ${livestock.log_path ? '[l] logs ' : ''}[e] edit`, children: sessionItems.length > 0 ? (_jsx(List, { items: sessionItems, focused: true, onSelect: (item) => {
141
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(LivestockHeader, { project: project, livestock: livestock }), _jsxs(Box, { paddingX: 2, gap: 3, children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "path:" }), " ", livestock.path] }), barn && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "barn:" }), " ", barn.name, " ", _jsxs(Text, { dimColor: true, children: ["(", barn.host, ")"] })] }))] }), _jsxs(Box, { paddingX: 2, gap: 3, marginBottom: 1, children: [livestock.repo && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "repo:" }), " ", livestock.repo] })), livestock.log_path && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "logs:" }), " ", livestock.log_path] })), livestock.env_path && (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "env:" }), " ", livestock.env_path] }))] }), _jsx(Box, { paddingX: 1, flexGrow: 1, children: _jsx(Panel, { title: "Sessions", focused: true, children: sessionItems.length > 0 ? (_jsx(List, { items: sessionItems, focused: true, onSelect: (item) => {
138
142
  const window = livestockWindows.find(w => String(w.index) === item.id);
139
143
  if (window)
140
144
  onSelectWindow(window);
@@ -5,7 +5,7 @@ interface ProjectContextProps {
5
5
  barns: Barn[];
6
6
  windows: TmuxWindow[];
7
7
  onBack: () => void;
8
- onNewClaude: () => void;
8
+ onNewClaudeForLivestock: (livestock: Livestock) => void;
9
9
  onSelectWindow: (window: TmuxWindow) => void;
10
10
  onSelectLivestock: (livestock: Livestock, barn: Barn | null) => void;
11
11
  onOpenLivestockSession: (livestock: Livestock, barn: Barn | null) => void;
@@ -14,5 +14,5 @@ interface ProjectContextProps {
14
14
  onOpenWiki: () => void;
15
15
  onOpenIssues: () => void;
16
16
  }
17
- export declare function ProjectContext({ project, barns, windows, onBack, onNewClaude, onSelectWindow, onSelectLivestock, onOpenLivestockSession, onUpdateProject, onDeleteProject, onOpenWiki, onOpenIssues, }: ProjectContextProps): import("react/jsx-runtime").JSX.Element;
17
+ export declare function ProjectContext({ project, barns, windows, onBack, onNewClaudeForLivestock, onSelectWindow, onSelectLivestock, onOpenLivestockSession, onUpdateProject, onDeleteProject, onOpenWiki, onOpenIssues, }: ProjectContextProps): import("react/jsx-runtime").JSX.Element;
18
18
  export {};
@@ -73,7 +73,7 @@ function hslToHex(h, s, l) {
73
73
  const toHex = (n) => Math.round((n + m) * 255).toString(16).padStart(2, '0');
74
74
  return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
75
75
  }
76
- export function ProjectContext({ project, barns, windows, onBack, onNewClaude, onSelectWindow, onSelectLivestock, onOpenLivestockSession, onUpdateProject, onDeleteProject, onOpenWiki, onOpenIssues, }) {
76
+ export function ProjectContext({ project, barns, windows, onBack, onNewClaudeForLivestock, onSelectWindow, onSelectLivestock, onOpenLivestockSession, onUpdateProject, onDeleteProject, onOpenWiki, onOpenIssues, }) {
77
77
  const [focusedPanel, setFocusedPanel] = useState('livestock');
78
78
  const [mode, setMode] = useState('normal');
79
79
  // Edit form state
@@ -267,10 +267,8 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
267
267
  setFocusedPanel((p) => (p === 'livestock' ? 'sessions' : 'livestock'));
268
268
  return;
269
269
  }
270
- if (input === 'c') {
271
- onNewClaude();
272
- return;
273
- }
270
+ // NOTE: 'c' for Claude is handled at row-level in the List component
271
+ // via the onAction callback - no page-level 'c' handler here
274
272
  if (input === 'e') {
275
273
  startEdit();
276
274
  return;
@@ -294,16 +292,6 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
294
292
  startAddLivestock();
295
293
  return;
296
294
  }
297
- if (input === 's') {
298
- // Open shell session for selected livestock
299
- const livestock = project.livestock || [];
300
- if (livestock.length > 0 && selectedLivestockIndex < livestock.length) {
301
- const selected = livestock[selectedLivestockIndex];
302
- const barn = selected.barn ? barns.find((b) => b.name === selected.barn) || null : null;
303
- onOpenLivestockSession(selected, barn);
304
- }
305
- return;
306
- }
307
295
  if (input === 'd') {
308
296
  const livestock = project.livestock || [];
309
297
  if (livestock.length > 0 && selectedLivestockIndex < livestock.length) {
@@ -411,12 +399,21 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
411
399
  livestock,
412
400
  barn: livestock.barn ? barns.find((b) => b.name === livestock.barn) || null : null,
413
401
  }));
414
- const livestockItems = livestockWithBarns.map(({ livestock, barn }) => ({
415
- id: livestock.name,
416
- label: barn ? `${livestock.name} (${barn.host})` : `${livestock.name} (local)`,
417
- status: 'active', // TODO: actual health check
418
- meta: livestock.path,
419
- }));
402
+ const livestockItems = livestockWithBarns.map(({ livestock, barn }) => {
403
+ const isLocal = !barn || isLocalBarn(barn);
404
+ // Local livestock: claude + shell
405
+ // Remote livestock: shell only
406
+ const actions = isLocal
407
+ ? [{ key: 'c', label: 'claude' }, { key: 's', label: 'shell' }]
408
+ : [{ key: 's', label: 'shell' }];
409
+ return {
410
+ id: livestock.name,
411
+ label: barn ? `${livestock.name} (${barn.host})` : `${livestock.name} (local)`,
412
+ status: 'active',
413
+ meta: livestock.path,
414
+ actions,
415
+ };
416
+ });
420
417
  // Parse session name for type hint (consistent with GlobalDashboard)
421
418
  const getSessionTypeHint = (name) => {
422
419
  if (name.endsWith('-claude'))
@@ -428,21 +425,33 @@ export function ProjectContext({ project, barns, windows, onBack, onNewClaude, o
428
425
  const sessionName = w.name.replace(`${project.name}-`, '');
429
426
  const typeHint = getSessionTypeHint(w.name);
430
427
  const displayName = sessionName.replace('-claude', '');
428
+ const statusInfo = getWindowStatus(w);
431
429
  return {
432
430
  id: String(w.index),
433
431
  label: `[${i + 1}] ${displayName}`,
434
432
  status: w.active ? 'active' : 'inactive',
435
- meta: `${typeHint} · ${getWindowStatus(w)}`,
433
+ meta: `${typeHint} · ${statusInfo.text}`,
434
+ sessionStatus: statusInfo.status,
436
435
  };
437
436
  });
438
437
  // Panel-specific hints (page-level hotkeys like c/w/i are in BottomBar)
439
- const livestockHints = '[s] shell [n] new [d] delete';
440
- const sessionHints = '1-9 switch';
438
+ const livestockHints = '[n] new [d] delete';
439
+ const sessionHints = '';
441
440
  return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, { text: project.name, subtitle: project.path, summary: project.summary, color: project.color, gradientSpread: project.gradientSpread, gradientInverted: project.gradientInverted }), _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) => {
442
441
  const found = livestockWithBarns.find((l) => l.livestock.name === item.id);
443
442
  if (found) {
444
443
  onSelectLivestock(found.livestock, found.barn);
445
444
  }
445
+ }, onAction: (item, actionKey) => {
446
+ const found = livestockWithBarns.find((l) => l.livestock.name === item.id);
447
+ if (!found)
448
+ return;
449
+ if (actionKey === 's') {
450
+ onOpenLivestockSession(found.livestock, found.barn);
451
+ }
452
+ if (actionKey === 'c') {
453
+ onNewClaudeForLivestock(found.livestock);
454
+ }
446
455
  } })) : (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "No livestock configured" }), _jsx(Text, { dimColor: true, italic: true, children: "Livestock are your deployed app instances" })] })) }), _jsx(Panel, { title: "Sessions", focused: focusedPanel === 'sessions', width: "50%", hints: sessionHints, children: sessionItems.length > 0 ? (_jsx(List, { items: sessionItems, focused: focusedPanel === 'sessions', onSelect: (item) => {
447
456
  const window = projectWindows.find((w) => String(w.index) === item.id);
448
457
  if (window)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colmbus72/yeehaw",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Terminal dashboard for managing projects, servers, and deployments",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -45,18 +45,18 @@
45
45
  "chokidar": "^3.6.0",
46
46
  "execa": "^8.0.1",
47
47
  "figlet": "^1.7.0",
48
- "ink": "^4.4.1",
49
- "ink-text-input": "^5.0.1",
48
+ "ink": "^6.6.0",
49
+ "ink-text-input": "^6.0.0",
50
50
  "js-yaml": "^4.1.0",
51
51
  "marked": "^9.1.6",
52
52
  "marked-terminal": "^6.2.0",
53
- "react": "^18.2.0"
53
+ "react": "^19.0.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/figlet": "^1.5.8",
57
57
  "@types/js-yaml": "^4.0.9",
58
58
  "@types/node": "^25.0.10",
59
- "@types/react": "^18.2.0",
59
+ "@types/react": "^19.0.0",
60
60
  "tsx": "^4.7.0",
61
61
  "typescript": "^5.3.0"
62
62
  },