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.
@@ -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.1.5",
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
  }
@@ -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
+ });