codex-webstrapper 0.1.5 → 0.2.5
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/bin/codex-webstrap.sh +48 -2
- package/package.json +2 -1
- package/src/bridge-shim.js +12 -0
- package/src/bun-pty-bridge.mjs +161 -0
- package/src/message-router.mjs +1303 -68
- package/src/server.mjs +25 -0
package/bin/codex-webstrap.sh
CHANGED
|
@@ -17,9 +17,20 @@ OPEN_FLAG="0"
|
|
|
17
17
|
TOKEN_FILE="${CODEX_WEBSTRAP_TOKEN_FILE:-}"
|
|
18
18
|
CODEX_APP="${CODEX_WEBSTRAP_CODEX_APP:-}"
|
|
19
19
|
INTERNAL_WS_PORT="${CODEX_WEBSTRAP_INTERNAL_WS_PORT:-38080}"
|
|
20
|
+
PORT_SET="0"
|
|
21
|
+
BIND_SET="0"
|
|
20
22
|
COPY_FLAG="0"
|
|
21
23
|
COMMAND="serve"
|
|
22
24
|
|
|
25
|
+
# Treat non-empty env overrides as explicit selections so open-mode runtime
|
|
26
|
+
# autodetection cannot replace them.
|
|
27
|
+
if [[ -n "${CODEX_WEBSTRAP_PORT:-}" ]]; then
|
|
28
|
+
PORT_SET="1"
|
|
29
|
+
fi
|
|
30
|
+
if [[ -n "${CODEX_WEBSTRAP_BIND:-}" ]]; then
|
|
31
|
+
BIND_SET="1"
|
|
32
|
+
fi
|
|
33
|
+
|
|
23
34
|
DEFAULT_TOKEN_FILE="${HOME}/.codex-webstrap/token"
|
|
24
35
|
if [[ -z "$TOKEN_FILE" ]]; then
|
|
25
36
|
TOKEN_FILE="$DEFAULT_TOKEN_FILE"
|
|
@@ -70,14 +81,20 @@ fi
|
|
|
70
81
|
|
|
71
82
|
while [[ $# -gt 0 ]]; do
|
|
72
83
|
case "$1" in
|
|
84
|
+
open)
|
|
85
|
+
COMMAND="open"
|
|
86
|
+
shift
|
|
87
|
+
;;
|
|
73
88
|
--port)
|
|
74
89
|
require_value "$1" "${2:-}"
|
|
75
90
|
PORT="$2"
|
|
91
|
+
PORT_SET="1"
|
|
76
92
|
shift 2
|
|
77
93
|
;;
|
|
78
94
|
--bind)
|
|
79
95
|
require_value "$1" "${2:-}"
|
|
80
96
|
BIND="$2"
|
|
97
|
+
BIND_SET="1"
|
|
81
98
|
shift 2
|
|
82
99
|
;;
|
|
83
100
|
--token-file)
|
|
@@ -126,6 +143,36 @@ if [[ "$COMMAND" == "open" ]]; then
|
|
|
126
143
|
exit 1
|
|
127
144
|
fi
|
|
128
145
|
|
|
146
|
+
if [[ "$PORT_SET" == "0" || "$BIND_SET" == "0" ]]; then
|
|
147
|
+
RUNTIME_FILE="${TOKEN_FILE}.runtime"
|
|
148
|
+
if [[ -f "$RUNTIME_FILE" ]]; then
|
|
149
|
+
RUNTIME_VALUES="$(node -e 'const fs = require("node:fs"); const filePath = process.argv[2]; try { const data = JSON.parse(fs.readFileSync(filePath, "utf8")); const bind = data.bind || ""; const port = data.port || ""; if (!bind || !port) { process.exit(1); } process.stdout.write(`${bind}\n${port}\n`); } catch { process.exit(1); }' "$TOKEN_FILE" "$RUNTIME_FILE" 2>/dev/null || true)"
|
|
150
|
+
if [[ -n "$RUNTIME_VALUES" ]]; then
|
|
151
|
+
RUNTIME_BIND="$(printf '%s' "$RUNTIME_VALUES" | sed -n '1p')"
|
|
152
|
+
RUNTIME_PORT="$(printf '%s' "$RUNTIME_VALUES" | sed -n '2p')"
|
|
153
|
+
if [[ -n "$RUNTIME_BIND" && -n "$RUNTIME_PORT" ]]; then
|
|
154
|
+
if command -v curl >/dev/null 2>&1; then
|
|
155
|
+
if curl -fsS --max-time 1 --connect-timeout 1 "http://${RUNTIME_BIND}:${RUNTIME_PORT}/__webstrapper/healthz" >/dev/null 2>&1; then
|
|
156
|
+
if [[ "$BIND_SET" == "0" ]]; then
|
|
157
|
+
BIND="$RUNTIME_BIND"
|
|
158
|
+
fi
|
|
159
|
+
if [[ "$PORT_SET" == "0" ]]; then
|
|
160
|
+
PORT="$RUNTIME_PORT"
|
|
161
|
+
fi
|
|
162
|
+
fi
|
|
163
|
+
else
|
|
164
|
+
if [[ "$BIND_SET" == "0" ]]; then
|
|
165
|
+
BIND="$RUNTIME_BIND"
|
|
166
|
+
fi
|
|
167
|
+
if [[ "$PORT_SET" == "0" ]]; then
|
|
168
|
+
PORT="$RUNTIME_PORT"
|
|
169
|
+
fi
|
|
170
|
+
fi
|
|
171
|
+
fi
|
|
172
|
+
fi
|
|
173
|
+
fi
|
|
174
|
+
fi
|
|
175
|
+
|
|
129
176
|
ENCODED_TOKEN="$(node -e 'process.stdout.write(encodeURIComponent(process.argv[1] || ""))' "$TOKEN")"
|
|
130
177
|
AUTH_URL="http://${BIND}:${PORT}/__webstrapper/auth?token=${ENCODED_TOKEN}"
|
|
131
178
|
|
|
@@ -134,9 +181,8 @@ if [[ "$COMMAND" == "open" ]]; then
|
|
|
134
181
|
echo "pbcopy not found in PATH" >&2
|
|
135
182
|
exit 1
|
|
136
183
|
fi
|
|
137
|
-
printf '%s' "$AUTH_URL" | pbcopy
|
|
184
|
+
printf '%s' "$AUTH_URL" | pbcopy >/dev/null 2>&1
|
|
138
185
|
printf 'Copied auth URL to clipboard.\n'
|
|
139
|
-
printf '%s\n' "$AUTH_URL"
|
|
140
186
|
else
|
|
141
187
|
open "$AUTH_URL"
|
|
142
188
|
printf 'Opened auth URL in browser.\n'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-webstrapper",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Web wrapper for Codex desktop assets with bridge + token auth",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@electron/asar": "^4.0.1",
|
|
26
|
+
"bun-pty": "^0.4.8",
|
|
26
27
|
"ws": "^8.18.0"
|
|
27
28
|
}
|
|
28
29
|
}
|
package/src/bridge-shim.js
CHANGED
|
@@ -599,6 +599,18 @@
|
|
|
599
599
|
}
|
|
600
600
|
|
|
601
601
|
function sendMessageFromView(payload) {
|
|
602
|
+
if (payload?.type === "open-in-browser") {
|
|
603
|
+
const target = payload?.url || payload?.href || null;
|
|
604
|
+
if (target) {
|
|
605
|
+
const opened = window.open(target, "_blank", "noopener,noreferrer");
|
|
606
|
+
if (!opened) {
|
|
607
|
+
// Fallback when popup is blocked.
|
|
608
|
+
window.location.href = target;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return Promise.resolve();
|
|
612
|
+
}
|
|
613
|
+
|
|
602
614
|
sendEnvelope({
|
|
603
615
|
type: "view-message",
|
|
604
616
|
payload
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { spawn } from "bun-pty";
|
|
2
|
+
|
|
3
|
+
function toErrorMessage(error) {
|
|
4
|
+
if (!error) {
|
|
5
|
+
return "unknown_error";
|
|
6
|
+
}
|
|
7
|
+
if (typeof error === "string") {
|
|
8
|
+
return error;
|
|
9
|
+
}
|
|
10
|
+
if (typeof error.message === "string" && error.message.length > 0) {
|
|
11
|
+
return error.message;
|
|
12
|
+
}
|
|
13
|
+
return String(error);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function send(payload) {
|
|
17
|
+
try {
|
|
18
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
19
|
+
} catch {
|
|
20
|
+
// Ignore JSON serialization/output failures.
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseConfig(raw) {
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(raw || "{}");
|
|
27
|
+
if (!parsed || typeof parsed !== "object") {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return parsed;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function toDimension(value, fallback) {
|
|
37
|
+
const parsed = Number(value);
|
|
38
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
39
|
+
return fallback;
|
|
40
|
+
}
|
|
41
|
+
return Math.max(1, Math.floor(parsed));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const config = parseConfig(process.env.CODEX_WEBSTRAP_BUN_PTY_CONFIG || "");
|
|
45
|
+
if (!config || typeof config.file !== "string" || config.file.length === 0) {
|
|
46
|
+
send({ type: "error", message: "Invalid bun-pty config." });
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let terminal;
|
|
51
|
+
try {
|
|
52
|
+
terminal = spawn(
|
|
53
|
+
config.file,
|
|
54
|
+
Array.isArray(config.args) ? config.args.filter((entry) => typeof entry === "string") : [],
|
|
55
|
+
{
|
|
56
|
+
name: typeof config.term === "string" && config.term.length > 0 ? config.term : "xterm-256color",
|
|
57
|
+
cols: toDimension(config.cols, 120),
|
|
58
|
+
rows: toDimension(config.rows, 30),
|
|
59
|
+
cwd: typeof config.cwd === "string" && config.cwd.length > 0 ? config.cwd : process.cwd(),
|
|
60
|
+
env: config.env && typeof config.env === "object" ? config.env : process.env
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
send({
|
|
65
|
+
type: "error",
|
|
66
|
+
message: `Failed to start bun-pty terminal: ${toErrorMessage(error)}`
|
|
67
|
+
});
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let exiting = false;
|
|
72
|
+
const requestExit = (code = 0) => {
|
|
73
|
+
if (exiting) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
exiting = true;
|
|
77
|
+
process.exit(code);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
terminal.onData((data) => {
|
|
81
|
+
send({
|
|
82
|
+
type: "data",
|
|
83
|
+
data
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
terminal.onExit((exit) => {
|
|
88
|
+
send({
|
|
89
|
+
type: "exit",
|
|
90
|
+
exitCode: typeof exit?.exitCode === "number" ? exit.exitCode : null,
|
|
91
|
+
signal: exit?.signal ?? null
|
|
92
|
+
});
|
|
93
|
+
requestExit(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
let inputBuffer = "";
|
|
97
|
+
process.stdin.setEncoding("utf8");
|
|
98
|
+
process.stdin.on("data", (chunk) => {
|
|
99
|
+
inputBuffer += chunk;
|
|
100
|
+
|
|
101
|
+
for (;;) {
|
|
102
|
+
const newlineAt = inputBuffer.indexOf("\n");
|
|
103
|
+
if (newlineAt < 0) {
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const line = inputBuffer.slice(0, newlineAt);
|
|
108
|
+
inputBuffer = inputBuffer.slice(newlineAt + 1);
|
|
109
|
+
|
|
110
|
+
if (line.trim().length === 0) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let command;
|
|
115
|
+
try {
|
|
116
|
+
command = JSON.parse(line);
|
|
117
|
+
} catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
if (command?.type === "write") {
|
|
123
|
+
terminal.write(typeof command.data === "string" ? command.data : "");
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (command?.type === "resize") {
|
|
128
|
+
terminal.resize(toDimension(command.cols, 120), toDimension(command.rows, 30));
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (command?.type === "close") {
|
|
133
|
+
terminal.kill("SIGTERM");
|
|
134
|
+
requestExit(0);
|
|
135
|
+
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
send({
|
|
138
|
+
type: "error",
|
|
139
|
+
message: toErrorMessage(error)
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
process.on("SIGTERM", () => {
|
|
146
|
+
try {
|
|
147
|
+
terminal.kill("SIGTERM");
|
|
148
|
+
} catch {
|
|
149
|
+
// Ignore.
|
|
150
|
+
}
|
|
151
|
+
requestExit(0);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
process.on("SIGINT", () => {
|
|
155
|
+
try {
|
|
156
|
+
terminal.kill("SIGTERM");
|
|
157
|
+
} catch {
|
|
158
|
+
// Ignore.
|
|
159
|
+
}
|
|
160
|
+
requestExit(0);
|
|
161
|
+
});
|