@makerbi/remodex 1.3.8

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.
@@ -0,0 +1,21 @@
1
+ // FILE: codex-home.js
2
+ // Purpose: Resolves local Codex cache paths shared by bridge services.
3
+ // Layer: CLI helper
4
+ // Exports: resolveCodexHome, resolveCodexGeneratedImagesRoot
5
+ // Depends on: os, path
6
+
7
+ const os = require("os");
8
+ const path = require("path");
9
+
10
+ function resolveCodexHome() {
11
+ return process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
12
+ }
13
+
14
+ function resolveCodexGeneratedImagesRoot() {
15
+ return path.join(resolveCodexHome(), "generated_images");
16
+ }
17
+
18
+ module.exports = {
19
+ resolveCodexGeneratedImagesRoot,
20
+ resolveCodexHome,
21
+ };
@@ -0,0 +1,353 @@
1
+ // FILE: codex-transport.js
2
+ // Purpose: Abstracts the Codex-side transport so the bridge can talk to either a spawned app-server or an existing WebSocket endpoint.
3
+ // Layer: CLI helper
4
+ // Exports: createCodexTransport
5
+ // Depends on: child_process, fs, path, ws
6
+
7
+ const { spawn } = require("child_process");
8
+ const fs = require("fs");
9
+ const path = require("path");
10
+ const WebSocket = require("ws");
11
+
12
+ function createCodexTransport({
13
+ endpoint = "",
14
+ env = process.env,
15
+ appPath = "",
16
+ spawnImpl = spawn,
17
+ WebSocketImpl = WebSocket,
18
+ } = {}) {
19
+ if (endpoint) {
20
+ return createWebSocketTransport({ endpoint, WebSocketImpl });
21
+ }
22
+
23
+ return createSpawnTransport({ env, appPath, spawnImpl });
24
+ }
25
+
26
+ function createSpawnTransport({ env, appPath, spawnImpl = spawn }) {
27
+ const launchPlans = createCodexLaunchPlans({ env, appPath });
28
+ let launchIndex = -1;
29
+ let activeLaunch = null;
30
+ let codex = null;
31
+ let stdoutBuffer = "";
32
+ let stderrBuffer = "";
33
+ let didRequestShutdown = false;
34
+ let didReportError = false;
35
+ const listeners = createListenerBag();
36
+
37
+ spawnNextLaunch();
38
+
39
+ return {
40
+ mode: "spawn",
41
+ describe() {
42
+ return activeLaunch?.description || launchPlans[0]?.description || "`codex app-server`";
43
+ },
44
+ send(message) {
45
+ if (!codex.stdin.writable || codex.stdin.destroyed || codex.stdin.writableEnded) {
46
+ return;
47
+ }
48
+
49
+ codex.stdin.write(message.endsWith("\n") ? message : `${message}\n`);
50
+ },
51
+ onMessage(handler) {
52
+ listeners.onMessage = handler;
53
+ },
54
+ onClose(handler) {
55
+ listeners.onClose = handler;
56
+ },
57
+ onError(handler) {
58
+ listeners.onError = handler;
59
+ },
60
+ onStarted(handler) {
61
+ listeners.onStarted = handler;
62
+ },
63
+ shutdown() {
64
+ didRequestShutdown = true;
65
+ shutdownCodexProcess(codex);
66
+ },
67
+ };
68
+
69
+ // Retries the launch once with the bundled desktop binary when the shell-visible
70
+ // `codex` command is unavailable in daemon environments like launchd.
71
+ function spawnNextLaunch() {
72
+ launchIndex += 1;
73
+ activeLaunch = launchPlans[launchIndex] || null;
74
+ if (!activeLaunch) {
75
+ return;
76
+ }
77
+
78
+ stdoutBuffer = "";
79
+ stderrBuffer = "";
80
+ codex = spawnImpl(activeLaunch.command, activeLaunch.args, activeLaunch.options);
81
+ attachChildListeners(codex, activeLaunch);
82
+ }
83
+
84
+ function attachChildListeners(child, launch) {
85
+ child.on("spawn", () => {
86
+ if (child !== codex) {
87
+ return;
88
+ }
89
+
90
+ listeners.emitStarted({
91
+ mode: "spawn",
92
+ launchDescription: launch.description,
93
+ });
94
+ });
95
+ child.on("error", (error) => {
96
+ if (child !== codex) {
97
+ return;
98
+ }
99
+
100
+ if (!didRequestShutdown && shouldRetryLaunchError(error, launchIndex, launchPlans)) {
101
+ spawnNextLaunch();
102
+ return;
103
+ }
104
+
105
+ didReportError = true;
106
+ listeners.emitError(error);
107
+ });
108
+ child.on("close", (code, signal) => {
109
+ if (child !== codex) {
110
+ return;
111
+ }
112
+
113
+ if (!didRequestShutdown && !didReportError && code !== 0) {
114
+ didReportError = true;
115
+ listeners.emitError(createCodexCloseError({
116
+ code,
117
+ signal,
118
+ stderrBuffer,
119
+ launchDescription: launch.description,
120
+ }));
121
+ return;
122
+ }
123
+
124
+ listeners.emitClose(code, signal);
125
+ });
126
+ // Ignore broken-pipe shutdown noise once the child is already going away.
127
+ child.stdin.on("error", (error) => {
128
+ if (child !== codex) {
129
+ return;
130
+ }
131
+
132
+ if (didRequestShutdown && isIgnorableStdinShutdownError(error)) {
133
+ return;
134
+ }
135
+
136
+ if (isIgnorableStdinShutdownError(error)) {
137
+ return;
138
+ }
139
+
140
+ didReportError = true;
141
+ listeners.emitError(error);
142
+ });
143
+ // Keep stderr muted during normal operation, but preserve enough output to
144
+ // explain launch failures when the child exits before the bridge can use it.
145
+ child.stderr.on("data", (chunk) => {
146
+ if (child !== codex) {
147
+ return;
148
+ }
149
+ stderrBuffer = appendOutputBuffer(stderrBuffer, chunk.toString("utf8"));
150
+ });
151
+
152
+ child.stdout.on("data", (chunk) => {
153
+ if (child !== codex) {
154
+ return;
155
+ }
156
+ stdoutBuffer += chunk.toString("utf8");
157
+ const lines = stdoutBuffer.split("\n");
158
+ stdoutBuffer = lines.pop() || "";
159
+
160
+ for (const line of lines) {
161
+ const trimmedLine = line.trim();
162
+ if (trimmedLine) {
163
+ listeners.emitMessage(trimmedLine);
164
+ }
165
+ }
166
+ });
167
+ }
168
+ }
169
+
170
+ // Builds a single, platform-aware launch path so the bridge never "guesses"
171
+ // between multiple commands and accidentally starts duplicate runtimes.
172
+ function createCodexLaunchPlans({
173
+ env,
174
+ appPath = "",
175
+ platform = process.platform,
176
+ fsImpl = fs,
177
+ pathImpl = path,
178
+ } = {}) {
179
+ const sharedOptions = {
180
+ stdio: ["pipe", "pipe", "pipe"],
181
+ env: { ...env },
182
+ };
183
+
184
+ if (platform === "win32") {
185
+ return [{
186
+ command: env.ComSpec || "cmd.exe",
187
+ args: ["/d", "/c", "codex app-server"],
188
+ options: {
189
+ ...sharedOptions,
190
+ windowsHide: true,
191
+ },
192
+ description: "`cmd.exe /d /c codex app-server`",
193
+ }];
194
+ }
195
+
196
+ const launches = [{
197
+ command: "codex",
198
+ args: ["app-server"],
199
+ options: sharedOptions,
200
+ description: "`codex app-server`",
201
+ }];
202
+
203
+ const bundledCommand = buildBundledCodexPath(appPath, { fsImpl, pathImpl });
204
+ if (bundledCommand) {
205
+ launches.push({
206
+ command: bundledCommand,
207
+ args: ["app-server"],
208
+ options: sharedOptions,
209
+ description: `\`${bundledCommand} app-server\``,
210
+ });
211
+ }
212
+
213
+ return launches;
214
+ }
215
+
216
+ function buildBundledCodexPath(appPath, { fsImpl = fs, pathImpl = path } = {}) {
217
+ if (typeof appPath !== "string" || !appPath.trim()) {
218
+ return "";
219
+ }
220
+
221
+ const candidate = pathImpl.join(appPath.trim(), "Contents", "Resources", "codex");
222
+ return isLaunchableFile(candidate, { fsImpl }) ? candidate : "";
223
+ }
224
+
225
+ function isLaunchableFile(candidatePath, { fsImpl = fs } = {}) {
226
+ try {
227
+ return fsImpl.statSync(candidatePath).isFile();
228
+ } catch {
229
+ return false;
230
+ }
231
+ }
232
+
233
+ // Stops the exact process tree we launched on Windows so the shell wrapper
234
+ // does not leave a child Codex process running in the background.
235
+ function shutdownCodexProcess(codex) {
236
+ if (codex.killed || codex.exitCode !== null) {
237
+ return;
238
+ }
239
+
240
+ if (process.platform === "win32" && codex.pid) {
241
+ const killer = spawn("taskkill", ["/pid", String(codex.pid), "/t", "/f"], {
242
+ stdio: "ignore",
243
+ windowsHide: true,
244
+ });
245
+ killer.on("error", () => {
246
+ codex.kill();
247
+ });
248
+ return;
249
+ }
250
+
251
+ codex.kill("SIGTERM");
252
+ }
253
+
254
+ function createCodexCloseError({ code, signal, stderrBuffer, launchDescription }) {
255
+ const details = stderrBuffer.trim();
256
+ const reason = details || `Process exited with code ${code}${signal ? ` (signal: ${signal})` : ""}.`;
257
+ return new Error(`Codex launcher ${launchDescription} failed: ${reason}`);
258
+ }
259
+
260
+ function appendOutputBuffer(buffer, chunk) {
261
+ const next = `${buffer}${chunk}`;
262
+ return next.slice(-4_096);
263
+ }
264
+
265
+ function isIgnorableStdinShutdownError(error) {
266
+ return error?.code === "EPIPE" || error?.code === "ERR_STREAM_DESTROYED";
267
+ }
268
+
269
+ function shouldRetryLaunchError(error, launchIndex, launchPlans) {
270
+ return error?.code === "ENOENT" && launchIndex < launchPlans.length - 1;
271
+ }
272
+
273
+ function createWebSocketTransport({ endpoint, WebSocketImpl = WebSocket }) {
274
+ const socket = new WebSocketImpl(endpoint);
275
+ const listeners = createListenerBag();
276
+ const openState = WebSocketImpl.OPEN ?? WebSocket.OPEN ?? 1;
277
+ const connectingState = WebSocketImpl.CONNECTING ?? WebSocket.CONNECTING ?? 0;
278
+
279
+ socket.on("message", (chunk) => {
280
+ const message = typeof chunk === "string" ? chunk : chunk.toString("utf8");
281
+ if (message.trim()) {
282
+ listeners.emitMessage(message);
283
+ }
284
+ });
285
+ socket.on("open", () => {
286
+ listeners.emitStarted({
287
+ mode: "websocket",
288
+ launchDescription: endpoint,
289
+ });
290
+ });
291
+
292
+ socket.on("close", (code, reason) => {
293
+ const safeReason = reason ? reason.toString("utf8") : "no reason";
294
+ listeners.emitClose(code, safeReason);
295
+ });
296
+
297
+ socket.on("error", (error) => listeners.emitError(error));
298
+
299
+ return {
300
+ mode: "websocket",
301
+ describe() {
302
+ return endpoint;
303
+ },
304
+ send(message) {
305
+ if (socket.readyState === openState) {
306
+ socket.send(message);
307
+ }
308
+ },
309
+ onMessage(handler) {
310
+ listeners.onMessage = handler;
311
+ },
312
+ onClose(handler) {
313
+ listeners.onClose = handler;
314
+ },
315
+ onError(handler) {
316
+ listeners.onError = handler;
317
+ },
318
+ onStarted(handler) {
319
+ listeners.onStarted = handler;
320
+ },
321
+ shutdown() {
322
+ if (socket.readyState === openState || socket.readyState === connectingState) {
323
+ socket.close();
324
+ }
325
+ },
326
+ };
327
+ }
328
+
329
+ function createListenerBag() {
330
+ return {
331
+ onMessage: null,
332
+ onClose: null,
333
+ onError: null,
334
+ onStarted: null,
335
+ emitMessage(message) {
336
+ this.onMessage?.(message);
337
+ },
338
+ emitClose(...args) {
339
+ this.onClose?.(...args);
340
+ },
341
+ emitError(error) {
342
+ this.onError?.(error);
343
+ },
344
+ emitStarted(info) {
345
+ this.onStarted?.(info);
346
+ },
347
+ };
348
+ }
349
+
350
+ module.exports = {
351
+ createCodexLaunchPlans,
352
+ createCodexTransport,
353
+ };
@@ -0,0 +1,153 @@
1
+ // FILE: daemon-state.js
2
+ // Purpose: Persists macOS service config/runtime state outside the repo for the launchd bridge flow.
3
+ // Layer: CLI helper
4
+ // Exports: path resolvers plus read/write helpers for daemon config, pairing payloads, and service status.
5
+ // Depends on: fs, os, path
6
+
7
+ const fs = require("fs");
8
+ const os = require("os");
9
+ const path = require("path");
10
+
11
+ const DEFAULT_STATE_DIR_NAME = ".remodex";
12
+ const DAEMON_CONFIG_FILE = "daemon-config.json";
13
+ const PAIRING_SESSION_FILE = "pairing-session.json";
14
+ const BRIDGE_STATUS_FILE = "bridge-status.json";
15
+ const LOGS_DIR = "logs";
16
+ const BRIDGE_STDOUT_LOG_FILE = "bridge.stdout.log";
17
+ const BRIDGE_STDERR_LOG_FILE = "bridge.stderr.log";
18
+
19
+ // Reuses the existing Remodex state root so daemon mode keeps the same local-first storage model.
20
+ function resolveRemodexStateDir({ env = process.env, osImpl = os } = {}) {
21
+ return normalizeNonEmptyString(env.REMODEX_DEVICE_STATE_DIR)
22
+ || path.join(osImpl.homedir(), DEFAULT_STATE_DIR_NAME);
23
+ }
24
+
25
+ function resolveDaemonConfigPath(options = {}) {
26
+ return path.join(resolveRemodexStateDir(options), DAEMON_CONFIG_FILE);
27
+ }
28
+
29
+ function resolvePairingSessionPath(options = {}) {
30
+ return path.join(resolveRemodexStateDir(options), PAIRING_SESSION_FILE);
31
+ }
32
+
33
+ function resolveBridgeStatusPath(options = {}) {
34
+ return path.join(resolveRemodexStateDir(options), BRIDGE_STATUS_FILE);
35
+ }
36
+
37
+ function resolveBridgeLogsDir(options = {}) {
38
+ return path.join(resolveRemodexStateDir(options), LOGS_DIR);
39
+ }
40
+
41
+ function resolveBridgeStdoutLogPath(options = {}) {
42
+ return path.join(resolveBridgeLogsDir(options), BRIDGE_STDOUT_LOG_FILE);
43
+ }
44
+
45
+ function resolveBridgeStderrLogPath(options = {}) {
46
+ return path.join(resolveBridgeLogsDir(options), BRIDGE_STDERR_LOG_FILE);
47
+ }
48
+
49
+ function writeDaemonConfig(config, options = {}) {
50
+ writeJsonFile(resolveDaemonConfigPath(options), config, options);
51
+ }
52
+
53
+ function readDaemonConfig(options = {}) {
54
+ return readJsonFile(resolveDaemonConfigPath(options), options);
55
+ }
56
+
57
+ // Persists the pairing payload plus any short recovery code so foreground CLI commands can render pairing locally.
58
+ function writePairingSession(pairingSessionOrPayload, { now = () => Date.now(), ...options } = {}) {
59
+ const pairingSession = pairingSessionOrPayload?.pairingPayload
60
+ ? pairingSessionOrPayload
61
+ : { pairingPayload: pairingSessionOrPayload };
62
+ writeJsonFile(resolvePairingSessionPath(options), {
63
+ createdAt: new Date(now()).toISOString(),
64
+ ...pairingSession,
65
+ }, options);
66
+ }
67
+
68
+ function readPairingSession(options = {}) {
69
+ return readJsonFile(resolvePairingSessionPath(options), options);
70
+ }
71
+
72
+ function clearPairingSession({ fsImpl = fs, ...options } = {}) {
73
+ removeFile(resolvePairingSessionPath(options), fsImpl);
74
+ }
75
+
76
+ // Captures the last known service heartbeat so `remodex status` does not depend on launchctl output alone.
77
+ function writeBridgeStatus(status, { now = () => Date.now(), ...options } = {}) {
78
+ writeJsonFile(resolveBridgeStatusPath(options), {
79
+ ...status,
80
+ updatedAt: new Date(now()).toISOString(),
81
+ }, options);
82
+ }
83
+
84
+ function readBridgeStatus(options = {}) {
85
+ return readJsonFile(resolveBridgeStatusPath(options), options);
86
+ }
87
+
88
+ function clearBridgeStatus({ fsImpl = fs, ...options } = {}) {
89
+ removeFile(resolveBridgeStatusPath(options), fsImpl);
90
+ }
91
+
92
+ function ensureRemodexStateDir({ fsImpl = fs, ...options } = {}) {
93
+ fsImpl.mkdirSync(resolveRemodexStateDir(options), { recursive: true });
94
+ }
95
+
96
+ function ensureRemodexLogsDir({ fsImpl = fs, ...options } = {}) {
97
+ fsImpl.mkdirSync(resolveBridgeLogsDir(options), { recursive: true });
98
+ }
99
+
100
+ function writeJsonFile(targetPath, value, { fsImpl = fs } = {}) {
101
+ fsImpl.mkdirSync(path.dirname(targetPath), { recursive: true });
102
+ const serialized = JSON.stringify(value, null, 2);
103
+ fsImpl.writeFileSync(targetPath, serialized, { mode: 0o600 });
104
+ try {
105
+ fsImpl.chmodSync(targetPath, 0o600);
106
+ } catch {
107
+ // Best-effort only on filesystems without POSIX mode support.
108
+ }
109
+ }
110
+
111
+ function readJsonFile(targetPath, { fsImpl = fs } = {}) {
112
+ if (!fsImpl.existsSync(targetPath)) {
113
+ return null;
114
+ }
115
+
116
+ try {
117
+ return JSON.parse(fsImpl.readFileSync(targetPath, "utf8"));
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+
123
+ function removeFile(targetPath, fsImpl) {
124
+ try {
125
+ fsImpl.rmSync(targetPath, { force: true });
126
+ } catch {
127
+ // Missing runtime files should not block control-plane commands.
128
+ }
129
+ }
130
+
131
+ function normalizeNonEmptyString(value) {
132
+ return typeof value === "string" && value.trim() ? value.trim() : "";
133
+ }
134
+
135
+ module.exports = {
136
+ clearBridgeStatus,
137
+ clearPairingSession,
138
+ ensureRemodexLogsDir,
139
+ ensureRemodexStateDir,
140
+ readBridgeStatus,
141
+ readDaemonConfig,
142
+ readPairingSession,
143
+ resolveBridgeLogsDir,
144
+ resolveBridgeStderrLogPath,
145
+ resolveBridgeStatusPath,
146
+ resolveBridgeStdoutLogPath,
147
+ resolveDaemonConfigPath,
148
+ resolvePairingSessionPath,
149
+ resolveRemodexStateDir,
150
+ writeBridgeStatus,
151
+ writeDaemonConfig,
152
+ writePairingSession,
153
+ };