remcodex 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +331 -0
- package/dist/server/src/app.js +186 -0
- package/dist/server/src/cli.js +270 -0
- package/dist/server/src/controllers/codex-options.controller.js +199 -0
- package/dist/server/src/controllers/message.controller.js +21 -0
- package/dist/server/src/controllers/project.controller.js +44 -0
- package/dist/server/src/controllers/session.controller.js +175 -0
- package/dist/server/src/db/client.js +10 -0
- package/dist/server/src/db/migrations.js +32 -0
- package/dist/server/src/gateways/ws.gateway.js +60 -0
- package/dist/server/src/services/codex-app-server-runner.js +363 -0
- package/dist/server/src/services/codex-exec-runner.js +147 -0
- package/dist/server/src/services/codex-rollout-sync.js +977 -0
- package/dist/server/src/services/codex-runner.js +11 -0
- package/dist/server/src/services/codex-stream-events.js +478 -0
- package/dist/server/src/services/event-store.js +328 -0
- package/dist/server/src/services/project-manager.js +130 -0
- package/dist/server/src/services/pty-runner.js +72 -0
- package/dist/server/src/services/session-manager.js +1586 -0
- package/dist/server/src/services/session-timeline-service.js +181 -0
- package/dist/server/src/types/codex-launch.js +2 -0
- package/dist/server/src/types/models.js +37 -0
- package/dist/server/src/utils/ansi.js +143 -0
- package/dist/server/src/utils/codex-launch.js +102 -0
- package/dist/server/src/utils/codex-quota.js +179 -0
- package/dist/server/src/utils/codex-status.js +163 -0
- package/dist/server/src/utils/codex-ui-options.js +114 -0
- package/dist/server/src/utils/command.js +46 -0
- package/dist/server/src/utils/errors.js +16 -0
- package/dist/server/src/utils/ids.js +7 -0
- package/dist/server/src/utils/node-pty.js +29 -0
- package/package.json +36 -0
- package/scripts/fix-node-pty-helper.js +36 -0
- package/web/api.js +175 -0
- package/web/app.js +8082 -0
- package/web/components/composer.js +627 -0
- package/web/components/session-workbench.js +173 -0
- package/web/i18n/index.js +171 -0
- package/web/i18n/locales/de.js +50 -0
- package/web/i18n/locales/en.js +320 -0
- package/web/i18n/locales/es.js +50 -0
- package/web/i18n/locales/fr.js +50 -0
- package/web/i18n/locales/ja.js +50 -0
- package/web/i18n/locales/ko.js +50 -0
- package/web/i18n/locales/pt-BR.js +50 -0
- package/web/i18n/locales/ru.js +50 -0
- package/web/i18n/locales/zh-CN.js +320 -0
- package/web/i18n/locales/zh-Hant.js +53 -0
- package/web/index.html +23 -0
- package/web/message-rich-text.js +218 -0
- package/web/session-command-activity.js +980 -0
- package/web/session-event-adapter.js +826 -0
- package/web/session-timeline-reducer.js +728 -0
- package/web/session-timeline-renderer.js +656 -0
- package/web/session-ws.js +31 -0
- package/web/styles.css +5665 -0
- package/web/vendor/markdown-it.js +6969 -0
|
@@ -0,0 +1,163 @@
|
|
|
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.resolveCodexStatus = resolveCodexStatus;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
11
|
+
function resolveCodexHomeDir() {
|
|
12
|
+
const override = process.env.CODEX_HOME?.trim();
|
|
13
|
+
if (override) {
|
|
14
|
+
return node_path_1.default.resolve(override);
|
|
15
|
+
}
|
|
16
|
+
return node_path_1.default.join(node_os_1.default.homedir(), ".codex");
|
|
17
|
+
}
|
|
18
|
+
function resolveStateDbPath() {
|
|
19
|
+
const override = process.env.CODEX_STATE_DB_PATH?.trim();
|
|
20
|
+
if (override) {
|
|
21
|
+
return node_path_1.default.resolve(override);
|
|
22
|
+
}
|
|
23
|
+
return node_path_1.default.join(resolveCodexHomeDir(), "state_5.sqlite");
|
|
24
|
+
}
|
|
25
|
+
function toIso(unixSeconds) {
|
|
26
|
+
return new Date(unixSeconds * 1000).toISOString();
|
|
27
|
+
}
|
|
28
|
+
function normalizeNonEmpty(value) {
|
|
29
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
30
|
+
return trimmed ? trimmed : null;
|
|
31
|
+
}
|
|
32
|
+
function parseSandboxPolicy(raw) {
|
|
33
|
+
const text = normalizeNonEmpty(raw);
|
|
34
|
+
if (!text) {
|
|
35
|
+
return {
|
|
36
|
+
sandboxMode: null,
|
|
37
|
+
writableRoots: [],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (!text.startsWith("{")) {
|
|
41
|
+
return {
|
|
42
|
+
sandboxMode: text,
|
|
43
|
+
writableRoots: [],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(text);
|
|
48
|
+
const writableRoots = Array.isArray(parsed.writable_roots)
|
|
49
|
+
? parsed.writable_roots
|
|
50
|
+
.filter((item) => typeof item === "string" && item.trim().length > 0)
|
|
51
|
+
: [];
|
|
52
|
+
return {
|
|
53
|
+
sandboxMode: normalizeNonEmpty(typeof parsed.type === "string" ? parsed.type : null),
|
|
54
|
+
writableRoots,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return {
|
|
59
|
+
sandboxMode: text,
|
|
60
|
+
writableRoots: [],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function deriveWritableRoots(sandboxMode, cwd, parsedRoots) {
|
|
65
|
+
if (parsedRoots.length > 0) {
|
|
66
|
+
return parsedRoots;
|
|
67
|
+
}
|
|
68
|
+
if (sandboxMode === "danger-full-access") {
|
|
69
|
+
return ["<full-access>"];
|
|
70
|
+
}
|
|
71
|
+
if (sandboxMode === "workspace-write") {
|
|
72
|
+
const roots = [];
|
|
73
|
+
if (cwd) {
|
|
74
|
+
roots.push(cwd);
|
|
75
|
+
}
|
|
76
|
+
roots.push("/tmp");
|
|
77
|
+
return roots;
|
|
78
|
+
}
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
function buildRuntime(row, input) {
|
|
82
|
+
const cwd = normalizeNonEmpty(row?.cwd ?? input.cwd ?? null);
|
|
83
|
+
const parsedPolicy = parseSandboxPolicy(row?.sandbox_policy ?? null);
|
|
84
|
+
const sandboxMode = parsedPolicy.sandboxMode;
|
|
85
|
+
const approvalMode = normalizeNonEmpty(row?.approval_mode ?? null);
|
|
86
|
+
return {
|
|
87
|
+
executionMode: input.executionMode ?? "codex app-server",
|
|
88
|
+
interactiveApprovalUi: input.interactiveApprovalUi ?? false,
|
|
89
|
+
cwd,
|
|
90
|
+
workspaceRoot: cwd,
|
|
91
|
+
sandboxMode,
|
|
92
|
+
approvalMode,
|
|
93
|
+
writableRoots: deriveWritableRoots(sandboxMode, cwd, parsedPolicy.writableRoots),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function mapThread(row) {
|
|
97
|
+
return {
|
|
98
|
+
threadId: row.id,
|
|
99
|
+
rolloutPath: row.rollout_path,
|
|
100
|
+
source: row.source,
|
|
101
|
+
modelProvider: row.model_provider,
|
|
102
|
+
cwd: row.cwd,
|
|
103
|
+
title: row.title,
|
|
104
|
+
sandboxPolicy: row.sandbox_policy,
|
|
105
|
+
approvalMode: row.approval_mode,
|
|
106
|
+
tokensUsed: row.tokens_used,
|
|
107
|
+
hasUserEvent: row.has_user_event !== 0,
|
|
108
|
+
archived: row.archived !== 0,
|
|
109
|
+
archivedAt: row.archived_at == null ? null : toIso(row.archived_at),
|
|
110
|
+
gitSha: row.git_sha,
|
|
111
|
+
gitBranch: row.git_branch,
|
|
112
|
+
gitOriginUrl: row.git_origin_url,
|
|
113
|
+
cliVersion: row.cli_version,
|
|
114
|
+
firstUserMessage: row.first_user_message,
|
|
115
|
+
agentNickname: row.agent_nickname,
|
|
116
|
+
agentRole: row.agent_role,
|
|
117
|
+
memoryMode: row.memory_mode,
|
|
118
|
+
model: row.model,
|
|
119
|
+
reasoningEffort: row.reasoning_effort,
|
|
120
|
+
createdAt: toIso(row.created_at),
|
|
121
|
+
updatedAt: toIso(row.updated_at),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function resolveCodexStatus(input = {}) {
|
|
125
|
+
const dbPath = resolveStateDbPath();
|
|
126
|
+
if (!(0, node_fs_1.existsSync)(dbPath)) {
|
|
127
|
+
return {
|
|
128
|
+
thread: null,
|
|
129
|
+
source: "none",
|
|
130
|
+
runtime: buildRuntime(undefined, input),
|
|
131
|
+
fetchedAt: new Date().toISOString(),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
135
|
+
try {
|
|
136
|
+
const byId = db.prepare("SELECT * FROM threads WHERE id = ? LIMIT 1");
|
|
137
|
+
const byCwd = db.prepare("SELECT * FROM threads WHERE cwd = ? ORDER BY updated_at DESC, id DESC LIMIT 1");
|
|
138
|
+
const latest = db.prepare("SELECT * FROM threads ORDER BY updated_at DESC, id DESC LIMIT 1");
|
|
139
|
+
let row;
|
|
140
|
+
let source = "none";
|
|
141
|
+
if (input.threadId?.trim()) {
|
|
142
|
+
row = byId.get(input.threadId.trim());
|
|
143
|
+
source = row ? "threadId" : "none";
|
|
144
|
+
}
|
|
145
|
+
if (!row && input.cwd?.trim()) {
|
|
146
|
+
row = byCwd.get(input.cwd.trim());
|
|
147
|
+
source = row ? "cwd" : source;
|
|
148
|
+
}
|
|
149
|
+
if (!row) {
|
|
150
|
+
row = latest.get();
|
|
151
|
+
source = row ? "latest" : "none";
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
thread: row ? mapThread(row) : null,
|
|
155
|
+
source,
|
|
156
|
+
runtime: buildRuntime(row, input),
|
|
157
|
+
fetchedAt: new Date().toISOString(),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
db.close?.();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
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.resolveCodexUiOptions = resolveCodexUiOptions;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const DEFAULT_OPTIONS = {
|
|
11
|
+
models: [
|
|
12
|
+
{ id: "gpt-5.4", label: "gpt-5.4" },
|
|
13
|
+
{ id: "gpt-5.4-mini", label: "GPT-5.4-Mini" },
|
|
14
|
+
{ id: "gpt-5.3-codex", label: "gpt-5.3-codex" },
|
|
15
|
+
{ id: "gpt-5.2-codex", label: "gpt-5.2-codex" },
|
|
16
|
+
{ id: "gpt-5.2", label: "gpt-5.2" },
|
|
17
|
+
{ id: "gpt-5.1-codex-max", label: "gpt-5.1-codex-max" },
|
|
18
|
+
{ id: "gpt-5.1-codex-mini", label: "gpt-5.1-codex-mini" },
|
|
19
|
+
],
|
|
20
|
+
reasoningLevels: [
|
|
21
|
+
{ id: "low", label: "Low", launch: { reasoningEffort: "low" } },
|
|
22
|
+
{ id: "medium", label: "Medium", launch: { reasoningEffort: "medium" } },
|
|
23
|
+
{ id: "high", label: "High", launch: { reasoningEffort: "high" } },
|
|
24
|
+
{ id: "xhigh", label: "Very high", launch: { reasoningEffort: "xhigh" } },
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
function resolveCodexHomeDir() {
|
|
28
|
+
const override = process.env.CODEX_HOME?.trim();
|
|
29
|
+
if (override) {
|
|
30
|
+
return node_path_1.default.resolve(override);
|
|
31
|
+
}
|
|
32
|
+
return node_path_1.default.join(node_os_1.default.homedir(), ".codex");
|
|
33
|
+
}
|
|
34
|
+
function resolveModelsCachePath() {
|
|
35
|
+
const override = process.env.CODEX_MODELS_CACHE_PATH?.trim();
|
|
36
|
+
if (override) {
|
|
37
|
+
return node_path_1.default.resolve(override);
|
|
38
|
+
}
|
|
39
|
+
return node_path_1.default.join(resolveCodexHomeDir(), "models_cache.json");
|
|
40
|
+
}
|
|
41
|
+
function sanitizeModel(raw) {
|
|
42
|
+
if (!raw || typeof raw !== "object") {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const o = raw;
|
|
46
|
+
const id = typeof o.slug === "string" ? o.slug.trim() : "";
|
|
47
|
+
const label = typeof o.display_name === "string" ? o.display_name.trim() : "";
|
|
48
|
+
const visibility = typeof o.visibility === "string" ? o.visibility.trim() : "";
|
|
49
|
+
if (!id || !label || id.length > 96 || label.length > 120) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
if (!/^[\w.+-]+$/i.test(id)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
if (visibility && visibility !== "list" && visibility !== "hide") {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
if (visibility !== "list") {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return { id, label };
|
|
62
|
+
}
|
|
63
|
+
function parseModelsCache(raw) {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(raw);
|
|
66
|
+
const modelsIn = Array.isArray(parsed.models) ? parsed.models : [];
|
|
67
|
+
const models = modelsIn.map(sanitizeModel).filter(Boolean);
|
|
68
|
+
if (models.length === 0) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const seen = new Set();
|
|
72
|
+
const deduped = models.filter((model) => {
|
|
73
|
+
if (seen.has(model.id)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
seen.add(model.id);
|
|
77
|
+
return true;
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
models: deduped,
|
|
81
|
+
reasoningLevels: DEFAULT_OPTIONS.reasoningLevels,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* CODEX_UI_OPTIONS_JSON: full JSON { models, reasoningLevels }.
|
|
90
|
+
* CODEX_MODELS_CACHE_PATH: optional override for ~/.codex/models_cache.json.
|
|
91
|
+
* Falls back to DEFAULT_OPTIONS when unset or invalid.
|
|
92
|
+
*/
|
|
93
|
+
function resolveCodexUiOptions() {
|
|
94
|
+
const fromEnv = process.env.CODEX_UI_OPTIONS_JSON?.trim();
|
|
95
|
+
if (fromEnv) {
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(fromEnv);
|
|
98
|
+
if (Array.isArray(parsed.models) && parsed.models.length > 0 && Array.isArray(parsed.reasoningLevels)) {
|
|
99
|
+
return parsed;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
/* ignore */
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const modelsCachePath = resolveModelsCachePath();
|
|
107
|
+
if ((0, node_fs_1.existsSync)(modelsCachePath)) {
|
|
108
|
+
const parsed = parseModelsCache((0, node_fs_1.readFileSync)(modelsCachePath, "utf8"));
|
|
109
|
+
if (parsed) {
|
|
110
|
+
return parsed;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return DEFAULT_OPTIONS;
|
|
114
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
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.resolveExecutable = resolveExecutable;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_child_process_1 = require("node:child_process");
|
|
10
|
+
function isExecutable(filePath) {
|
|
11
|
+
try {
|
|
12
|
+
(0, node_fs_1.accessSync)(filePath, node_fs_1.constants.X_OK);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function shellQuote(value) {
|
|
20
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
21
|
+
}
|
|
22
|
+
function resolveExecutable(command) {
|
|
23
|
+
const trimmed = command.trim();
|
|
24
|
+
if (!trimmed) {
|
|
25
|
+
return trimmed;
|
|
26
|
+
}
|
|
27
|
+
if (trimmed.includes(node_path_1.default.sep)) {
|
|
28
|
+
const candidate = node_path_1.default.resolve(trimmed);
|
|
29
|
+
return isExecutable(candidate) ? candidate : trimmed;
|
|
30
|
+
}
|
|
31
|
+
const envPath = process.env.PATH ?? "";
|
|
32
|
+
for (const root of envPath.split(node_path_1.default.delimiter).filter(Boolean)) {
|
|
33
|
+
const candidate = node_path_1.default.join(root, trimmed);
|
|
34
|
+
if (isExecutable(candidate)) {
|
|
35
|
+
return candidate;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const shell = process.env.SHELL ?? "/bin/zsh";
|
|
40
|
+
const resolved = (0, node_child_process_1.execFileSync)(shell, ["-lc", `command -v ${shellQuote(trimmed)}`], { encoding: "utf8" }).trim();
|
|
41
|
+
return resolved || trimmed;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return trimmed;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AppError = void 0;
|
|
4
|
+
exports.isAppError = isAppError;
|
|
5
|
+
class AppError extends Error {
|
|
6
|
+
statusCode;
|
|
7
|
+
constructor(statusCode, message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.statusCode = statusCode;
|
|
10
|
+
this.name = "AppError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.AppError = AppError;
|
|
14
|
+
function isAppError(error) {
|
|
15
|
+
return error instanceof AppError;
|
|
16
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createId = createId;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
function createId(prefix) {
|
|
6
|
+
return `${prefix}_${(0, node_crypto_1.randomUUID)().replace(/-/g, "").slice(0, 12)}`;
|
|
7
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
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.ensureNodePtyHelperExecutable = ensureNodePtyHelperExecutable;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
function ensureNodePtyHelperExecutable() {
|
|
10
|
+
if (process.platform !== "darwin") {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
let packageJsonPath;
|
|
14
|
+
try {
|
|
15
|
+
packageJsonPath = require.resolve("node-pty/package.json");
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const helperPath = node_path_1.default.join(node_path_1.default.dirname(packageJsonPath), "prebuilds", `darwin-${process.arch}`, "spawn-helper");
|
|
21
|
+
if (!(0, node_fs_1.existsSync)(helperPath)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const currentMode = (0, node_fs_1.statSync)(helperPath).mode & 0o777;
|
|
25
|
+
const nextMode = currentMode | 0o111;
|
|
26
|
+
if (currentMode !== nextMode) {
|
|
27
|
+
(0, node_fs_1.chmodSync)(helperPath, nextMode);
|
|
28
|
+
}
|
|
29
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "remcodex",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"description": "Control Codex from anywhere. Even on your phone.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"remcodex": "dist/server/src/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"web",
|
|
12
|
+
"scripts/fix-node-pty-helper.js",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"postinstall": "node scripts/fix-node-pty-helper.js",
|
|
17
|
+
"dev": "tsx watch server/src/app.ts",
|
|
18
|
+
"start": "tsx server/src/cli.ts --no-open",
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"cli": "tsx server/src/cli.ts --no-open"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"better-sqlite3": "^11.8.1",
|
|
24
|
+
"express": "^4.21.2",
|
|
25
|
+
"markdown-it": "^14.1.1",
|
|
26
|
+
"node-pty": "^1.0.0",
|
|
27
|
+
"ws": "^8.18.1"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/express": "^5.0.1",
|
|
31
|
+
"@types/node": "^22.13.10",
|
|
32
|
+
"@types/ws": "^8.5.14",
|
|
33
|
+
"tsx": "^4.19.3",
|
|
34
|
+
"typescript": "^5.8.2"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
|
|
4
|
+
function fixNodePtyHelper() {
|
|
5
|
+
if (process.platform !== "darwin") {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let packageJsonPath;
|
|
10
|
+
try {
|
|
11
|
+
packageJsonPath = require.resolve("node-pty/package.json");
|
|
12
|
+
} catch {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const helperPath = path.join(
|
|
17
|
+
path.dirname(packageJsonPath),
|
|
18
|
+
"prebuilds",
|
|
19
|
+
`darwin-${process.arch}`,
|
|
20
|
+
"spawn-helper",
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
if (!fs.existsSync(helperPath)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const currentMode = fs.statSync(helperPath).mode & 0o777;
|
|
28
|
+
const nextMode = currentMode | 0o111;
|
|
29
|
+
|
|
30
|
+
if (currentMode !== nextMode) {
|
|
31
|
+
fs.chmodSync(helperPath, nextMode);
|
|
32
|
+
console.log(`[postinstall] fixed executable bit: ${helperPath}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fixNodePtyHelper();
|
package/web/api.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
const headers = {
|
|
2
|
+
"Content-Type": "application/json",
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
async function request(path, options = {}) {
|
|
6
|
+
const response = await fetch(path, {
|
|
7
|
+
...options,
|
|
8
|
+
headers: {
|
|
9
|
+
...headers,
|
|
10
|
+
...(options.headers ?? {}),
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
const data = await safeJson(response);
|
|
16
|
+
throw new Error(data?.error ?? `Request failed: ${response.status}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return safeJson(response);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function safeJson(response) {
|
|
23
|
+
const text = await response.text();
|
|
24
|
+
return text ? JSON.parse(text) : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getProjects() {
|
|
28
|
+
return request("/api/projects");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createProject(payload) {
|
|
32
|
+
return request("/api/projects", {
|
|
33
|
+
method: "POST",
|
|
34
|
+
body: JSON.stringify(payload),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function browseProjectDirectories(pathValue = "") {
|
|
39
|
+
const query = pathValue ? `?path=${encodeURIComponent(pathValue)}` : "";
|
|
40
|
+
return request(`/api/projects/browse${query}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getSessions() {
|
|
44
|
+
return request("/api/sessions");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createSession(payload) {
|
|
48
|
+
return request("/api/sessions", {
|
|
49
|
+
method: "POST",
|
|
50
|
+
body: JSON.stringify(payload),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function importCodexSession(payload) {
|
|
55
|
+
return request("/api/sessions/import-codex", {
|
|
56
|
+
method: "POST",
|
|
57
|
+
body: JSON.stringify(payload),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getSession(sessionId) {
|
|
62
|
+
return request(`/api/sessions/${sessionId}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function syncImportedSession(sessionId) {
|
|
66
|
+
return request(`/api/sessions/${sessionId}/sync`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getSessionEvents(sessionId, options = 0) {
|
|
72
|
+
const search = new URLSearchParams();
|
|
73
|
+
|
|
74
|
+
if (typeof options === "number") {
|
|
75
|
+
if (options > 0) {
|
|
76
|
+
search.set("after", String(options));
|
|
77
|
+
}
|
|
78
|
+
} else if (options && typeof options === "object") {
|
|
79
|
+
if (options.after) {
|
|
80
|
+
search.set("after", String(options.after));
|
|
81
|
+
}
|
|
82
|
+
if (options.before) {
|
|
83
|
+
search.set("before", String(options.before));
|
|
84
|
+
}
|
|
85
|
+
if (options.limit) {
|
|
86
|
+
search.set("limit", String(options.limit));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const query = search.toString();
|
|
91
|
+
return request(`/api/sessions/${sessionId}/events${query ? `?${query}` : ""}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function getSessionTimeline(sessionId, options = 0) {
|
|
95
|
+
const search = new URLSearchParams();
|
|
96
|
+
|
|
97
|
+
if (typeof options === "number") {
|
|
98
|
+
if (options > 0) {
|
|
99
|
+
search.set("after", String(options));
|
|
100
|
+
}
|
|
101
|
+
} else if (options && typeof options === "object") {
|
|
102
|
+
if (options.after) {
|
|
103
|
+
search.set("after", String(options.after));
|
|
104
|
+
}
|
|
105
|
+
if (options.before) {
|
|
106
|
+
search.set("before", String(options.before));
|
|
107
|
+
}
|
|
108
|
+
if (options.limit) {
|
|
109
|
+
search.set("limit", String(options.limit));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const query = search.toString();
|
|
114
|
+
return request(`/api/sessions/${sessionId}/timeline${query ? `?${query}` : ""}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function getSessionTimelineEvents(sessionId, options = 0) {
|
|
118
|
+
return getSessionTimeline(sessionId, options);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function sendMessage(sessionId, payload) {
|
|
122
|
+
return request(`/api/sessions/${sessionId}/messages`, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
body: JSON.stringify(payload),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function stopSession(sessionId) {
|
|
129
|
+
return request(`/api/sessions/${sessionId}/stop`, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function resolveSessionApproval(sessionId, requestId, decision) {
|
|
135
|
+
return request(`/api/sessions/${sessionId}/approvals/${encodeURIComponent(requestId)}`, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
body: JSON.stringify({ decision }),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function getHealth() {
|
|
142
|
+
return request("/health");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function getCodexUiOptions() {
|
|
146
|
+
return request("/api/codex/mode");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function getCodexStatus(params = {}) {
|
|
150
|
+
const search = new URLSearchParams();
|
|
151
|
+
if (params.sessionId) {
|
|
152
|
+
search.set("sessionId", params.sessionId);
|
|
153
|
+
}
|
|
154
|
+
if (params.threadId) {
|
|
155
|
+
search.set("threadId", params.threadId);
|
|
156
|
+
}
|
|
157
|
+
if (params.cwd) {
|
|
158
|
+
search.set("cwd", params.cwd);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const query = search.toString();
|
|
162
|
+
return request(`/api/codex/status${query ? `?${query}` : ""}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function getCodexQuota(sessionId) {
|
|
166
|
+
return request(`/api/codex/quota?sessionId=${encodeURIComponent(sessionId)}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function getCodexHosts() {
|
|
170
|
+
return request("/api/codex/hosts");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function getImportableCodexSessions() {
|
|
174
|
+
return request("/api/codex/importable-sessions");
|
|
175
|
+
}
|