covebox 0.1.0 → 0.1.1

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.
@@ -242,11 +242,6 @@ async function handleCommand(client, line, rl) {
242
242
  console.log(chalk.red("Usage: ssh <id>"));
243
243
  break;
244
244
  }
245
- const daytonaApiKey = process.env["DAYTONA_API_KEY"];
246
- if (!daytonaApiKey) {
247
- console.log(chalk.red("DAYTONA_API_KEY environment variable is required for shell access."));
248
- break;
249
- }
250
245
  const sandboxId = await resolveId(client, id);
251
246
  if (!sandboxId)
252
247
  break;
@@ -257,8 +252,9 @@ async function handleCommand(client, line, rl) {
257
252
  console.log(chalk.red(`Sandbox is ${sandbox.state}. Start it first with: start ${sandboxId.slice(0, 12)}`));
258
253
  break;
259
254
  }
260
- if (sandbox.noVncUrl) {
261
- console.log(`NoVNC: ${chalk.blue(sandbox.noVncUrl)}`);
255
+ if (!sandbox.ptyUrl) {
256
+ console.log(chalk.red("Shell access not available for this sandbox."));
257
+ break;
262
258
  }
263
259
  console.log(chalk.gray(`\nConnecting to ${sandboxId.slice(0, 12)}...\n`));
264
260
  // Close readline before PTY (removes its stdin listeners)
@@ -268,7 +264,7 @@ async function handleCommand(client, line, rl) {
268
264
  // Only remove keypress listener (readline's), not all listeners
269
265
  process.stdin.removeAllListeners("keypress");
270
266
  try {
271
- await runPtySession(sandboxId, daytonaApiKey);
267
+ await runPtySession(sandbox.ptyUrl);
272
268
  }
273
269
  catch (err) {
274
270
  console.error(chalk.red(`Connection failed: ${err}`));
@@ -1,9 +1,9 @@
1
1
  /**
2
- * PTY Client using Daytona SDK
2
+ * PTY Client using WebSocket
3
3
  *
4
- * Provides interactive terminal access to sandboxes using Daytona's native PTY support.
4
+ * Connects to the sandbox's PTY endpoint for interactive terminal access.
5
5
  */
6
6
  /**
7
- * Run an interactive PTY session using Daytona SDK
7
+ * Run an interactive PTY session
8
8
  */
9
- export declare function runPtySession(sandboxId: string, apiKey: string): Promise<void>;
9
+ export declare function runPtySession(ptyUrl: string): Promise<void>;
@@ -1,110 +1,181 @@
1
1
  /**
2
- * PTY Client using Daytona SDK
2
+ * PTY Client using WebSocket
3
3
  *
4
- * Provides interactive terminal access to sandboxes using Daytona's native PTY support.
4
+ * Connects to the sandbox's PTY endpoint for interactive terminal access.
5
5
  */
6
- import { Daytona } from "@daytonaio/sdk";
6
+ import WebSocket from "ws";
7
+ // Message types (must match cove-proxy/pty.go)
8
+ const MsgStdin = 0x00;
9
+ const MsgStdout = 0x01;
10
+ const MsgResize = 0x10;
11
+ const MsgCreateSession = 0x20;
12
+ const MsgAttachSession = 0x22;
13
+ const MsgCreateWindow = 0x30;
14
+ const MsgCreatePane = 0x40;
15
+ const MsgResponse = 0xf0;
16
+ const MsgError = 0xff;
7
17
  /**
8
- * Run an interactive PTY session using Daytona SDK
18
+ * Run an interactive PTY session
9
19
  */
10
- export async function runPtySession(sandboxId, apiKey) {
11
- const client = new Daytona({ apiKey });
12
- // Get terminal size
13
- const rows = process.stdout.rows || 24;
14
- const cols = process.stdout.columns || 80;
15
- let pty = null;
16
- let cleanedUp = false;
17
- let stdinHandler = null;
18
- let resizeHandler = null;
19
- const cleanup = async () => {
20
- if (cleanedUp)
21
- return;
22
- cleanedUp = true;
23
- // Remove event listeners first
24
- if (stdinHandler) {
25
- process.stdin.removeListener("data", stdinHandler);
26
- stdinHandler = null;
27
- }
28
- if (resizeHandler) {
29
- process.stdout.removeListener("resize", resizeHandler);
30
- resizeHandler = null;
31
- }
32
- // Restore terminal to cooked mode
33
- if (process.stdin.isTTY) {
34
- process.stdin.setRawMode(false);
35
- }
36
- // Reset encoding to default (buffer) so readline can manage it
37
- process.stdin.setEncoding("utf8");
38
- process.stdin.pause();
39
- // Disconnect PTY
40
- if (pty) {
41
- try {
42
- await pty.disconnect();
20
+ export async function runPtySession(ptyUrl) {
21
+ return new Promise((resolve, reject) => {
22
+ const rows = process.stdout.rows || 24;
23
+ const cols = process.stdout.columns || 80;
24
+ let ws = null;
25
+ let paneId = null;
26
+ let cleanedUp = false;
27
+ let stdinHandler = null;
28
+ let resizeHandler = null;
29
+ const cleanup = () => {
30
+ if (cleanedUp)
31
+ return;
32
+ cleanedUp = true;
33
+ if (stdinHandler) {
34
+ process.stdin.removeListener("data", stdinHandler);
35
+ stdinHandler = null;
43
36
  }
44
- catch {
45
- // Ignore disconnect errors
37
+ if (resizeHandler) {
38
+ process.stdout.removeListener("resize", resizeHandler);
39
+ resizeHandler = null;
46
40
  }
47
- pty = null;
48
- }
49
- };
50
- try {
51
- const sandbox = await client.get(sandboxId);
52
- // Create PTY session with unique ID
53
- const sessionId = `cli-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
54
- pty = await sandbox.process.createPty({
55
- id: sessionId,
56
- cols,
57
- rows,
58
- onData: (data) => {
59
- const text = typeof data === "string" ? data : new TextDecoder().decode(data);
60
- process.stdout.write(text);
61
- },
62
- });
63
- // Wait for connection to be established
64
- await pty.waitForConnection();
65
- // Set raw mode for stdin (disables local echo)
66
- if (process.stdin.isTTY) {
67
- process.stdin.setRawMode(true);
68
- }
69
- process.stdin.resume();
70
- process.stdin.setEncoding("utf8");
71
- // Handle stdin - use named function so we can remove it later
72
- stdinHandler = (data) => {
73
- if (pty && !cleanedUp) {
74
- pty.sendInput(data.toString()).catch(() => {
75
- // Connection may have closed
76
- });
41
+ if (process.stdin.isTTY) {
42
+ process.stdin.setRawMode(false);
77
43
  }
44
+ process.stdin.setEncoding("utf8");
45
+ process.stdin.pause();
46
+ if (ws && ws.readyState === WebSocket.OPEN) {
47
+ ws.close();
48
+ }
49
+ ws = null;
78
50
  };
79
- process.stdin.on("data", stdinHandler);
80
- // Handle resize
81
- resizeHandler = () => {
82
- if (pty && !cleanedUp) {
83
- const newRows = process.stdout.rows || 24;
84
- const newCols = process.stdout.columns || 80;
85
- pty.resize(newCols, newRows).catch(() => {
86
- // Ignore resize errors
87
- });
51
+ const sendBinary = (data) => {
52
+ if (ws && ws.readyState === WebSocket.OPEN) {
53
+ ws.send(data);
88
54
  }
89
55
  };
90
- process.stdout.on("resize", resizeHandler);
91
- // Handle process signals
92
- const handleSignal = async () => {
93
- await cleanup();
94
- process.exit(0);
56
+ const sendJson = (msgType, payload) => {
57
+ const json = JSON.stringify(payload);
58
+ const buf = Buffer.alloc(1 + json.length);
59
+ buf[0] = msgType;
60
+ buf.write(json, 1);
61
+ sendBinary(buf);
62
+ };
63
+ const sendStdin = (data) => {
64
+ if (paneId === null)
65
+ return;
66
+ const dataBuf = Buffer.from(data);
67
+ const buf = Buffer.alloc(5 + dataBuf.length);
68
+ buf[0] = MsgStdin;
69
+ buf.writeUInt32BE(paneId, 1);
70
+ dataBuf.copy(buf, 5);
71
+ sendBinary(buf);
72
+ };
73
+ const sendResize = (newRows, newCols) => {
74
+ if (paneId === null)
75
+ return;
76
+ const buf = Buffer.alloc(9);
77
+ buf[0] = MsgResize;
78
+ buf.writeUInt32BE(paneId, 1);
79
+ buf.writeUInt16BE(newRows, 5);
80
+ buf.writeUInt16BE(newCols, 7);
81
+ sendBinary(buf);
95
82
  };
96
- process.once("SIGINT", handleSignal);
97
- process.once("SIGTERM", handleSignal);
98
- // Wait for PTY to exit
99
- const result = await pty.wait();
100
- console.log(`\nSession ended (exit code: ${result.exitCode})`);
101
- }
102
- catch (error) {
103
- const message = error instanceof Error ? error.message : String(error);
104
- console.error(`\nPTY Error: ${message}`);
105
- throw error;
106
- }
107
- finally {
108
- await cleanup();
109
- }
83
+ try {
84
+ ws = new WebSocket(ptyUrl);
85
+ ws.on("open", () => {
86
+ // Create session
87
+ sendJson(MsgCreateSession, { name: "cli-session" });
88
+ });
89
+ ws.on("message", (data) => {
90
+ if (data.length < 1)
91
+ return;
92
+ const msgType = data[0];
93
+ const payload = data.slice(1);
94
+ if (msgType === MsgStdout && paneId !== null) {
95
+ // Skip pane ID (4 bytes), output the rest
96
+ const output = payload.slice(4);
97
+ process.stdout.write(output);
98
+ }
99
+ else if (msgType === MsgResponse) {
100
+ try {
101
+ const response = JSON.parse(payload.toString());
102
+ if (response.type === "session_created" && response.session) {
103
+ // Attach to session
104
+ const sessionId = response.session.ID;
105
+ const buf = Buffer.alloc(1 + 36);
106
+ buf[0] = MsgAttachSession;
107
+ buf.write(sessionId, 1);
108
+ sendBinary(buf);
109
+ }
110
+ else if (response.type === "attached") {
111
+ // Create window
112
+ sendJson(MsgCreateWindow, { name: "main" });
113
+ }
114
+ else if (response.type === "window_created" && response.window) {
115
+ // Create pane
116
+ sendJson(MsgCreatePane, {
117
+ windowId: response.window.ID,
118
+ rows,
119
+ cols,
120
+ });
121
+ }
122
+ else if (response.type === "pane_created" && response.pane) {
123
+ paneId = response.pane.ID;
124
+ // Now we're connected - set up terminal
125
+ if (process.stdin.isTTY) {
126
+ process.stdin.setRawMode(true);
127
+ }
128
+ process.stdin.resume();
129
+ process.stdin.setEncoding("utf8");
130
+ stdinHandler = (inputData) => {
131
+ if (!cleanedUp) {
132
+ sendStdin(inputData.toString());
133
+ }
134
+ };
135
+ process.stdin.on("data", stdinHandler);
136
+ resizeHandler = () => {
137
+ if (!cleanedUp) {
138
+ const newRows = process.stdout.rows || 24;
139
+ const newCols = process.stdout.columns || 80;
140
+ sendResize(newRows, newCols);
141
+ }
142
+ };
143
+ process.stdout.on("resize", resizeHandler);
144
+ }
145
+ }
146
+ catch {
147
+ // Ignore JSON parse errors
148
+ }
149
+ }
150
+ else if (msgType === MsgError) {
151
+ try {
152
+ const error = JSON.parse(payload.toString());
153
+ console.error(`\nPTY Error: ${error.error}`);
154
+ }
155
+ catch {
156
+ // Ignore
157
+ }
158
+ }
159
+ });
160
+ ws.on("close", () => {
161
+ cleanup();
162
+ resolve();
163
+ });
164
+ ws.on("error", (err) => {
165
+ cleanup();
166
+ reject(err);
167
+ });
168
+ // Handle signals
169
+ const handleSignal = () => {
170
+ cleanup();
171
+ resolve();
172
+ };
173
+ process.once("SIGINT", handleSignal);
174
+ process.once("SIGTERM", handleSignal);
175
+ }
176
+ catch (error) {
177
+ cleanup();
178
+ reject(error);
179
+ }
180
+ });
110
181
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "covebox",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "CLI for Cove sandboxes - cloud development environments",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,15 +18,16 @@
18
18
  "test:e2e": "tsx src/test-e2e.ts"
19
19
  },
20
20
  "dependencies": {
21
- "@daytonaio/sdk": "^0.115.2",
22
21
  "chalk": "^5.6.2",
23
22
  "cli-table3": "^0.6.5",
24
23
  "commander": "^12.1.0",
25
24
  "conf": "^13.1.0",
26
- "ora": "^8.2.0"
25
+ "ora": "^8.2.0",
26
+ "ws": "^8.18.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^22.15.32",
30
+ "@types/ws": "^8.5.13",
30
31
  "tsx": "^4.21.0",
31
32
  "typescript": "^5.7.3"
32
33
  }