Skip to content

Commit 8993d64

Browse files
committed
cp dines
1 parent 4486bcb commit 8993d64

18 files changed

Lines changed: 302 additions & 232 deletions

.cursor/worktrees.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"setup-worktree": [
3+
"npm install"
4+
]
5+
}

package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"ink-big-text": "^2.0.0",
7272
"ink-gradient": "^3.0.0",
7373
"ink-link": "^5.0.0",
74+
"ink-spawn": "^0.1.4",
7475
"ink-spinner": "^5.0.0",
7576
"ink-text-input": "^6.0.0",
7677
"react": "19.2.0",

src/commands/devbox/list.tsx

Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ import { ActionsPopup } from "../../components/ActionsPopup.js";
1919
import { getDevboxUrl } from "../../utils/url.js";
2020
import { useViewportHeight } from "../../hooks/useViewportHeight.js";
2121
import { exitAlternateScreen } from "../../utils/screen.js";
22-
import {
23-
runSSHSession,
24-
type SSHSessionConfig,
25-
} from "../../utils/sshSession.js";
2622
import { colors } from "../../utils/theme.js";
2723

2824
interface ListOptions {
@@ -35,13 +31,11 @@ const MAX_CACHE_SIZE = 10; // Limit cache to 10 pages to prevent memory leaks
3531

3632
const ListDevboxesUI = ({
3733
status,
38-
onSSHRequest,
3934
focusDevboxId,
4035
onBack,
4136
onExit,
4237
}: {
4338
status?: string;
44-
onSSHRequest?: (config: SSHSessionConfig) => void;
4539
focusDevboxId?: string;
4640
onBack?: () => void;
4741
onExit?: () => void;
@@ -220,8 +214,8 @@ const ListDevboxesUI = ({
220214
(devbox: any) => {
221215
if (devbox?.blueprint_id) {
222216
const bpId = String(devbox.blueprint_id);
223-
const truncated = bpId.slice(0, 10);
224-
const text = `blueprint:${truncated}`;
217+
const truncated = bpId.slice(0, 16);
218+
const text = `${truncated}`;
225219
// Cap source text to absolute maximum
226220
return text.length > 30 ? text.substring(0, 27) + "..." : text;
227221
}
@@ -776,7 +770,6 @@ const ListDevboxesUI = ({
776770
]}
777771
initialOperation={selectedOp?.key}
778772
skipOperationsMenu={true}
779-
onSSHRequest={onSSHRequest}
780773
/>
781774
);
782775
}
@@ -787,7 +780,6 @@ const ListDevboxesUI = ({
787780
<DevboxDetailPage
788781
devbox={selectedDevbox}
789782
onBack={() => setShowDetails(false)}
790-
onSSHRequest={onSSHRequest}
791783
/>
792784
);
793785
}
@@ -971,8 +963,6 @@ export async function listDevboxes(
971963
) {
972964
const executor = createExecutor(options);
973965

974-
let sshSessionConfig: SSHSessionConfig | null = null;
975-
976966
await executor.executeList(
977967
async () => {
978968
const client = executor.getClient();
@@ -984,29 +974,8 @@ export async function listDevboxes(
984974
});
985975
},
986976
() => (
987-
<ListDevboxesUI
988-
status={options.status}
989-
focusDevboxId={focusDevboxId}
990-
onSSHRequest={(config) => {
991-
sshSessionConfig = config;
992-
}}
993-
/>
977+
<ListDevboxesUI status={options.status} focusDevboxId={focusDevboxId} />
994978
),
995979
DEFAULT_PAGE_SIZE,
996980
);
997-
998-
// If SSH was requested, handle it now after Ink has exited
999-
if (sshSessionConfig) {
1000-
const result = await runSSHSession(sshSessionConfig);
1001-
1002-
if (result.shouldRestart) {
1003-
console.log(`\nSSH session ended. Returning to CLI...\n`);
1004-
await new Promise((resolve) => setTimeout(resolve, 500));
1005-
1006-
// Restart the list view with the devbox ID to focus on
1007-
await listDevboxes(options, result.returnToDevboxId);
1008-
} else {
1009-
process.exit(result.exitCode);
1010-
}
1011-
}
1012981
}

src/commands/menu.tsx

Lines changed: 25 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,30 @@
11
import React from "react";
22
import { render } from "ink";
3-
import { runSSHSession, type SSHSessionConfig } from "../utils/sshSession.js";
43
import { enterAlternateScreen, exitAlternateScreen } from "../utils/screen.js";
54

65
import { Router } from "../router/Router.js";
76
import { NavigationProvider } from "../store/navigationStore.js";
87
import type { ScreenName } from "../store/navigationStore.js";
98

10-
interface AppProps {
11-
onSSHRequest: (config: SSHSessionConfig) => void;
12-
initialScreen?: ScreenName;
13-
focusDevboxId?: string;
14-
}
15-
16-
function AppInner({
17-
onSSHRequest,
18-
}: {
19-
onSSHRequest: (config: SSHSessionConfig) => void;
20-
}) {
9+
function AppInner() {
2110
// NavigationProvider already handles initialScreen and initialParams
2211
// No need for useEffect here - provider sets state on mount
23-
return <Router onSSHRequest={onSSHRequest} />;
12+
return <Router />;
2413
}
2514

2615
function App({
27-
onSSHRequest,
2816
initialScreen = "menu",
2917
focusDevboxId,
30-
}: AppProps) {
18+
}: {
19+
initialScreen?: ScreenName;
20+
focusDevboxId?: string;
21+
}) {
3122
return (
3223
<NavigationProvider
3324
initialScreen={initialScreen}
3425
initialParams={focusDevboxId ? { focusDevboxId } : {}}
3526
>
36-
<AppInner onSSHRequest={onSSHRequest} />
27+
<AppInner />
3728
</NavigationProvider>
3829
);
3930
}
@@ -43,60 +34,27 @@ export async function runMainMenu(
4334
focusDevboxId?: string,
4435
) {
4536
// Enter alternate screen buffer for fullscreen experience (like top/vim)
46-
enterAlternateScreen();
47-
48-
let sshSessionConfig: SSHSessionConfig | null = null;
49-
let shouldContinue = true;
50-
let currentInitialScreen = initialScreen;
51-
let currentFocusDevboxId = focusDevboxId;
52-
53-
while (shouldContinue) {
54-
sshSessionConfig = null;
55-
56-
try {
57-
const { waitUntilExit } = render(
58-
<App
59-
key={`app-${currentInitialScreen}-${currentFocusDevboxId}`}
60-
onSSHRequest={(config) => {
61-
sshSessionConfig = config;
62-
}}
63-
initialScreen={currentInitialScreen}
64-
focusDevboxId={currentFocusDevboxId}
65-
/>,
66-
{
67-
patchConsole: false,
68-
exitOnCtrlC: false,
69-
},
70-
);
71-
await waitUntilExit();
72-
shouldContinue = false;
73-
} catch (error) {
74-
console.error("Error in menu:", error);
75-
shouldContinue = false;
76-
}
77-
78-
// If SSH was requested, handle it now after Ink has exited
79-
if (sshSessionConfig) {
80-
const result = await runSSHSession(sshSessionConfig);
81-
82-
if (result.shouldRestart) {
83-
console.log(`\nSSH session ended. Returning to menu...\n`);
84-
await new Promise((resolve) => setTimeout(resolve, 500));
85-
86-
currentInitialScreen = "devbox-list";
87-
currentFocusDevboxId = result.returnToDevboxId;
88-
shouldContinue = true;
89-
} else {
90-
shouldContinue = false;
91-
}
92-
}
37+
//enterAlternateScreen();
38+
39+
try {
40+
const { waitUntilExit } = render(
41+
<App
42+
key={`app-${initialScreen}-${focusDevboxId}`}
43+
initialScreen={initialScreen}
44+
focusDevboxId={focusDevboxId}
45+
/>,
46+
{
47+
patchConsole: false,
48+
exitOnCtrlC: false,
49+
},
50+
);
51+
await waitUntilExit();
52+
} catch (error) {
53+
console.error("Error in menu:", error);
9354
}
9455

95-
// Disable synchronous updates
96-
// disableSynchronousUpdates();
97-
9856
// Exit alternate screen buffer
99-
exitAlternateScreen();
57+
//exitAlternateScreen();
10058

10159
process.exit(0);
10260
}

src/components/DevboxActionsMenu.tsx

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import { SpinnerComponent } from "./Spinner.js";
77
import { ErrorMessage } from "./ErrorMessage.js";
88
import { SuccessMessage } from "./SuccessMessage.js";
99
import { Breadcrumb } from "./Breadcrumb.js";
10-
import type { SSHSessionConfig } from "../utils/sshSession.js";
1110
import { colors } from "../utils/theme.js";
1211
import { useViewportHeight } from "../hooks/useViewportHeight.js";
12+
import { useNavigation } from "../store/navigationStore.js";
1313
import {
1414
getDevboxLogs,
1515
execCommand,
@@ -41,7 +41,6 @@ interface DevboxActionsMenuProps {
4141
initialOperation?: string; // Operation to execute immediately
4242
initialOperationIndex?: number; // Index of the operation to select
4343
skipOperationsMenu?: boolean; // Skip showing operations menu and execute immediately
44-
onSSHRequest?: (config: SSHSessionConfig) => void; // Callback when SSH is requested
4544
}
4645

4746
export const DevboxActionsMenu = ({
@@ -54,9 +53,8 @@ export const DevboxActionsMenu = ({
5453
initialOperation,
5554
initialOperationIndex = 0,
5655
skipOperationsMenu = false,
57-
onSSHRequest,
5856
}: DevboxActionsMenuProps) => {
59-
const { exit } = useApp();
57+
const { navigate, currentScreen, params } = useNavigation();
6058
const [loading, setLoading] = React.useState(false);
6159
const [selectedOperation, setSelectedOperation] = React.useState(
6260
initialOperationIndex,
@@ -561,24 +559,22 @@ export const DevboxActionsMenu = ({
561559
devbox.launch_parameters?.user_parameters?.username || "user";
562560
const env = process.env.RUNLOOP_ENV?.toLowerCase();
563561
const sshHost = env === "dev" ? "ssh.runloop.pro" : "ssh.runloop.ai";
564-
const proxyCommand = `openssl s_client -quiet -verify_quiet -servername %h -connect ${sshHost}:443 2>/dev/null`;
562+
// macOS openssl doesn't support -verify_quiet, use compatible flags
563+
// servername should be %h (target hostname) - SSH will replace %h with the actual hostname from the SSH command
564+
// This matches the reference implementation where servername is the target hostname
565+
const proxyCommand = `openssl s_client -quiet -servername %h -connect ${sshHost}:443 2>/dev/null`;
565566

566-
const sshConfig: SSHSessionConfig = {
567+
// Navigate to SSH session screen
568+
navigate("ssh-session", {
567569
keyPath,
568570
proxyCommand,
569571
sshUser,
570572
url: sshKey.url,
571573
devboxId: devbox.id,
572574
devboxName: devbox.name || devbox.id,
573-
};
574-
575-
// Notify parent that SSH is requested
576-
if (onSSHRequest) {
577-
onSSHRequest(sshConfig);
578-
exit();
579-
} else {
580-
setOperationError(new Error("SSH session handler not configured"));
581-
}
575+
returnScreen: currentScreen,
576+
returnParams: params,
577+
});
582578
break;
583579

584580
case "logs":

src/components/DevboxDetailPage.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ import { MetadataDisplay } from "./MetadataDisplay.js";
77
import { Breadcrumb } from "./Breadcrumb.js";
88
import { DevboxActionsMenu } from "./DevboxActionsMenu.js";
99
import { getDevboxUrl } from "../utils/url.js";
10-
import type { SSHSessionConfig } from "../utils/sshSession.js";
1110
import { colors } from "../utils/theme.js";
1211
import { useViewportHeight } from "../hooks/useViewportHeight.js";
1312

1413
interface DevboxDetailPageProps {
1514
devbox: any;
1615
onBack: () => void;
17-
onSSHRequest?: (config: SSHSessionConfig) => void;
1816
}
1917

2018
// Format time ago in a succinct way
@@ -42,7 +40,6 @@ const formatTimeAgo = (timestamp: number): string => {
4240
export const DevboxDetailPage = ({
4341
devbox: initialDevbox,
4442
onBack,
45-
onSSHRequest,
4643
}: DevboxDetailPageProps) => {
4744
const isMounted = React.useRef(true);
4845

@@ -602,7 +599,6 @@ export const DevboxDetailPage = ({
602599
]}
603600
initialOperation={selectedOp?.key}
604601
skipOperationsMenu={true}
605-
onSSHRequest={onSSHRequest}
606602
/>
607603
);
608604
}

0 commit comments

Comments
 (0)