remcodex 0.1.0-beta.10 β†’ 0.1.0-beta.11

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/README.md CHANGED
@@ -1,104 +1,45 @@
1
- # πŸš€ RemCodex
1
+ # RemCodex
2
2
 
3
- > Remote control for Codex.
4
- > From your browser and phone.
3
+ > Control Codex from anywhere. Even on your phone.
5
4
 
6
- Run Codex on one machine.
7
- Monitor, approve, and control the same session from another.
5
+ RemCodex is a local-first web UI for running, reviewing, approving, and resuming Codex sessions from your browser.
8
6
 
9
- 🌐 https://remcodex.com
7
+ It is built for the real workflow: long-running sessions, mobile check-ins, approval prompts, imported rollout history, and timeline-style execution flow.
10
8
 
11
- > Not a remote desktop. Not a proxy.
12
- > A local-first way to control Codex away from the terminal.
9
+ - Watch live Codex runs without staying in the terminal
10
+ - Approve sensitive actions from a cleaner UI
11
+ - Pick up the same session again after refresh, sleep, or reconnect
13
12
 
14
- ![RemCodex hero cover](docs/assets/hero-cover.png)
13
+ ## Status
15
14
 
16
- ---
15
+ This project is currently a **beta / developer preview**.
17
16
 
18
- ## ✨ What is RemCodex?
17
+ It is already usable for local and internal workflows, but it is not yet packaged as a one-click desktop app.
19
18
 
20
- RemCodex is **remote control for Codex**.
19
+ ## Why People Use It
21
20
 
22
- It lets you start Codex on one machine, then keep the same session visible,
23
- interruptible, and controllable from another.
21
+ Codex is powerful in the terminal, but many real workflows need a better control surface:
24
22
 
25
- - πŸ‘€ See what the AI is doing β€” in real time
26
- - βœ… Approve or reject actions before execution
27
- - ⏹ Interrupt or stop at any moment
28
- - πŸ“± Access your session from any device
29
- - πŸ”„ Sessions don’t break β€” they resume
23
+ - checking progress from your phone
24
+ - watching a long run without babysitting a terminal window
25
+ - approving writes from a cleaner interface
26
+ - reopening a session after refresh, sleep, or reconnect
27
+ - reviewing imported rollout history next to native sessions
30
28
 
31
- > One session. Any device.
29
+ RemCodex turns Codex's event stream into a browser-based workspace that is easier to follow, easier to resume, and easier to operate.
32
30
 
33
- ---
31
+ ## What It Does
34
32
 
35
- ## 🎬 A real workflow
33
+ - Run Codex sessions from a browser
34
+ - View sessions in a single-page workspace with a sidebar and execution timeline
35
+ - Follow streaming assistant output, commands, patches, and approvals
36
+ - Approve or reject file-system actions from the UI
37
+ - Import existing Codex rollout sessions from `~/.codex/sessions/...`
38
+ - Keep imported sessions in sync while they are still active
39
+ - Resume stale sessions after the page comes back from background
40
+ - Work well on desktop and on mobile
36
41
 
37
- You start a long Codex session on your machine.
38
-
39
- Then you leave your desk.
40
-
41
- On your phone:
42
-
43
- - you see progress in real time
44
- - you receive an approval request
45
- - you approve it
46
-
47
- The session continues instantly.
48
-
49
- > Everything else can disconnect β€” your session won’t.
50
-
51
- ---
52
-
53
- ## πŸ”₯ Why RemCodex exists
54
-
55
- AI coding agents are powerful.
56
- But today, they run like a black box.
57
-
58
- You either:
59
-
60
- - trust everything blindly
61
- - or sit in front of your terminal watching it
62
-
63
- RemCodex fixes that.
64
-
65
- > AI is no longer a black box.
66
-
67
- ---
68
-
69
- ## ⚑ What it does
70
-
71
- - Real-time execution timeline (messages, commands, approvals)
72
- - Human-in-the-loop command approval
73
- - Multi-device access to the same live session
74
- - Resume after refresh, sleep, or reconnect
75
- - Browser-based UI β€” **no extra client required**
76
- - Works with Codex CLI
77
-
78
- > No extra client install. Just open a browser.
79
- > Your code never leaves your machine.
80
-
81
- ---
82
-
83
- ## πŸš€ Quick start
84
-
85
- ```bash
86
- npx remcodex
87
- ```
88
-
89
- Then open:
90
-
91
- http://127.0.0.1:18840
92
-
93
- Access from another device:
94
-
95
- http://<your-ip>:18840
96
-
97
- > Runs entirely on your local machine. No cloud, no data upload.
98
-
99
- ---
100
-
101
- ## πŸ–₯ Screenshots
42
+ ## Screenshots
102
43
 
103
44
  ![RemCodex desktop workspace](docs/assets/hero-desktop.png)
104
45
 
@@ -114,203 +55,268 @@ http://<your-ip>:18840
114
55
 
115
56
  ![RemCodex imported Codex session](docs/assets/imported-session.png)
116
57
 
117
- > Bring imported Codex rollouts into the same workspace.
58
+ > Bring imported Codex rollouts into the same workspace and keep them easy to review.
118
59
 
119
- ---
60
+ ## Who It Is For
120
61
 
121
- ## 🧠 What it actually is
62
+ - developers who already use Codex locally
63
+ - people who want a browser-based control surface instead of raw terminal watching
64
+ - teams who want to review or monitor runs from another device on the same network
65
+ - anyone who wants approvals, timeline view, and imported rollout history in one place
122
66
 
123
- RemCodex is a **browser-based workspace for Codex sessions**.
67
+ ## Screens It Aims To Replace
124
68
 
125
- It is built for real workflows:
69
+ - terminal-only session watching
70
+ - ad-hoc mobile remote desktop checks
71
+ - raw log scrolling for approvals and command progress
72
+ - fragmented session history between local and imported rollouts
126
73
 
127
- - long-running sessions
128
- - mobile check-ins
129
- - approval prompts
130
- - imported rollout history
131
- - timeline-style execution flow
132
-
133
- Instead of raw terminal logs, you get a structured, visual timeline you can follow and control.
134
-
135
- ---
136
-
137
- ## 🧩 Current product shape
74
+ ## Current Product Shape
138
75
 
139
76
  - Single-page workspace UI
140
77
  - Left sidebar for session navigation
141
- - Right-side execution timeline
142
- - Fixed input composer
78
+ - Right-side timeline / execution flow for the active session
79
+ - Fixed composer at the bottom
143
80
  - Semantic timeline rendering for:
144
- - user messages
145
- - assistant output
146
- - thinking
147
- - commands
148
- - patches
149
- - approvals
150
- - system events
151
-
152
- ---
81
+ - user messages
82
+ - assistant commentary / final messages
83
+ - thinking
84
+ - commands
85
+ - patches
86
+ - approvals
87
+ - system events
153
88
 
154
- ## βš™οΈ Key behaviors
89
+ ## Tech Stack
155
90
 
156
- ### Approvals
91
+ - Backend: Node.js + TypeScript + Express + WebSocket
92
+ - Database: SQLite via `better-sqlite3`
93
+ - Terminal/runtime integration: `node-pty` + Codex app-server
94
+ - Frontend: zero-build static web app (`web/`)
95
+ - Markdown rendering: `markdown-it`
157
96
 
158
- - Writes inside working area β†’ auto allowed
159
- - Writes outside β†’ require approval
160
- - `Allow once` / `Allow for this turn` supported
161
- - Approval history stays visible in timeline
97
+ ## Requirements
162
98
 
163
- ---
99
+ Before running this project, you should have:
164
100
 
165
- ### Timeline
101
+ - Node.js 20.x installed
102
+ - Codex CLI installed and already working locally
103
+ - A machine where this app can access your local Codex data and working directories
166
104
 
167
- - Semantic rendering (not raw logs)
168
- - Commands grouped into readable activity blocks
169
- - Running / failed states clearly visible
170
- - Smooth streaming + recovery after refresh
105
+ This project is currently developed primarily around a local macOS workflow.
171
106
 
172
- ---
107
+ ## Quick Start
173
108
 
174
- ### Imported sessions
109
+ For the current developer preview, the recommended local install path is:
175
110
 
176
- - Import from `~/.codex/sessions/...`
177
- - Keep syncing if still active
178
- - Unified view with native sessions
111
+ ```bash
112
+ npm install
113
+ npm run build
114
+ npm link
115
+ remcodex start
116
+ ```
179
117
 
180
- ---
118
+ If you switch Node.js versions later, reinstall dependencies and relink or reinstall `remcodex`.
181
119
 
182
- ## 🧠 Architecture
120
+ Then open:
183
121
 
184
- ```
185
- Codex CLI β†’ Event stream β†’ Semantic layer β†’ Timeline β†’ Web UI
122
+ ```text
123
+ http://127.0.0.1:3000
186
124
  ```
187
125
 
188
- ---
126
+ If you want to make it reachable from your phone, expose the local machine on your LAN and open the same URL with your host IP.
189
127
 
190
- ## βš™οΈ Requirements
128
+ ## Local CLI
191
129
 
192
- - Node.js
193
- - Codex CLI (already working locally)
130
+ RemCodex already ships with a local CLI entrypoint, even though the npm package is not published yet.
194
131
 
195
- ---
132
+ If you do not want to run `npm link`, you can call the built CLI directly:
196
133
 
197
- ## βš™οΈ Configuration
134
+ ```bash
135
+ node dist/server/src/cli.js start --no-open
136
+ ```
198
137
 
199
- Default port: **18840**
138
+ Useful commands:
200
139
 
201
140
  ```bash
202
- PORT=18841 npx remcodex
141
+ node dist/server/src/cli.js doctor
142
+ node dist/server/src/cli.js start --no-open
143
+ node dist/server/src/cli.js version
203
144
  ```
204
145
 
205
- ---
146
+ Published package note:
206
147
 
207
- ## πŸ“¦ Install FAQ
148
+ - the npm package currently expects Node.js 20.x
149
+ - if `better-sqlite3` or `node-pty` reports a native module / `NODE_MODULE_VERSION` error, reinstall `remcodex` under the same Node.js version you will use to run it
208
150
 
209
- ### Why does `npx remcodex` hang on Linux?
151
+ Use a specific database:
210
152
 
211
- First install may compile native deps:
153
+ ```bash
154
+ node dist/server/src/cli.js start --db ~/.remcodex/remcodex-demo.db --no-open
155
+ node dist/server/src/cli.js doctor --db ~/.remcodex/remcodex-demo.db
156
+ ```
212
157
 
213
- - `better-sqlite3`
214
- - `node-pty`
158
+ Planned install target after the npm package is published:
215
159
 
216
- Make sure you have:
160
+ ```bash
161
+ npx remcodex
162
+ ```
217
163
 
218
- - `python3`
219
- - `make`
220
- - `g++`
164
+ ## Development
221
165
 
222
- ---
166
+ ```bash
167
+ npm install
168
+ npm run dev
169
+ ```
223
170
 
224
- ### Debug install issues
171
+ Smoke-test the published-package shape locally:
225
172
 
226
173
  ```bash
227
- npm install -g remcodex
228
- remcodex doctor
229
- remcodex start
174
+ npm run smoke:tarball
230
175
  ```
231
176
 
232
- ---
233
-
234
- ### Headless mode
177
+ Smoke-test a real isolated startup and `/health` check:
235
178
 
236
179
  ```bash
237
- npx remcodex --no-open
180
+ npm run smoke:start
238
181
  ```
239
182
 
240
- ---
183
+ ## How It Works
184
+
185
+ The app uses `codex app-server` as the primary runtime path.
241
186
 
242
- ## πŸ”§ How it works
187
+ At a high level:
243
188
 
244
- 1. Codex emits events
245
- 2. Backend stores them (SQLite)
246
- 3. Frontend loads timeline snapshot
247
- 4. Live updates stream via WebSocket
189
+ 1. Codex emits semantic events
190
+ 2. The backend stores them in SQLite
191
+ 3. The frontend reads an aggregated timeline view for initial load
192
+ 4. Live updates continue over WebSocket with catch-up after refresh
248
193
 
249
- Result:
194
+ This gives the UI:
250
195
 
196
+ - smooth streaming
251
197
  - recoverable sessions
252
- - real-time UI
253
- - consistent execution flow
198
+ - imported rollout support
199
+ - a consistent execution timeline instead of raw terminal logs
254
200
 
255
- ---
201
+ ## Key Behaviors
256
202
 
257
- ## πŸ“Š Status
203
+ ### Approvals
258
204
 
259
- - Beta / developer preview
260
- - Local-first architecture
261
- - No cloud dependency
205
+ - Writes inside the working area usually pass directly
206
+ - Writes outside the working area trigger approval
207
+ - `Allow once` approves only the current request
208
+ - `Allow for this turn` expands writable roots for the active turn
209
+ - Historical approval records stay visible in the timeline
210
+ - Only live approvals stay actionable in the bottom approval bar
262
211
 
263
- ---
212
+ ### Imported Codex Sessions
264
213
 
265
- ## πŸ—Ί Roadmap
214
+ - Existing Codex rollouts can be imported from local session history
215
+ - Imported sessions keep their own source metadata
216
+ - Imported sessions can continue syncing after you open them
217
+ - Native sessions are excluded from the import picker
266
218
 
267
- **Visibility**
219
+ ### Timeline and Execution Flow
268
220
 
269
- - fully observable execution
270
- - clear action timeline
221
+ - The UI renders semantic timeline items, not raw logs
222
+ - Commands and patches can be grouped into lighter activity summaries
223
+ - Running and failed commands remain visually important
224
+ - The final thinking placeholder appears only at the end of the active flow
271
225
 
272
- **Control**
226
+ ## Configuration
273
227
 
274
- - fine-grained approvals
275
- - safer execution
228
+ Supported environment variables:
276
229
 
277
- **Continuity**
230
+ - `PORT`
231
+ - `DATABASE_PATH`
232
+ - `PROJECT_ROOTS`
233
+ - `CODEX_COMMAND`
234
+ - `CODEX_MODE`
278
235
 
279
- - survive refresh / sleep
280
- - stable long runs
236
+ For launch screenshots or demo data, you can rebuild a clean demo database with:
281
237
 
282
- **Access**
238
+ ```bash
239
+ DATABASE_PATH="$HOME/.remcodex/remcodex-demo.db" ~/.nvm/versions/node/v20.19.5/bin/node scripts/seed-launch-demo-data.js --clean
240
+ ```
241
+ - `REMOTE_HOSTS`
242
+ - `ACTIVE_REMOTE_HOST`
243
+
244
+ Notes:
245
+
246
+ - The default runtime mode is `app-server`
247
+ - `exec-json` is kept only as a fallback compatibility path
248
+ - If `PROJECT_ROOTS` is not set, the app falls back to a broad local browsing root
249
+
250
+ ## Project Structure
251
+
252
+ ```text
253
+ server/
254
+ src/
255
+ app.ts
256
+ controllers/
257
+ db/
258
+ gateways/
259
+ services/
260
+ types/
261
+ utils/
262
+ web/
263
+ index.html
264
+ styles.css
265
+ api.js
266
+ session-ws.js
267
+ app.js
268
+ scripts/
269
+ fix-node-pty-helper.js
270
+ import-codex-rollout.js
271
+ reset-semantic-demo-data.js
272
+ ```
283
273
 
284
- - control from any device
274
+ ## Main Endpoints
285
275
 
286
- **Integration**
276
+ - `GET /health`
277
+ - `GET /api/codex/mode`
278
+ - `GET /api/codex/status`
279
+ - `GET /api/codex/quota`
280
+ - `GET /api/sessions`
281
+ - `GET /api/sessions/:sessionId`
282
+ - `GET /api/sessions/:sessionId/timeline`
283
+ - `GET /api/sessions/:sessionId/events`
284
+ - `POST /api/sessions`
285
+ - `POST /api/sessions/:sessionId/messages`
286
+ - `POST /api/sessions/:sessionId/stop`
287
+ - `POST /api/sessions/:sessionId/approvals/:requestId`
288
+ - `WS /ws/sessions/:sessionId`
287
289
 
288
- - IDE integrations
289
- - optional sharing
290
+ ## What Is Not Finished Yet
290
291
 
291
- ---
292
+ This is the honest list:
292
293
 
293
- ## πŸ‘₯ Who it’s for
294
+ - no polished installer yet
295
+ - no desktop packaging yet
296
+ - no full automated test suite yet
297
+ - no production-grade auth / multi-user hardening yet
298
+ - no release pipeline yet
294
299
 
295
- - developers already using Codex
296
- - people tired of terminal-only workflows
297
- - anyone who wants **control, not just output**
298
- - multi-device workflows
300
+ If you are comfortable cloning a repo and running a local Node app, you can use it today.
299
301
 
300
- ---
302
+ ## Roadmap
301
303
 
302
- ## ⚠️ What’s not finished yet
304
+ Near-term:
303
305
 
304
- - no polished installer yet
305
- - no desktop packaging
306
- - no production-grade auth
307
- - no release pipeline
306
+ - improve onboarding and installation
307
+ - ship a cleaner public README and screenshots
308
+ - add stronger regression coverage
309
+ - harden long-running session recovery
310
+ - continue refining the execution timeline UI
311
+
312
+ Later:
308
313
 
309
- If you're comfortable running a local Node app β€”
310
- you can use it today.
314
+ - package for easier local install
315
+ - optional sync / multi-device helpers
316
+ - stronger sharing, auditing, and team workflows
311
317
 
312
- ---
318
+ ## License
313
319
 
314
- ## πŸ“„ License
320
+ No license has been added yet.
315
321
 
316
- MIT License
322
+ Until a license is added, assume this repository is **source-available for review only**, not open source for reuse.
@@ -3,13 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DEFAULT_PORT = void 0;
7
- exports.resolvePackageRoot = resolvePackageRoot;
8
- exports.resolveDefaultDatabasePath = resolveDefaultDatabasePath;
9
6
  exports.startRemCodexServer = startRemCodexServer;
10
7
  const node_fs_1 = require("node:fs");
11
8
  const node_http_1 = __importDefault(require("node:http"));
12
- const node_os_1 = require("node:os");
13
9
  const node_path_1 = __importDefault(require("node:path"));
14
10
  const express_1 = __importDefault(require("express"));
15
11
  const codex_options_controller_1 = require("./controllers/codex-options.controller");
@@ -24,36 +20,15 @@ const codex_rollout_sync_1 = require("./services/codex-rollout-sync");
24
20
  const project_manager_1 = require("./services/project-manager");
25
21
  const session_manager_1 = require("./services/session-manager");
26
22
  const session_timeline_service_1 = require("./services/session-timeline-service");
23
+ const runtime_paths_1 = require("./utils/runtime-paths");
27
24
  const command_1 = require("./utils/command");
28
25
  const errors_1 = require("./utils/errors");
29
- function isPackageRoot(root) {
30
- return ((0, node_fs_1.existsSync)(node_path_1.default.join(root, "package.json")) &&
31
- (0, node_fs_1.existsSync)(node_path_1.default.join(root, "web", "index.html")));
32
- }
33
- function resolvePackageRoot(startDir = __dirname) {
34
- let current = node_path_1.default.resolve(startDir);
35
- while (true) {
36
- if (isPackageRoot(current)) {
37
- return current;
38
- }
39
- const parent = node_path_1.default.dirname(current);
40
- if (parent === current) {
41
- break;
42
- }
43
- current = parent;
44
- }
45
- return process.cwd();
46
- }
47
- function resolveDefaultDatabasePath() {
48
- return node_path_1.default.join((0, node_os_1.homedir)(), ".remcodex", "remcodex.db");
49
- }
50
- exports.DEFAULT_PORT = 18840;
51
26
  function buildRemCodexServer(options = {}) {
52
- const repoRoot = options.repoRoot ? node_path_1.default.resolve(options.repoRoot) : resolvePackageRoot();
53
- const port = options.port ?? Number.parseInt(process.env.PORT ?? String(exports.DEFAULT_PORT), 10);
27
+ const repoRoot = options.repoRoot ? node_path_1.default.resolve(options.repoRoot) : (0, runtime_paths_1.resolvePackageRoot)();
28
+ const port = options.port ?? Number.parseInt(process.env.PORT ?? "3000", 10);
54
29
  const databasePath = options.databasePath ??
55
30
  process.env.DATABASE_PATH ??
56
- resolveDefaultDatabasePath();
31
+ (0, runtime_paths_1.resolveDefaultDatabasePath)();
57
32
  const codexCommand = (0, command_1.resolveExecutable)(options.codexCommand ?? process.env.CODEX_COMMAND ?? "codex");
58
33
  const codexMode = options.codexMode ?? (process.env.CODEX_MODE === "exec-json" ? "exec-json" : "app-server");
59
34
  const projectRootsEnv = options.projectRootsEnv ?? process.env.PROJECT_ROOTS;
@@ -1,5 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
3
36
  var __importDefault = (this && this.__importDefault) || function (mod) {
4
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
38
  };
@@ -8,7 +41,7 @@ const node_fs_1 = require("node:fs");
8
41
  const node_child_process_1 = require("node:child_process");
9
42
  const node_os_1 = require("node:os");
10
43
  const node_path_1 = __importDefault(require("node:path"));
11
- const app_1 = require("./app");
44
+ const runtime_paths_1 = require("./utils/runtime-paths");
12
45
  const command_1 = require("./utils/command");
13
46
  function print(message = "") {
14
47
  process.stdout.write(`${message}\n`);
@@ -18,7 +51,7 @@ function printError(message = "") {
18
51
  }
19
52
  function readPackageVersion() {
20
53
  try {
21
- const packageRoot = (0, app_1.resolvePackageRoot)();
54
+ const packageRoot = (0, runtime_paths_1.resolvePackageRoot)();
22
55
  const packageJson = JSON.parse((0, node_fs_1.readFileSync)(node_path_1.default.join(packageRoot, "package.json"), "utf8"));
23
56
  return typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
24
57
  }
@@ -123,12 +156,24 @@ function usage() {
123
156
  print(" --db <path> Use a specific SQLite database path");
124
157
  print(" --no-open Do not open a browser automatically");
125
158
  }
159
+ function formatNativeModuleError(error) {
160
+ const message = error instanceof Error ? error.message : String(error);
161
+ if (!message.includes("NODE_MODULE_VERSION")) {
162
+ return null;
163
+ }
164
+ return [
165
+ "Native module failed to load. This usually means RemCodex was installed with a different Node.js version.",
166
+ `Current Node: ${process.version}`,
167
+ "Reinstall remcodex with the same Node.js version you will use to run it.",
168
+ "If you use nvm/fnm/asdf, switch to the target Node version first, then reinstall.",
169
+ ].join("\n");
170
+ }
126
171
  async function runDoctor(flags) {
127
172
  const version = readPackageVersion();
128
173
  const rawCodexCommand = process.env.CODEX_COMMAND ?? "codex";
129
174
  const codex = commandExists(rawCodexCommand);
130
- const packageRoot = (0, app_1.resolvePackageRoot)();
131
- const databasePath = flags.databasePath ?? process.env.DATABASE_PATH ?? (0, app_1.resolveDefaultDatabasePath)();
175
+ const packageRoot = (0, runtime_paths_1.resolvePackageRoot)();
176
+ const databasePath = flags.databasePath ?? process.env.DATABASE_PATH ?? (0, runtime_paths_1.resolveDefaultDatabasePath)();
132
177
  const databaseDir = node_path_1.default.dirname(databasePath);
133
178
  const databaseDirExists = (0, node_fs_1.existsSync)(databaseDir);
134
179
  const databaseDirWritable = databaseDirExists && (() => {
@@ -161,6 +206,18 @@ async function runDoctor(flags) {
161
206
  return 0;
162
207
  }
163
208
  async function runStart(flags) {
209
+ let startRemCodexServer;
210
+ try {
211
+ ({ startRemCodexServer } = await Promise.resolve().then(() => __importStar(require("./app"))));
212
+ }
213
+ catch (error) {
214
+ const nativeModuleMessage = formatNativeModuleError(error);
215
+ if (nativeModuleMessage) {
216
+ printError(nativeModuleMessage);
217
+ return 1;
218
+ }
219
+ throw error;
220
+ }
164
221
  const version = readPackageVersion();
165
222
  const rawCodexCommand = process.env.CODEX_COMMAND ?? "codex";
166
223
  const codex = commandExists(rawCodexCommand);
@@ -169,14 +226,14 @@ async function runStart(flags) {
169
226
  printError("Install Codex first, or set CODEX_COMMAND to the correct executable.");
170
227
  return 1;
171
228
  }
172
- const preferredPort = flags.port ?? Number.parseInt(process.env.PORT ?? String(app_1.DEFAULT_PORT), 10);
229
+ const preferredPort = flags.port ?? Number.parseInt(process.env.PORT ?? "3000", 10);
173
230
  const codexMode = process.env.CODEX_MODE === "exec-json" ? "exec-json" : "app-server";
174
231
  let started = null;
175
232
  let activePort = preferredPort;
176
233
  for (let offset = 0; offset < 20; offset += 1) {
177
234
  const candidate = preferredPort + offset;
178
235
  try {
179
- started = await (0, app_1.startRemCodexServer)({
236
+ started = await startRemCodexServer({
180
237
  port: candidate,
181
238
  databasePath: flags.databasePath,
182
239
  codexCommand: rawCodexCommand,
@@ -6,6 +6,35 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.runMigrations = runMigrations;
7
7
  const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
+ function isPackageRoot(root) {
10
+ return (0, node_fs_1.existsSync)(node_path_1.default.join(root, "package.json")) && (0, node_fs_1.existsSync)(node_path_1.default.join(root, "web", "index.html"));
11
+ }
12
+ function resolvePackageRoot(startDir = __dirname) {
13
+ let current = node_path_1.default.resolve(startDir);
14
+ while (true) {
15
+ if (isPackageRoot(current)) {
16
+ return current;
17
+ }
18
+ const parent = node_path_1.default.dirname(current);
19
+ if (parent === current) {
20
+ break;
21
+ }
22
+ current = parent;
23
+ }
24
+ return process.cwd();
25
+ }
26
+ function resolveSchemaFile() {
27
+ const packageRoot = resolvePackageRoot();
28
+ const candidates = [
29
+ node_path_1.default.join(packageRoot, "server", "src", "db", "schema.sql"),
30
+ node_path_1.default.join(packageRoot, "dist", "server", "src", "db", "schema.sql"),
31
+ ];
32
+ const resolved = candidates.find((candidate) => (0, node_fs_1.existsSync)(candidate));
33
+ if (!resolved) {
34
+ throw new Error(`Database schema file not found. Tried: ${candidates.join(", ")}`);
35
+ }
36
+ return resolved;
37
+ }
9
38
  function ensureColumn(db, table, column, definition) {
10
39
  const rows = db.prepare(`PRAGMA table_info(${table})`).all();
11
40
  if (rows.some((row) => row.name === column)) {
@@ -14,7 +43,7 @@ function ensureColumn(db, table, column, definition) {
14
43
  db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
15
44
  }
16
45
  function runMigrations(db) {
17
- const schemaFile = node_path_1.default.join(process.cwd(), "server", "src", "db", "schema.sql");
46
+ const schemaFile = resolveSchemaFile();
18
47
  const schema = (0, node_fs_1.readFileSync)(schemaFile, "utf8");
19
48
  db.exec(schema);
20
49
  ensureColumn(db, "sessions", "source_kind", "TEXT NOT NULL DEFAULT 'native'");
@@ -0,0 +1,48 @@
1
+ CREATE TABLE IF NOT EXISTS projects (
2
+ id TEXT PRIMARY KEY,
3
+ name TEXT NOT NULL,
4
+ path TEXT NOT NULL,
5
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
6
+ );
7
+
8
+ CREATE TABLE IF NOT EXISTS sessions (
9
+ id TEXT PRIMARY KEY,
10
+ title TEXT,
11
+ project_id TEXT NOT NULL,
12
+ status TEXT NOT NULL,
13
+ pid INTEGER,
14
+ codex_thread_id TEXT,
15
+ source_kind TEXT NOT NULL DEFAULT 'native',
16
+ source_rollout_path TEXT,
17
+ source_thread_id TEXT,
18
+ source_sync_cursor INTEGER,
19
+ source_last_synced_at TEXT,
20
+ source_rollout_has_open_turn INTEGER NOT NULL DEFAULT 0,
21
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
22
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
23
+ FOREIGN KEY (project_id) REFERENCES projects(id)
24
+ );
25
+
26
+ CREATE TABLE IF NOT EXISTS session_events (
27
+ id TEXT PRIMARY KEY,
28
+ session_id TEXT NOT NULL,
29
+ turn_id TEXT,
30
+ seq INTEGER NOT NULL,
31
+ event_type TEXT NOT NULL,
32
+ message_id TEXT,
33
+ call_id TEXT,
34
+ request_id TEXT,
35
+ phase TEXT,
36
+ stream TEXT,
37
+ payload_json TEXT NOT NULL,
38
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
39
+ FOREIGN KEY (session_id) REFERENCES sessions(id),
40
+ UNIQUE (session_id, seq)
41
+ );
42
+
43
+ CREATE INDEX IF NOT EXISTS idx_sessions_project_id ON sessions(project_id);
44
+ CREATE INDEX IF NOT EXISTS idx_session_events_session_seq ON session_events(session_id, seq);
45
+ CREATE INDEX IF NOT EXISTS idx_session_events_type_seq ON session_events(session_id, event_type, seq);
46
+ CREATE INDEX IF NOT EXISTS idx_session_events_message_id ON session_events(session_id, message_id, seq);
47
+ CREATE INDEX IF NOT EXISTS idx_session_events_call_id ON session_events(session_id, call_id, seq);
48
+ CREATE INDEX IF NOT EXISTS idx_session_events_request_id ON session_events(session_id, request_id, seq);
@@ -279,17 +279,6 @@ class SessionManager {
279
279
  seq: event.seq,
280
280
  };
281
281
  }
282
- stopSession(sessionId) {
283
- const runtime = this.runners.get(sessionId);
284
- if (!runtime || !runtime.runner.isAlive()) {
285
- this.setStatus(sessionId, "idle");
286
- return { accepted: true };
287
- }
288
- runtime.stopRequested = true;
289
- this.setStatus(sessionId, "stopping");
290
- runtime.runner.stop();
291
- return { accepted: true };
292
- }
293
282
  retryApprovalRequest(sessionId, requestId, codexLaunch) {
294
283
  const session = this.getSessionOrThrow(sessionId);
295
284
  const project = this.options.projectManager.getProject(session.project_id);
@@ -314,6 +303,17 @@ class SessionManager {
314
303
  turnId,
315
304
  };
316
305
  }
306
+ stopSession(sessionId) {
307
+ const runtime = this.runners.get(sessionId);
308
+ if (!runtime || !runtime.runner.isAlive()) {
309
+ this.setStatus(sessionId, "idle");
310
+ return { accepted: true };
311
+ }
312
+ runtime.stopRequested = true;
313
+ this.setStatus(sessionId, "stopping");
314
+ runtime.runner.stop();
315
+ return { accepted: true };
316
+ }
317
317
  resolveApproval(sessionId, requestId, decision) {
318
318
  const runtime = this.runners.get(sessionId);
319
319
  if (!runtime?.runner.isAlive()) {
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolvePackageRoot = resolvePackageRoot;
7
+ exports.resolveDefaultDatabasePath = resolveDefaultDatabasePath;
8
+ const node_fs_1 = require("node:fs");
9
+ const node_os_1 = require("node:os");
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ function isPackageRoot(root) {
12
+ return ((0, node_fs_1.existsSync)(node_path_1.default.join(root, "package.json")) &&
13
+ (0, node_fs_1.existsSync)(node_path_1.default.join(root, "web", "index.html")));
14
+ }
15
+ function resolvePackageRoot(startDir = __dirname) {
16
+ let current = node_path_1.default.resolve(startDir);
17
+ while (true) {
18
+ if (isPackageRoot(current)) {
19
+ return current;
20
+ }
21
+ const parent = node_path_1.default.dirname(current);
22
+ if (parent === current) {
23
+ break;
24
+ }
25
+ current = parent;
26
+ }
27
+ return process.cwd();
28
+ }
29
+ function resolveDefaultDatabasePath() {
30
+ return node_path_1.default.join((0, node_os_1.homedir)(), ".remcodex", "remcodex.db");
31
+ }
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,23 +1,31 @@
1
1
  {
2
2
  "name": "remcodex",
3
- "version": "0.1.0-beta.10",
3
+ "version": "0.1.0-beta.11",
4
4
  "description": "Control Codex from anywhere. Even on your phone.",
5
- "license": "MIT",
5
+ "license": "UNLICENSED",
6
+ "engines": {
7
+ "node": "20.x"
8
+ },
6
9
  "bin": {
7
10
  "remcodex": "dist/server/src/cli.js"
8
11
  },
9
12
  "files": [
10
13
  "dist",
11
14
  "web",
15
+ "scripts/check-node-version.js",
12
16
  "scripts/fix-node-pty-helper.js",
13
- "README.md"
17
+ "README.md",
18
+ "docs/assets"
14
19
  ],
15
20
  "scripts": {
21
+ "preinstall": "node scripts/check-node-version.js",
16
22
  "postinstall": "node scripts/fix-node-pty-helper.js",
17
23
  "dev": "tsx watch server/src/app.ts",
18
24
  "start": "tsx server/src/cli.ts --no-open",
19
- "build": "tsc -p tsconfig.json",
20
- "cli": "tsx server/src/cli.ts --no-open"
25
+ "build": "tsc -p tsconfig.json && node scripts/copy-db-assets.js",
26
+ "cli": "tsx server/src/cli.ts --no-open",
27
+ "smoke:tarball": "node scripts/smoke-test-tarball.js",
28
+ "smoke:start": "node scripts/smoke-start-tarball.js"
21
29
  },
22
30
  "dependencies": {
23
31
  "better-sqlite3": "^11.8.1",
@@ -0,0 +1,14 @@
1
+ const supportedMajor = 20;
2
+ const currentVersion = process.versions.node;
3
+ const currentMajor = Number.parseInt(currentVersion.split(".")[0] || "", 10);
4
+
5
+ if (currentMajor !== supportedMajor) {
6
+ console.error(
7
+ [
8
+ `remcodex requires Node.js ${supportedMajor}.x for the published package.`,
9
+ `Current Node.js: ${currentVersion}`,
10
+ "Switch Node versions first, then reinstall remcodex so native modules are built against the same runtime.",
11
+ ].join("\n"),
12
+ );
13
+ process.exit(1);
14
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 RemCodex contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.