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.
- package/dist/interactive.js +4 -8
- package/dist/pty-client.d.ts +4 -4
- package/dist/pty-client.js +168 -97
- package/package.json +4 -3
package/dist/interactive.js
CHANGED
|
@@ -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.
|
|
261
|
-
console.log(
|
|
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(
|
|
267
|
+
await runPtySession(sandbox.ptyUrl);
|
|
272
268
|
}
|
|
273
269
|
catch (err) {
|
|
274
270
|
console.error(chalk.red(`Connection failed: ${err}`));
|
package/dist/pty-client.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PTY Client using
|
|
2
|
+
* PTY Client using WebSocket
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Connects to the sandbox's PTY endpoint for interactive terminal access.
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
|
-
* Run an interactive PTY session
|
|
7
|
+
* Run an interactive PTY session
|
|
8
8
|
*/
|
|
9
|
-
export declare function runPtySession(
|
|
9
|
+
export declare function runPtySession(ptyUrl: string): Promise<void>;
|
package/dist/pty-client.js
CHANGED
|
@@ -1,110 +1,181 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PTY Client using
|
|
2
|
+
* PTY Client using WebSocket
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Connects to the sandbox's PTY endpoint for interactive terminal access.
|
|
5
5
|
*/
|
|
6
|
-
import
|
|
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
|
|
18
|
+
* Run an interactive PTY session
|
|
9
19
|
*/
|
|
10
|
-
export async function runPtySession(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
45
|
-
|
|
37
|
+
if (resizeHandler) {
|
|
38
|
+
process.stdout.removeListener("resize", resizeHandler);
|
|
39
|
+
resizeHandler = null;
|
|
46
40
|
}
|
|
47
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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.
|
|
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
|
}
|