@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.
- package/claude-plugin/.claude-plugin/plugin.json +2 -1
- package/claude-plugin/hooks/hooks.json +41 -0
- package/claude-plugin/hooks/session-status.sh +13 -0
- package/dist/app.js +80 -31
- package/dist/components/HelpOverlay.js +4 -2
- package/dist/components/List.d.ts +8 -1
- package/dist/components/List.js +14 -5
- package/dist/components/Panel.js +27 -1
- package/dist/components/SplashScreen.js +1 -1
- package/dist/hooks/useSessions.js +2 -2
- package/dist/index.js +41 -1
- package/dist/lib/config.d.ts +13 -1
- package/dist/lib/config.js +51 -0
- package/dist/lib/critters.d.ts +28 -0
- package/dist/lib/critters.js +201 -0
- package/dist/lib/hooks.d.ts +20 -0
- package/dist/lib/hooks.js +91 -0
- package/dist/lib/hotkeys.js +24 -20
- package/dist/lib/paths.d.ts +2 -0
- package/dist/lib/paths.js +2 -0
- package/dist/lib/signals.d.ts +30 -0
- package/dist/lib/signals.js +104 -0
- package/dist/lib/tmux.d.ts +8 -2
- package/dist/lib/tmux.js +69 -18
- package/dist/mcp-server.js +161 -1
- package/dist/types.d.ts +4 -2
- package/dist/views/BarnContext.d.ts +4 -2
- package/dist/views/BarnContext.js +79 -20
- package/dist/views/GlobalDashboard.d.ts +2 -2
- package/dist/views/GlobalDashboard.js +20 -18
- package/dist/views/LivestockDetailView.js +11 -7
- package/dist/views/ProjectContext.d.ts +2 -2
- package/dist/views/ProjectContext.js +33 -24
- package/package.json +5 -5
|
@@ -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,
|
|
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} · ${
|
|
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 = '
|
|
266
|
-
const barnHints = '[n] new
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
271
|
-
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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} · ${
|
|
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 = '[
|
|
440
|
-
const sessionHints = '
|
|
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.
|
|
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": "^
|
|
49
|
-
"ink-text-input": "^
|
|
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": "^
|
|
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": "^
|
|
59
|
+
"@types/react": "^19.0.0",
|
|
60
60
|
"tsx": "^4.7.0",
|
|
61
61
|
"typescript": "^5.3.0"
|
|
62
62
|
},
|