codexui-android 0.1.83 → 0.1.88
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/dist/assets/{ReviewPane-nTMVVMpp.js → ReviewPane-Blnhnm6J.js} +2 -2
- package/dist/assets/{ReviewPane-nTMVVMpp.js.map → ReviewPane-Blnhnm6J.js.map} +1 -1
- package/dist/assets/{SkillsHub-BqjWaUSP.js → SkillsHub-C93DMbLM.js} +2 -2
- package/dist/assets/{SkillsHub-BqjWaUSP.js.map → SkillsHub-C93DMbLM.js.map} +1 -1
- package/dist/assets/ThreadConversation-B-Ik7tFp.js +40 -0
- package/dist/assets/ThreadConversation-B-Ik7tFp.js.map +1 -0
- package/dist/assets/{ThreadConversation-BsN7bN3q.css → ThreadConversation-BKfO22g2.css} +1 -1
- package/dist/assets/{index-iN-aqOuT.css → index-C_knKlTI.css} +1 -1
- package/dist/assets/index-CmAz60O4.js +92 -0
- package/dist/assets/index-CmAz60O4.js.map +1 -0
- package/dist/index.html +2 -2
- package/dist-cli/chunk-PUR7OUAG.js +127 -0
- package/dist-cli/chunk-PUR7OUAG.js.map +1 -0
- package/dist-cli/index.js +743 -142
- package/dist-cli/index.js.map +1 -1
- package/dist-cli/instrument.js +1 -1
- package/package.json +10 -4
- package/dist/assets/ThreadConversation-Ctw55jX6.js +0 -40
- package/dist/assets/ThreadConversation-Ctw55jX6.js.map +0 -1
- package/dist/assets/index-BEf3759i.js +0 -92
- package/dist/assets/index-BEf3759i.js.map +0 -1
- package/dist-cli/chunk-UUZOL7SL.js +0 -121
- package/dist-cli/chunk-UUZOL7SL.js.map +0 -1
package/dist-cli/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-PUR7OUAG.js";
|
|
3
3
|
|
|
4
4
|
// src/cli/index.ts
|
|
5
5
|
import { createServer as createServer2 } from "http";
|
|
@@ -217,9 +217,10 @@ function parseApprovalPolicy(value) {
|
|
|
217
217
|
|
|
218
218
|
// src/server/httpServer.ts
|
|
219
219
|
import { fileURLToPath } from "url";
|
|
220
|
-
import { dirname as dirname3, extname as extname3, isAbsolute as isAbsolute3, join as join7 } from "path";
|
|
220
|
+
import { basename as basename5, dirname as dirname3, extname as extname3, isAbsolute as isAbsolute3, join as join7 } from "path";
|
|
221
221
|
import { existsSync as existsSync3 } from "fs";
|
|
222
222
|
import { writeFile as writeFile5, stat as stat6 } from "fs/promises";
|
|
223
|
+
import { createReadStream as createReadStream2 } from "fs";
|
|
223
224
|
import express from "express";
|
|
224
225
|
|
|
225
226
|
// src/server/codexAppServerBridge.ts
|
|
@@ -480,6 +481,22 @@ async function validateSwitchedAccount(appServer) {
|
|
|
480
481
|
quotaSnapshot: pickCodexRateLimitSnapshot(quotaPayload)
|
|
481
482
|
};
|
|
482
483
|
}
|
|
484
|
+
async function validateSwitchedAccountWithTimeout(appServer) {
|
|
485
|
+
let timeoutHandle = null;
|
|
486
|
+
try {
|
|
487
|
+
return await Promise.race([
|
|
488
|
+
validateSwitchedAccount(appServer),
|
|
489
|
+
new Promise((_, reject) => {
|
|
490
|
+
timeoutHandle = setTimeout(() => {
|
|
491
|
+
reject(new Error(`Account switch validation timed out after ${ACCOUNT_INSPECTION_TIMEOUT_MS}ms`));
|
|
492
|
+
}, ACCOUNT_INSPECTION_TIMEOUT_MS);
|
|
493
|
+
timeoutHandle.unref?.();
|
|
494
|
+
})
|
|
495
|
+
]);
|
|
496
|
+
} finally {
|
|
497
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
483
500
|
async function restoreActiveAuth(raw) {
|
|
484
501
|
const path = getActiveAuthPath();
|
|
485
502
|
if (raw === null) {
|
|
@@ -794,7 +811,7 @@ async function handleAccountRoutes(req, res, url, context) {
|
|
|
794
811
|
const imported = await importAccountFromAuthPath(getActiveAuthPath());
|
|
795
812
|
try {
|
|
796
813
|
appServer.dispose();
|
|
797
|
-
const inspection = await
|
|
814
|
+
const inspection = await validateSwitchedAccountWithTimeout(appServer);
|
|
798
815
|
const state = await readStoredAccountsState();
|
|
799
816
|
const importedAccountId = imported.importedAccountId;
|
|
800
817
|
const target = state.accounts.find((entry) => entry.accountId === importedAccountId) ?? null;
|
|
@@ -893,7 +910,7 @@ async function handleAccountRoutes(req, res, url, context) {
|
|
|
893
910
|
await writeFile(getActiveAuthPath(), targetRaw, { encoding: "utf8", mode: 384 });
|
|
894
911
|
try {
|
|
895
912
|
appServer.dispose();
|
|
896
|
-
const inspection = await
|
|
913
|
+
const inspection = await validateSwitchedAccountWithTimeout(appServer);
|
|
897
914
|
const nextEntry = {
|
|
898
915
|
...target,
|
|
899
916
|
email: inspection.metadata.email ?? target.email,
|
|
@@ -1034,7 +1051,7 @@ async function handleAccountRoutes(req, res, url, context) {
|
|
|
1034
1051
|
await writeFile(getActiveAuthPath(), replacementRaw, { encoding: "utf8", mode: 384 });
|
|
1035
1052
|
try {
|
|
1036
1053
|
appServer.dispose();
|
|
1037
|
-
const inspection = await
|
|
1054
|
+
const inspection = await validateSwitchedAccountWithTimeout(appServer);
|
|
1038
1055
|
const activatedReplacement = {
|
|
1039
1056
|
...replacement,
|
|
1040
1057
|
email: inspection.metadata.email ?? replacement.email,
|
|
@@ -3762,6 +3779,78 @@ function spawnSyncCommand(command, args = [], options = {}) {
|
|
|
3762
3779
|
var PROVIDER_MODELS_FETCH_TIMEOUT_MS = 5e3;
|
|
3763
3780
|
var THREAD_RESPONSE_TURN_LIMIT = 10;
|
|
3764
3781
|
var THREAD_METHODS_WITH_TURNS = /* @__PURE__ */ new Set(["thread/read", "thread/resume", "thread/fork", "thread/rollback"]);
|
|
3782
|
+
var THREAD_SEARCH_FULL_TEXT_THREAD_LIMIT = 100;
|
|
3783
|
+
var API_PERF_LOGGING_ENV_KEY = "CODEXUI_API_PERF_LOGGING";
|
|
3784
|
+
var API_PERF_MS_THRESHOLD_ENV_KEY = "CODEXUI_API_PERF_MS_THRESHOLD";
|
|
3785
|
+
var API_PERF_BODY_MB_THRESHOLD_ENV_KEY = "CODEXUI_API_PERF_BODY_MB_THRESHOLD";
|
|
3786
|
+
var DEFAULT_API_PERF_MS_THRESHOLD = 300;
|
|
3787
|
+
var DEFAULT_API_PERF_BODY_MB_THRESHOLD = 1;
|
|
3788
|
+
var MB_DIVISOR = 1024 * 1024;
|
|
3789
|
+
function readEnvValueFromFile(filePath, key) {
|
|
3790
|
+
try {
|
|
3791
|
+
const content = readFileSync(filePath, "utf8");
|
|
3792
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3793
|
+
const match = content.match(new RegExp(`^\\s*${escapedKey}\\s*=\\s*(.+)\\s*$`, "m"));
|
|
3794
|
+
if (!match) return null;
|
|
3795
|
+
const rawValue = match[1]?.trim() ?? "";
|
|
3796
|
+
if (!rawValue) return null;
|
|
3797
|
+
if (rawValue.startsWith('"') && rawValue.endsWith('"') || rawValue.startsWith("'") && rawValue.endsWith("'")) {
|
|
3798
|
+
return rawValue.slice(1, -1).trim();
|
|
3799
|
+
}
|
|
3800
|
+
return rawValue;
|
|
3801
|
+
} catch {
|
|
3802
|
+
return null;
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
function parseBooleanEnvFlag(value) {
|
|
3806
|
+
if (!value) return null;
|
|
3807
|
+
const normalized = value.trim().toLowerCase();
|
|
3808
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
3809
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
3810
|
+
return null;
|
|
3811
|
+
}
|
|
3812
|
+
function resolveApiPerfLoggingEnabled() {
|
|
3813
|
+
const explicitValue = parseBooleanEnvFlag(process.env[API_PERF_LOGGING_ENV_KEY]);
|
|
3814
|
+
if (explicitValue !== null) return explicitValue;
|
|
3815
|
+
const fromEnvLocal = parseBooleanEnvFlag(readEnvValueFromFile(".env.local", API_PERF_LOGGING_ENV_KEY));
|
|
3816
|
+
if (fromEnvLocal !== null) return fromEnvLocal;
|
|
3817
|
+
const fromEnv = parseBooleanEnvFlag(readEnvValueFromFile(".env", API_PERF_LOGGING_ENV_KEY));
|
|
3818
|
+
if (fromEnv !== null) return fromEnv;
|
|
3819
|
+
return false;
|
|
3820
|
+
}
|
|
3821
|
+
var API_PERF_LOGGING_ENABLED = resolveApiPerfLoggingEnabled();
|
|
3822
|
+
function parseNumberEnvFlag(value) {
|
|
3823
|
+
if (!value) return null;
|
|
3824
|
+
const parsed = Number.parseFloat(value.trim());
|
|
3825
|
+
if (!Number.isFinite(parsed)) return null;
|
|
3826
|
+
return parsed;
|
|
3827
|
+
}
|
|
3828
|
+
function resolveNumericEnvConfig(envKey, fallback) {
|
|
3829
|
+
const fromProcess = parseNumberEnvFlag(process.env[envKey]);
|
|
3830
|
+
if (fromProcess !== null) return fromProcess;
|
|
3831
|
+
const fromEnvLocal = parseNumberEnvFlag(readEnvValueFromFile(".env.local", envKey));
|
|
3832
|
+
if (fromEnvLocal !== null) return fromEnvLocal;
|
|
3833
|
+
const fromEnv = parseNumberEnvFlag(readEnvValueFromFile(".env", envKey));
|
|
3834
|
+
if (fromEnv !== null) return fromEnv;
|
|
3835
|
+
return fallback;
|
|
3836
|
+
}
|
|
3837
|
+
var API_PERF_MS_THRESHOLD = resolveNumericEnvConfig(API_PERF_MS_THRESHOLD_ENV_KEY, DEFAULT_API_PERF_MS_THRESHOLD);
|
|
3838
|
+
var API_PERF_BODY_MB_THRESHOLD = resolveNumericEnvConfig(API_PERF_BODY_MB_THRESHOLD_ENV_KEY, DEFAULT_API_PERF_BODY_MB_THRESHOLD);
|
|
3839
|
+
function getChunkByteLength(chunk, encoding) {
|
|
3840
|
+
if (typeof chunk === "string") {
|
|
3841
|
+
return Buffer.byteLength(chunk, encoding);
|
|
3842
|
+
}
|
|
3843
|
+
if (chunk instanceof Uint8Array) {
|
|
3844
|
+
return chunk.byteLength;
|
|
3845
|
+
}
|
|
3846
|
+
if (ArrayBuffer.isView(chunk)) {
|
|
3847
|
+
return chunk.byteLength;
|
|
3848
|
+
}
|
|
3849
|
+
if (chunk instanceof ArrayBuffer) {
|
|
3850
|
+
return chunk.byteLength;
|
|
3851
|
+
}
|
|
3852
|
+
return 0;
|
|
3853
|
+
}
|
|
3765
3854
|
function asRecord5(value) {
|
|
3766
3855
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
3767
3856
|
}
|
|
@@ -5949,10 +6038,21 @@ async function loadAllThreadsForSearch(appServer) {
|
|
|
5949
6038
|
}
|
|
5950
6039
|
cursor = typeof response?.nextCursor === "string" && response.nextCursor.length > 0 ? response.nextCursor : null;
|
|
5951
6040
|
} while (cursor);
|
|
5952
|
-
const docs =
|
|
6041
|
+
const docs = threads.map((thread) => {
|
|
6042
|
+
const searchableText = [thread.title, thread.preview].filter(Boolean).join("\n");
|
|
6043
|
+
return {
|
|
6044
|
+
id: thread.id,
|
|
6045
|
+
title: thread.title,
|
|
6046
|
+
preview: thread.preview,
|
|
6047
|
+
messageText: "",
|
|
6048
|
+
searchableText
|
|
6049
|
+
};
|
|
6050
|
+
});
|
|
6051
|
+
const docsById = new Map(docs.map((doc) => [doc.id, doc]));
|
|
6052
|
+
const fullTextThreads = threads.slice(0, THREAD_SEARCH_FULL_TEXT_THREAD_LIMIT);
|
|
5953
6053
|
const concurrency = 4;
|
|
5954
|
-
for (let offset = 0; offset <
|
|
5955
|
-
const batch =
|
|
6054
|
+
for (let offset = 0; offset < fullTextThreads.length; offset += concurrency) {
|
|
6055
|
+
const batch = fullTextThreads.slice(offset, offset + concurrency);
|
|
5956
6056
|
const loaded = await Promise.all(batch.map(async (thread) => {
|
|
5957
6057
|
try {
|
|
5958
6058
|
const readResponse = await appServer.rpc("thread/read", {
|
|
@@ -5961,27 +6061,23 @@ async function loadAllThreadsForSearch(appServer) {
|
|
|
5961
6061
|
});
|
|
5962
6062
|
const messageText = extractThreadMessageText(readResponse);
|
|
5963
6063
|
const searchableText = [thread.title, thread.preview, messageText].filter(Boolean).join("\n");
|
|
5964
|
-
return {
|
|
6064
|
+
return [thread.id, {
|
|
5965
6065
|
id: thread.id,
|
|
5966
6066
|
title: thread.title,
|
|
5967
6067
|
preview: thread.preview,
|
|
5968
6068
|
messageText,
|
|
5969
6069
|
searchableText
|
|
5970
|
-
};
|
|
6070
|
+
}];
|
|
5971
6071
|
} catch {
|
|
5972
|
-
|
|
5973
|
-
return {
|
|
5974
|
-
id: thread.id,
|
|
5975
|
-
title: thread.title,
|
|
5976
|
-
preview: thread.preview,
|
|
5977
|
-
messageText: "",
|
|
5978
|
-
searchableText
|
|
5979
|
-
};
|
|
6072
|
+
return null;
|
|
5980
6073
|
}
|
|
5981
6074
|
}));
|
|
5982
|
-
|
|
6075
|
+
for (const row of loaded) {
|
|
6076
|
+
if (!row) continue;
|
|
6077
|
+
docsById.set(row[0], row[1]);
|
|
6078
|
+
}
|
|
5983
6079
|
}
|
|
5984
|
-
return
|
|
6080
|
+
return Array.from(docsById.values());
|
|
5985
6081
|
});
|
|
5986
6082
|
}
|
|
5987
6083
|
async function buildThreadSearchIndex(appServer) {
|
|
@@ -6014,6 +6110,42 @@ function createCodexBridgeMiddleware() {
|
|
|
6014
6110
|
}).catch(() => {
|
|
6015
6111
|
});
|
|
6016
6112
|
const middleware = async (req, res, next) => {
|
|
6113
|
+
const requestStartNs = process.hrtime.bigint();
|
|
6114
|
+
const rawUrl = req.url ?? "";
|
|
6115
|
+
const parsedRequestUrl = rawUrl ? new URL(rawUrl, "http://localhost") : null;
|
|
6116
|
+
const requestPath = parsedRequestUrl?.pathname ?? "";
|
|
6117
|
+
const requestMethod = req.method ?? "UNKNOWN";
|
|
6118
|
+
const rawContentLength = Array.isArray(req.headers["content-length"]) ? req.headers["content-length"][0] : req.headers["content-length"];
|
|
6119
|
+
const parsedContentLength = rawContentLength ? Number.parseInt(rawContentLength, 10) : NaN;
|
|
6120
|
+
let requestBodyBytes = Number.isFinite(parsedContentLength) && parsedContentLength >= 0 ? parsedContentLength : null;
|
|
6121
|
+
let responseBodyBytes = 0;
|
|
6122
|
+
let rpcMethod = null;
|
|
6123
|
+
const originalWrite = res.write.bind(res);
|
|
6124
|
+
const originalEnd = res.end.bind(res);
|
|
6125
|
+
res.write = ((chunk, encoding, cb) => {
|
|
6126
|
+
const resolvedEncoding = typeof encoding === "string" ? encoding : void 0;
|
|
6127
|
+
responseBodyBytes += getChunkByteLength(chunk, resolvedEncoding);
|
|
6128
|
+
return originalWrite(chunk, encoding, cb);
|
|
6129
|
+
});
|
|
6130
|
+
res.end = ((chunk, encoding, cb) => {
|
|
6131
|
+
const resolvedEncoding = typeof encoding === "string" ? encoding : void 0;
|
|
6132
|
+
responseBodyBytes += getChunkByteLength(chunk, resolvedEncoding);
|
|
6133
|
+
return originalEnd(chunk, encoding, cb);
|
|
6134
|
+
});
|
|
6135
|
+
let didLog = false;
|
|
6136
|
+
const logApiRequestDuration = () => {
|
|
6137
|
+
if (!API_PERF_LOGGING_ENABLED || didLog || !requestPath.startsWith("/codex-api/")) return;
|
|
6138
|
+
const durationMs = Number((process.hrtime.bigint() - requestStartNs) / 1000000n);
|
|
6139
|
+
const requestBytes = requestBodyBytes ?? 0;
|
|
6140
|
+
const bodyMbValue = (requestBytes + responseBodyBytes) / MB_DIVISOR;
|
|
6141
|
+
const shouldLog = durationMs > API_PERF_MS_THRESHOLD || bodyMbValue > API_PERF_BODY_MB_THRESHOLD;
|
|
6142
|
+
if (!shouldLog) return;
|
|
6143
|
+
didLog = true;
|
|
6144
|
+
const rpcPart = rpcMethod ? `, rpcMethod=${rpcMethod}` : "";
|
|
6145
|
+
console.info(`[codex-api-perf] ${requestMethod} ${requestPath} -> ${res.statusCode} (${durationMs}ms, bodyMB=${bodyMbValue.toFixed(4)}${rpcPart})`);
|
|
6146
|
+
};
|
|
6147
|
+
res.once("finish", logApiRequestDuration);
|
|
6148
|
+
res.once("close", logApiRequestDuration);
|
|
6017
6149
|
try {
|
|
6018
6150
|
if (!req.url) {
|
|
6019
6151
|
next();
|
|
@@ -7149,76 +7281,92 @@ function createAuthSession(password) {
|
|
|
7149
7281
|
}
|
|
7150
7282
|
|
|
7151
7283
|
// src/server/localBrowseUi.ts
|
|
7152
|
-
import { dirname as dirname2, extname as extname2, join as join6 } from "path";
|
|
7284
|
+
import { basename as basename4, dirname as dirname2, extname as extname2, join as join6 } from "path";
|
|
7153
7285
|
import { open, readFile as readFile4, readdir as readdir3, stat as stat5 } from "fs/promises";
|
|
7154
|
-
var
|
|
7155
|
-
"
|
|
7156
|
-
"
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
"
|
|
7160
|
-
"
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
"
|
|
7164
|
-
".
|
|
7165
|
-
"
|
|
7166
|
-
".
|
|
7167
|
-
".
|
|
7168
|
-
".
|
|
7169
|
-
".
|
|
7170
|
-
".
|
|
7171
|
-
".
|
|
7172
|
-
".py",
|
|
7173
|
-
".sh",
|
|
7174
|
-
".toml",
|
|
7175
|
-
".ini",
|
|
7176
|
-
".conf",
|
|
7177
|
-
".sql",
|
|
7178
|
-
".bat",
|
|
7179
|
-
".cmd",
|
|
7180
|
-
".ps1"
|
|
7286
|
+
var TEXT_LANGUAGE = {
|
|
7287
|
+
label: "text",
|
|
7288
|
+
aceMode: "text"
|
|
7289
|
+
};
|
|
7290
|
+
var BAZEL_LANGUAGE = {
|
|
7291
|
+
label: "bazel",
|
|
7292
|
+
aceMode: "python"
|
|
7293
|
+
};
|
|
7294
|
+
var FILE_LANGUAGE_RULES_BY_NAME = /* @__PURE__ */ new Map([
|
|
7295
|
+
["build", BAZEL_LANGUAGE],
|
|
7296
|
+
["build.bazel", BAZEL_LANGUAGE],
|
|
7297
|
+
["workspace", BAZEL_LANGUAGE],
|
|
7298
|
+
["workspace.bazel", BAZEL_LANGUAGE],
|
|
7299
|
+
["module.bazel", BAZEL_LANGUAGE],
|
|
7300
|
+
[".bazelrc", BAZEL_LANGUAGE],
|
|
7301
|
+
[".env", TEXT_LANGUAGE],
|
|
7302
|
+
[".gitignore", TEXT_LANGUAGE],
|
|
7303
|
+
[".npmrc", TEXT_LANGUAGE]
|
|
7181
7304
|
]);
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7305
|
+
var FILE_LANGUAGE_RULES_BY_EXTENSION = /* @__PURE__ */ new Map([
|
|
7306
|
+
[".txt", TEXT_LANGUAGE],
|
|
7307
|
+
[".log", TEXT_LANGUAGE],
|
|
7308
|
+
[".csv", TEXT_LANGUAGE],
|
|
7309
|
+
[".md", { label: "markdown", aceMode: "markdown" }],
|
|
7310
|
+
[".markdown", { label: "markdown", aceMode: "markdown" }],
|
|
7311
|
+
[".json", { label: "json", aceMode: "json" }],
|
|
7312
|
+
[".jsonc", { label: "json", aceMode: "json" }],
|
|
7313
|
+
[".yaml", { label: "yaml", aceMode: "yaml" }],
|
|
7314
|
+
[".yml", { label: "yaml", aceMode: "yaml" }],
|
|
7315
|
+
[".lua", { label: "lua", aceMode: "lua" }],
|
|
7316
|
+
[".rs", { label: "rust", aceMode: "rust" }],
|
|
7317
|
+
[".py", { label: "python", aceMode: "python" }],
|
|
7318
|
+
[".c", { label: "c", aceMode: "c_cpp" }],
|
|
7319
|
+
[".h", { label: "c/c++", aceMode: "c_cpp" }],
|
|
7320
|
+
[".cpp", { label: "c++", aceMode: "c_cpp" }],
|
|
7321
|
+
[".cc", { label: "c++", aceMode: "c_cpp" }],
|
|
7322
|
+
[".cxx", { label: "c++", aceMode: "c_cpp" }],
|
|
7323
|
+
[".hpp", { label: "c++", aceMode: "c_cpp" }],
|
|
7324
|
+
[".hh", { label: "c++", aceMode: "c_cpp" }],
|
|
7325
|
+
[".hxx", { label: "c++", aceMode: "c_cpp" }],
|
|
7326
|
+
[".toml", { label: "toml", aceMode: "toml" }],
|
|
7327
|
+
[".bzl", BAZEL_LANGUAGE],
|
|
7328
|
+
[".bazel", BAZEL_LANGUAGE],
|
|
7329
|
+
[".js", { label: "javascript", aceMode: "javascript" }],
|
|
7330
|
+
[".mjs", { label: "javascript", aceMode: "javascript" }],
|
|
7331
|
+
[".cjs", { label: "javascript", aceMode: "javascript" }],
|
|
7332
|
+
[".jsx", { label: "jsx", aceMode: "jsx" }],
|
|
7333
|
+
[".ts", { label: "typescript", aceMode: "typescript" }],
|
|
7334
|
+
[".mts", { label: "typescript", aceMode: "typescript" }],
|
|
7335
|
+
[".cts", { label: "typescript", aceMode: "typescript" }],
|
|
7336
|
+
[".tsx", { label: "tsx", aceMode: "tsx" }],
|
|
7337
|
+
[".vue", { label: "vue", aceMode: "vue" }],
|
|
7338
|
+
[".css", { label: "css", aceMode: "css" }],
|
|
7339
|
+
[".scss", { label: "scss", aceMode: "scss" }],
|
|
7340
|
+
[".html", { label: "html", aceMode: "html" }],
|
|
7341
|
+
[".htm", { label: "html", aceMode: "html" }],
|
|
7342
|
+
[".xml", { label: "xml", aceMode: "xml" }],
|
|
7343
|
+
[".sql", { label: "sql", aceMode: "sql" }],
|
|
7344
|
+
[".sh", { label: "shell", aceMode: "sh" }],
|
|
7345
|
+
[".bash", { label: "shell", aceMode: "sh" }],
|
|
7346
|
+
[".zsh", { label: "shell", aceMode: "sh" }],
|
|
7347
|
+
[".ini", { label: "ini", aceMode: "ini" }],
|
|
7348
|
+
[".conf", { label: "ini", aceMode: "ini" }],
|
|
7349
|
+
[".ps1", { label: "powershell", aceMode: "powershell" }],
|
|
7350
|
+
[".bat", TEXT_LANGUAGE],
|
|
7351
|
+
[".cmd", TEXT_LANGUAGE]
|
|
7352
|
+
]);
|
|
7353
|
+
var MAX_INLINE_PREVIEW_BYTES = 1024 * 1024;
|
|
7354
|
+
var DARK_MODE_STORAGE_KEY = "codex-web-local.dark-mode.v1";
|
|
7355
|
+
var ACE_CDN_BASE = "https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.2";
|
|
7356
|
+
function getLanguageConfigForPath(pathValue) {
|
|
7357
|
+
const fileName = basename4(pathValue).toLowerCase();
|
|
7358
|
+
if (fileName === ".env" || fileName.startsWith(".env.")) {
|
|
7359
|
+
return { language: TEXT_LANGUAGE, recognized: true };
|
|
7360
|
+
}
|
|
7361
|
+
const exactMatch = FILE_LANGUAGE_RULES_BY_NAME.get(fileName);
|
|
7362
|
+
if (exactMatch) {
|
|
7363
|
+
return { language: exactMatch, recognized: true };
|
|
7364
|
+
}
|
|
7365
|
+
const extensionMatch = FILE_LANGUAGE_RULES_BY_EXTENSION.get(extname2(fileName));
|
|
7366
|
+
if (extensionMatch) {
|
|
7367
|
+
return { language: extensionMatch, recognized: true };
|
|
7221
7368
|
}
|
|
7369
|
+
return { language: TEXT_LANGUAGE, recognized: false };
|
|
7222
7370
|
}
|
|
7223
7371
|
function normalizeLocalPath(rawPath) {
|
|
7224
7372
|
const trimmed = rawPath.trim();
|
|
@@ -7241,7 +7389,7 @@ function decodeBrowsePath(rawPath) {
|
|
|
7241
7389
|
}
|
|
7242
7390
|
}
|
|
7243
7391
|
function isTextEditablePath(pathValue) {
|
|
7244
|
-
return
|
|
7392
|
+
return getLanguageConfigForPath(pathValue).recognized;
|
|
7245
7393
|
}
|
|
7246
7394
|
function isHiddenName(value) {
|
|
7247
7395
|
return value.startsWith(".");
|
|
@@ -7275,25 +7423,157 @@ async function isTextEditableFile(localPath) {
|
|
|
7275
7423
|
return false;
|
|
7276
7424
|
}
|
|
7277
7425
|
}
|
|
7426
|
+
async function getLocalTextFileMetadata(localPath) {
|
|
7427
|
+
const { language, recognized } = getLanguageConfigForPath(localPath);
|
|
7428
|
+
try {
|
|
7429
|
+
const fileStat = await stat5(localPath);
|
|
7430
|
+
if (!fileStat.isFile()) return null;
|
|
7431
|
+
if (recognized) {
|
|
7432
|
+
return {
|
|
7433
|
+
language,
|
|
7434
|
+
sizeBytes: fileStat.size
|
|
7435
|
+
};
|
|
7436
|
+
}
|
|
7437
|
+
const isText = await probeFileIsText(localPath);
|
|
7438
|
+
if (!isText) return null;
|
|
7439
|
+
return {
|
|
7440
|
+
language,
|
|
7441
|
+
sizeBytes: fileStat.size
|
|
7442
|
+
};
|
|
7443
|
+
} catch {
|
|
7444
|
+
return null;
|
|
7445
|
+
}
|
|
7446
|
+
}
|
|
7278
7447
|
function escapeHtml(value) {
|
|
7279
7448
|
return value.replace(/&/gu, "&").replace(/</gu, "<").replace(/>/gu, ">").replace(/"/gu, """).replace(/'/gu, "'");
|
|
7280
7449
|
}
|
|
7281
7450
|
function normalizeNewProjectName(value) {
|
|
7282
7451
|
return value.trim().replace(/[\\/]+/gu, "").trim();
|
|
7283
7452
|
}
|
|
7284
|
-
function
|
|
7285
|
-
|
|
7286
|
-
const query = normalizedName ? `?newProjectName=${encodeURIComponent(normalizedName)}` : "";
|
|
7287
|
-
return `/codex-local-browse${encodeURI(pathValue)}${query}`;
|
|
7453
|
+
function normalizeLineTarget(value) {
|
|
7454
|
+
return Number.isInteger(value) && Number(value) > 0 ? Number(value) : null;
|
|
7288
7455
|
}
|
|
7289
|
-
function
|
|
7290
|
-
const
|
|
7291
|
-
const
|
|
7292
|
-
|
|
7456
|
+
function buildLocationQuery(options) {
|
|
7457
|
+
const query = new URLSearchParams();
|
|
7458
|
+
const normalizedName = normalizeNewProjectName(options?.newProjectName ?? "");
|
|
7459
|
+
const line = normalizeLineTarget(options?.line);
|
|
7460
|
+
const column = line ? normalizeLineTarget(options?.column) : null;
|
|
7461
|
+
if (normalizedName) query.set("newProjectName", normalizedName);
|
|
7462
|
+
if (line) query.set("line", String(line));
|
|
7463
|
+
if (column) query.set("column", String(column));
|
|
7464
|
+
const encoded = query.toString();
|
|
7465
|
+
return encoded ? `?${encoded}` : "";
|
|
7466
|
+
}
|
|
7467
|
+
function toBrowseHref(pathValue, options) {
|
|
7468
|
+
return `/codex-local-browse${encodeURI(pathValue)}${buildLocationQuery(options)}`;
|
|
7469
|
+
}
|
|
7470
|
+
function toEditHref(pathValue, options) {
|
|
7471
|
+
return `/codex-local-edit${encodeURI(pathValue)}${buildLocationQuery(options)}`;
|
|
7472
|
+
}
|
|
7473
|
+
function toRawFileHref(pathValue, options) {
|
|
7474
|
+
const query = new URLSearchParams({ path: pathValue });
|
|
7475
|
+
if (options?.download === true) {
|
|
7476
|
+
query.set("download", "1");
|
|
7477
|
+
}
|
|
7478
|
+
return `/codex-local-file?${query.toString()}`;
|
|
7293
7479
|
}
|
|
7294
7480
|
function escapeForInlineScriptString(value) {
|
|
7295
7481
|
return JSON.stringify(value).replace(/<\//gu, "<\\/").replace(/<!--/gu, "<\\!--").replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
|
|
7296
7482
|
}
|
|
7483
|
+
function renderStandaloneThemeBootstrapScript() {
|
|
7484
|
+
return [
|
|
7485
|
+
"(function() {",
|
|
7486
|
+
` const storageKey = ${JSON.stringify(DARK_MODE_STORAGE_KEY)};`,
|
|
7487
|
+
' const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");',
|
|
7488
|
+
" function readStoredPreference() {",
|
|
7489
|
+
" try {",
|
|
7490
|
+
" const value = window.localStorage.getItem(storageKey);",
|
|
7491
|
+
' return value === "light" || value === "dark" ? value : "system";',
|
|
7492
|
+
" } catch {",
|
|
7493
|
+
' return "system";',
|
|
7494
|
+
" }",
|
|
7495
|
+
" }",
|
|
7496
|
+
" function resolveTheme(preference) {",
|
|
7497
|
+
' if (preference === "light" || preference === "dark") return preference;',
|
|
7498
|
+
' return mediaQuery.matches ? "dark" : "light";',
|
|
7499
|
+
" }",
|
|
7500
|
+
" function applyTheme() {",
|
|
7501
|
+
" const preference = readStoredPreference();",
|
|
7502
|
+
" const resolvedTheme = resolveTheme(preference);",
|
|
7503
|
+
" document.documentElement.dataset.theme = resolvedTheme;",
|
|
7504
|
+
" document.documentElement.style.colorScheme = resolvedTheme;",
|
|
7505
|
+
" window.__codexLocalBrowseTheme = { preference, resolvedTheme };",
|
|
7506
|
+
' window.dispatchEvent(new CustomEvent("codex-local-browse-themechange", { detail: window.__codexLocalBrowseTheme }));',
|
|
7507
|
+
" }",
|
|
7508
|
+
" applyTheme();",
|
|
7509
|
+
" window.__codexApplyLocalBrowseTheme = applyTheme;",
|
|
7510
|
+
' window.addEventListener("storage", function(event) {',
|
|
7511
|
+
" if (event.key !== storageKey) return;",
|
|
7512
|
+
" applyTheme();",
|
|
7513
|
+
" });",
|
|
7514
|
+
' mediaQuery.addEventListener("change", function() {',
|
|
7515
|
+
' if (readStoredPreference() !== "system") return;',
|
|
7516
|
+
" applyTheme();",
|
|
7517
|
+
" });",
|
|
7518
|
+
"})();"
|
|
7519
|
+
].join("\n");
|
|
7520
|
+
}
|
|
7521
|
+
function renderStandaloneThemeCss() {
|
|
7522
|
+
return `
|
|
7523
|
+
:root {
|
|
7524
|
+
color-scheme: light;
|
|
7525
|
+
--lb-bg: #f3f6fb;
|
|
7526
|
+
--lb-surface: #ffffff;
|
|
7527
|
+
--lb-surface-muted: #eef3fb;
|
|
7528
|
+
--lb-surface-strong: #e7edf8;
|
|
7529
|
+
--lb-toolbar-bg: rgba(243, 246, 251, 0.96);
|
|
7530
|
+
--lb-border: #cfd9ea;
|
|
7531
|
+
--lb-border-strong: #b6c4db;
|
|
7532
|
+
--lb-text: #172033;
|
|
7533
|
+
--lb-text-muted: #52607a;
|
|
7534
|
+
--lb-link: #2457b8;
|
|
7535
|
+
--lb-button-text: #172033;
|
|
7536
|
+
--lb-accent-bg: #dfeafe;
|
|
7537
|
+
--lb-accent-border: #9db8ef;
|
|
7538
|
+
--lb-primary-bg: linear-gradient(135deg, #2f67d8 0%, #4b88ff 100%);
|
|
7539
|
+
--lb-primary-border: #2f67d8;
|
|
7540
|
+
--lb-primary-text: #f8fbff;
|
|
7541
|
+
--lb-selection: rgba(77, 124, 255, 0.18);
|
|
7542
|
+
--lb-selection-strong: rgba(77, 124, 255, 0.24);
|
|
7543
|
+
--lb-target-line: rgba(77, 124, 255, 0.14);
|
|
7544
|
+
--lb-target-stripe: #4d7cff;
|
|
7545
|
+
--lb-warning-text: #8a5a00;
|
|
7546
|
+
--lb-warning-bg: rgba(194, 136, 18, 0.12);
|
|
7547
|
+
--lb-shadow: 0 16px 40px rgba(31, 49, 82, 0.12);
|
|
7548
|
+
}
|
|
7549
|
+
:root[data-theme='dark'] {
|
|
7550
|
+
color-scheme: dark;
|
|
7551
|
+
--lb-bg: #09111f;
|
|
7552
|
+
--lb-surface: #0d182b;
|
|
7553
|
+
--lb-surface-muted: #101f3a;
|
|
7554
|
+
--lb-surface-strong: #13233d;
|
|
7555
|
+
--lb-toolbar-bg: rgba(9, 17, 31, 0.96);
|
|
7556
|
+
--lb-border: #20324d;
|
|
7557
|
+
--lb-border-strong: #36557a;
|
|
7558
|
+
--lb-text: #dbe6ff;
|
|
7559
|
+
--lb-text-muted: #8fb8ec;
|
|
7560
|
+
--lb-link: #8cc2ff;
|
|
7561
|
+
--lb-button-text: #dbe6ff;
|
|
7562
|
+
--lb-accent-bg: #162643;
|
|
7563
|
+
--lb-accent-border: #36557a;
|
|
7564
|
+
--lb-primary-bg: linear-gradient(135deg, #2e6ee6 0%, #3d8cff 100%);
|
|
7565
|
+
--lb-primary-border: #4f8de0;
|
|
7566
|
+
--lb-primary-text: #eef6ff;
|
|
7567
|
+
--lb-selection: rgba(140, 194, 255, 0.16);
|
|
7568
|
+
--lb-selection-strong: rgba(140, 194, 255, 0.3);
|
|
7569
|
+
--lb-target-line: rgba(140, 194, 255, 0.16);
|
|
7570
|
+
--lb-target-stripe: #8cc2ff;
|
|
7571
|
+
--lb-warning-text: #f0cf78;
|
|
7572
|
+
--lb-warning-bg: rgba(240, 207, 120, 0.08);
|
|
7573
|
+
--lb-shadow: 0 20px 40px rgba(0, 0, 0, 0.24);
|
|
7574
|
+
}
|
|
7575
|
+
`;
|
|
7576
|
+
}
|
|
7297
7577
|
async function getDirectoryItems(localPath) {
|
|
7298
7578
|
const entries = await readdir3(localPath, { withFileTypes: true });
|
|
7299
7579
|
const withMeta = await Promise.all(entries.map(async (entry) => {
|
|
@@ -7343,6 +7623,64 @@ function actionButtonsHtml(localPath, newProjectName) {
|
|
|
7343
7623
|
const openButton = `<button class="header-open-btn open-folder-btn" type="button" aria-label="Open current folder in Codex" title="Open folder in Codex" data-path="${escapeHtml(localPath)}" data-label="" data-status="${escapeHtml(openFolderStatusText(normalizedName))}" data-error="${escapeHtml(failureStatusText(normalizedName))}">Open folder in Codex</button>`;
|
|
7344
7624
|
return `${createButton}${openButton}`;
|
|
7345
7625
|
}
|
|
7626
|
+
function renderTextPreviewToolbar(localPath, options) {
|
|
7627
|
+
const newProjectName = normalizeNewProjectName(options?.newProjectName ?? "");
|
|
7628
|
+
const line = normalizeLineTarget(options?.line);
|
|
7629
|
+
const column = line ? normalizeLineTarget(options?.column) : null;
|
|
7630
|
+
const backHref = toBrowseHref(dirname2(localPath), { newProjectName });
|
|
7631
|
+
const rawHref = toRawFileHref(localPath);
|
|
7632
|
+
const downloadHref = toRawFileHref(localPath, { download: true });
|
|
7633
|
+
const lineLocation = line ? { newProjectName, line, column } : { newProjectName };
|
|
7634
|
+
return [
|
|
7635
|
+
`<a href="${escapeHtml(backHref)}">Back</a>`,
|
|
7636
|
+
`<a href="${escapeHtml(rawHref)}" target="_blank" rel="noopener noreferrer">Raw</a>`,
|
|
7637
|
+
`<a href="${escapeHtml(downloadHref)}">Download</a>`,
|
|
7638
|
+
`<a href="${escapeHtml(toEditHref(localPath, lineLocation))}">Edit</a>`
|
|
7639
|
+
].filter(Boolean).join("");
|
|
7640
|
+
}
|
|
7641
|
+
function formatLineTargetLabel(line, column) {
|
|
7642
|
+
if (!line) return "";
|
|
7643
|
+
if (column) return `line ${String(line)}:${String(column)}`;
|
|
7644
|
+
return `line ${String(line)}`;
|
|
7645
|
+
}
|
|
7646
|
+
function renderPlainPreviewContent(content, targetLine) {
|
|
7647
|
+
if (!targetLine || targetLine < 1) return escapeHtml(content);
|
|
7648
|
+
const trailingNewline = content.endsWith("\n");
|
|
7649
|
+
const lines = content.split("\n");
|
|
7650
|
+
if (trailingNewline) lines.pop();
|
|
7651
|
+
if (targetLine > lines.length) return escapeHtml(content);
|
|
7652
|
+
return lines.map((line, index) => {
|
|
7653
|
+
const escapedLine = escapeHtml(line || " ");
|
|
7654
|
+
if (index + 1 === targetLine) {
|
|
7655
|
+
return `<span id="previewTargetLine" class="preview-target-line">${escapedLine}</span>`;
|
|
7656
|
+
}
|
|
7657
|
+
return escapedLine;
|
|
7658
|
+
}).join("\n") + (trailingNewline ? "\n" : "");
|
|
7659
|
+
}
|
|
7660
|
+
function formatPreviewSize(sizeBytes) {
|
|
7661
|
+
if (sizeBytes >= 1024 * 1024) return `${(sizeBytes / (1024 * 1024)).toFixed(1).replace(/\.0$/u, "")} MiB`;
|
|
7662
|
+
if (sizeBytes >= 1024) return `${Math.round(sizeBytes / 1024)} KiB`;
|
|
7663
|
+
return `${sizeBytes} B`;
|
|
7664
|
+
}
|
|
7665
|
+
function renderAceLineTargetScript() {
|
|
7666
|
+
return [
|
|
7667
|
+
"function selectTargetLine(editorInstance, lineNumber, columnNumber) {",
|
|
7668
|
+
" if (!lineNumber) return;",
|
|
7669
|
+
" const zeroBasedRow = lineNumber - 1;",
|
|
7670
|
+
" const zeroBasedColumn = Math.max(0, (columnNumber ?? 1) - 1);",
|
|
7671
|
+
" editorInstance.gotoLine(lineNumber, zeroBasedColumn, true);",
|
|
7672
|
+
" editorInstance.scrollToLine(lineNumber, true, true, function() {});",
|
|
7673
|
+
" editorInstance.selection.moveCursorToPosition({ row: zeroBasedRow, column: zeroBasedColumn });",
|
|
7674
|
+
" editorInstance.selection.selectLine();",
|
|
7675
|
+
"}",
|
|
7676
|
+
"function currentAceThemeName() {",
|
|
7677
|
+
' return document.documentElement.dataset.theme === "light" ? "tomorrow" : "tomorrow_night";',
|
|
7678
|
+
"}",
|
|
7679
|
+
"function applyAceTheme(editorInstance) {",
|
|
7680
|
+
' editorInstance.setTheme("ace/theme/" + currentAceThemeName());',
|
|
7681
|
+
"}"
|
|
7682
|
+
].join("\n");
|
|
7683
|
+
}
|
|
7346
7684
|
async function getLocalDirectoryListing(localPath, options = {}) {
|
|
7347
7685
|
const entries = await readdir3(localPath, { withFileTypes: true });
|
|
7348
7686
|
const directories = entries.filter((entry) => entry.isDirectory()).map((entry) => ({
|
|
@@ -7366,10 +7704,10 @@ async function createDirectoryListingHtml(localPath, options) {
|
|
|
7366
7704
|
const parentPath = dirname2(localPath);
|
|
7367
7705
|
const rows = items.map((item) => {
|
|
7368
7706
|
const suffix = item.isDirectory ? "/" : "";
|
|
7369
|
-
const editAction = item.editable ? ` <a class="icon-btn" aria-label="Edit ${escapeHtml(item.name)}" href="${escapeHtml(toEditHref(item.path, newProjectName))}" title="Edit">\u270F\uFE0F</a>` : "";
|
|
7370
|
-
return `<li class="file-row"><a class="file-link" href="${escapeHtml(toBrowseHref(item.path, newProjectName))}">${escapeHtml(item.name)}${suffix}</a><span class="row-actions">${editAction}</span></li>`;
|
|
7707
|
+
const editAction = item.editable ? ` <a class="icon-btn" aria-label="Edit ${escapeHtml(item.name)}" href="${escapeHtml(toEditHref(item.path, { newProjectName }))}" title="Edit">\u270F\uFE0F</a>` : "";
|
|
7708
|
+
return `<li class="file-row"><a class="file-link" href="${escapeHtml(toBrowseHref(item.path, { newProjectName }))}">${escapeHtml(item.name)}${suffix}</a><span class="row-actions">${editAction}</span></li>`;
|
|
7371
7709
|
}).join("\n");
|
|
7372
|
-
const parentLink = localPath !== parentPath ? `<a class="header-parent-link" href="${escapeHtml(toBrowseHref(parentPath, newProjectName))}">..</a>` : "";
|
|
7710
|
+
const parentLink = localPath !== parentPath ? `<a class="header-parent-link" href="${escapeHtml(toBrowseHref(parentPath, { newProjectName }))}">..</a>` : "";
|
|
7373
7711
|
const pickerSummary = newProjectName ? `<p class="picker-summary">Browse to the parent folder where you want to create <strong>${escapeHtml(newProjectName)}</strong>, or open the current folder directly.</p>` : "";
|
|
7374
7712
|
const actionButtons = actionButtonsHtml(localPath, newProjectName);
|
|
7375
7713
|
return `<!doctype html>
|
|
@@ -7378,35 +7716,37 @@ async function createDirectoryListingHtml(localPath, options) {
|
|
|
7378
7716
|
<meta charset="utf-8" />
|
|
7379
7717
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7380
7718
|
<title>Index of ${escapeHtml(localPath)}</title>
|
|
7719
|
+
<script>${renderStandaloneThemeBootstrapScript()}</script>
|
|
7381
7720
|
<style>
|
|
7382
|
-
|
|
7383
|
-
|
|
7721
|
+
${renderStandaloneThemeCss()}
|
|
7722
|
+
body { font-family: ui-monospace, Menlo, Monaco, monospace; margin: 16px; background: var(--lb-bg); color: var(--lb-text); }
|
|
7723
|
+
a { color: var(--lb-link); text-decoration: none; }
|
|
7384
7724
|
a:hover { text-decoration: underline; }
|
|
7385
7725
|
ul { list-style: none; padding: 0; margin: 12px 0 0; display: flex; flex-direction: column; gap: 8px; }
|
|
7386
7726
|
.file-row { display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: center; gap: 10px; }
|
|
7387
|
-
.file-link { display: block; padding: 10px 12px; border: 1px solid
|
|
7727
|
+
.file-link { display: block; padding: 10px 12px; border: 1px solid var(--lb-border); border-radius: 10px; background: var(--lb-surface); color: var(--lb-text); overflow-wrap: anywhere; box-shadow: var(--lb-shadow); }
|
|
7388
7728
|
.header-actions { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
|
|
7389
|
-
.header-parent-link { color:
|
|
7729
|
+
.header-parent-link { color: var(--lb-link); font-size: 14px; padding: 8px 10px; border: 1px solid var(--lb-border); border-radius: 10px; background: var(--lb-surface-muted); }
|
|
7390
7730
|
.header-parent-link:hover { text-decoration: none; filter: brightness(1.08); }
|
|
7391
7731
|
.header-open-btn {
|
|
7392
7732
|
height: 42px;
|
|
7393
7733
|
padding: 0 14px;
|
|
7394
|
-
border: 1px solid
|
|
7734
|
+
border: 1px solid var(--lb-primary-border);
|
|
7395
7735
|
border-radius: 10px;
|
|
7396
|
-
background:
|
|
7397
|
-
color:
|
|
7736
|
+
background: var(--lb-primary-bg);
|
|
7737
|
+
color: var(--lb-primary-text);
|
|
7398
7738
|
font-weight: 700;
|
|
7399
7739
|
letter-spacing: 0.01em;
|
|
7400
7740
|
cursor: pointer;
|
|
7401
|
-
box-shadow:
|
|
7741
|
+
box-shadow: var(--lb-shadow);
|
|
7402
7742
|
}
|
|
7403
7743
|
.header-open-btn:hover { filter: brightness(1.08); }
|
|
7404
7744
|
.header-open-btn:disabled { opacity: 0.6; cursor: default; }
|
|
7405
|
-
.picker-summary { margin: 10px 0 0; color:
|
|
7745
|
+
.picker-summary { margin: 10px 0 0; color: var(--lb-text-muted); max-width: 60rem; line-height: 1.45; }
|
|
7406
7746
|
.row-actions { display: inline-flex; align-items: center; gap: 8px; min-width: 42px; justify-content: flex-end; }
|
|
7407
|
-
.icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 42px; height: 42px; border: 1px solid
|
|
7747
|
+
.icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 42px; height: 42px; border: 1px solid var(--lb-accent-border); border-radius: 10px; background: var(--lb-accent-bg); color: var(--lb-button-text); text-decoration: none; cursor: pointer; }
|
|
7408
7748
|
.icon-btn:hover { filter: brightness(1.08); text-decoration: none; }
|
|
7409
|
-
.status { margin: 10px 0 0; color:
|
|
7749
|
+
.status { margin: 10px 0 0; color: var(--lb-text-muted); min-height: 1.25em; }
|
|
7410
7750
|
h1 { font-size: 18px; margin: 0; word-break: break-all; }
|
|
7411
7751
|
@media (max-width: 640px) {
|
|
7412
7752
|
body { margin: 12px; }
|
|
@@ -7467,10 +7807,144 @@ async function createDirectoryListingHtml(localPath, options) {
|
|
|
7467
7807
|
</body>
|
|
7468
7808
|
</html>`;
|
|
7469
7809
|
}
|
|
7470
|
-
async function
|
|
7810
|
+
async function createTextPreviewHtml(localPath, options) {
|
|
7811
|
+
const newProjectName = normalizeNewProjectName(options?.newProjectName ?? "");
|
|
7812
|
+
const line = normalizeLineTarget(options?.line);
|
|
7813
|
+
const column = line ? normalizeLineTarget(options?.column) : null;
|
|
7814
|
+
const metadata = await getLocalTextFileMetadata(localPath);
|
|
7815
|
+
if (!metadata) {
|
|
7816
|
+
throw new Error("Only text-like files can be previewed inline.");
|
|
7817
|
+
}
|
|
7818
|
+
const toolbar = renderTextPreviewToolbar(localPath, { newProjectName, line, column });
|
|
7819
|
+
const lineTargetLabel = formatLineTargetLabel(line, column);
|
|
7820
|
+
const previewMeta = [localPath, metadata.language.label, formatPreviewSize(metadata.sizeBytes), lineTargetLabel].filter(Boolean).join(" \xB7 ");
|
|
7821
|
+
const previewUnavailable = metadata.sizeBytes > MAX_INLINE_PREVIEW_BYTES;
|
|
7822
|
+
const previewContent = previewUnavailable ? "" : await readFile4(localPath, "utf8");
|
|
7823
|
+
const previewPlainHtml = renderPlainPreviewContent(previewContent, line);
|
|
7824
|
+
const safePreviewLiteral = escapeForInlineScriptString(previewContent);
|
|
7825
|
+
return `<!doctype html>
|
|
7826
|
+
<html lang="en">
|
|
7827
|
+
<head>
|
|
7828
|
+
<meta charset="utf-8" />
|
|
7829
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7830
|
+
<title>${escapeHtml(basename4(localPath))}</title>
|
|
7831
|
+
<script>${renderStandaloneThemeBootstrapScript()}</script>
|
|
7832
|
+
<style>
|
|
7833
|
+
${renderStandaloneThemeCss()}
|
|
7834
|
+
html, body { margin: 0; width: 100%; min-height: 100%; }
|
|
7835
|
+
body { min-height: 100vh; font-family: ui-monospace, Menlo, Monaco, monospace; background: var(--lb-bg); color: var(--lb-text); display: flex; flex-direction: column; }
|
|
7836
|
+
a { color: inherit; text-decoration: none; }
|
|
7837
|
+
.toolbar { position: sticky; top: 0; z-index: 10; display: flex; flex-direction: column; gap: 10px; padding: 12px 16px; background: var(--lb-toolbar-bg); backdrop-filter: blur(8px); border-bottom: 1px solid var(--lb-border); }
|
|
7838
|
+
.toolbar-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
7839
|
+
.toolbar-row a { display: inline-flex; align-items: center; justify-content: center; min-height: 38px; padding: 0 12px; border: 1px solid var(--lb-accent-border); border-radius: 10px; background: var(--lb-accent-bg); color: var(--lb-button-text); }
|
|
7840
|
+
.toolbar-row a:hover { filter: brightness(1.08); }
|
|
7841
|
+
.meta { color: var(--lb-text-muted); font-size: 12px; overflow-wrap: anywhere; line-height: 1.5; }
|
|
7842
|
+
.preview-shell { flex: 1 1 auto; min-height: 0; display: flex; padding: 18px 16px 24px; }
|
|
7843
|
+
.preview-card { flex: 1 1 auto; min-height: 0; max-width: 100%; border: 1px solid var(--lb-border); border-radius: 14px; background: var(--lb-surface); overflow: hidden; box-shadow: var(--lb-shadow); display: flex; flex-direction: column; }
|
|
7844
|
+
.preview-notice { margin: 0; padding: 12px 16px; border-bottom: 1px solid var(--lb-border); color: var(--lb-warning-text); background: var(--lb-warning-bg); }
|
|
7845
|
+
.preview-unavailable { padding: 26px 20px; }
|
|
7846
|
+
.preview-unavailable-title { margin: 0 0 8px; font-size: 15px; font-weight: 700; }
|
|
7847
|
+
.preview-unavailable-text { margin: 0; color: var(--lb-text-muted); line-height: 1.6; }
|
|
7848
|
+
.preview-plain {
|
|
7849
|
+
flex: 1 1 auto;
|
|
7850
|
+
box-sizing: border-box;
|
|
7851
|
+
margin: 0;
|
|
7852
|
+
padding: 18px 20px 24px;
|
|
7853
|
+
min-height: 0;
|
|
7854
|
+
overflow: auto;
|
|
7855
|
+
white-space: pre-wrap;
|
|
7856
|
+
overflow-wrap: anywhere;
|
|
7857
|
+
tab-size: 2;
|
|
7858
|
+
line-height: 1.55;
|
|
7859
|
+
color: var(--lb-text);
|
|
7860
|
+
background: var(--lb-surface);
|
|
7861
|
+
}
|
|
7862
|
+
.preview-target-line {
|
|
7863
|
+
display: inline-block;
|
|
7864
|
+
min-width: 100%;
|
|
7865
|
+
margin: 0 -20px;
|
|
7866
|
+
padding: 0 20px;
|
|
7867
|
+
background: var(--lb-target-line);
|
|
7868
|
+
box-shadow: inset 3px 0 0 var(--lb-target-stripe);
|
|
7869
|
+
}
|
|
7870
|
+
#previewEditor { flex: 1 1 auto; width: 100%; min-height: 0; }
|
|
7871
|
+
.ace_editor { width: 100% !important; height: 100% !important; }
|
|
7872
|
+
.ace_marker-layer .ace_active-line { background: transparent !important; }
|
|
7873
|
+
.ace_marker-layer .ace_selection { background: var(--lb-selection) !important; }
|
|
7874
|
+
@media (max-width: 640px) {
|
|
7875
|
+
.toolbar { padding: 12px; }
|
|
7876
|
+
.preview-shell { padding: 12px; }
|
|
7877
|
+
}
|
|
7878
|
+
</style>
|
|
7879
|
+
</head>
|
|
7880
|
+
<body>
|
|
7881
|
+
<div class="toolbar">
|
|
7882
|
+
<div class="toolbar-row">${toolbar}</div>
|
|
7883
|
+
<div class="meta">${escapeHtml(previewMeta)}</div>
|
|
7884
|
+
</div>
|
|
7885
|
+
<main class="preview-shell">
|
|
7886
|
+
<section class="preview-card">
|
|
7887
|
+
${previewUnavailable ? `<div class="preview-unavailable">
|
|
7888
|
+
<p class="preview-unavailable-title">Inline preview disabled</p>
|
|
7889
|
+
<p class="preview-unavailable-text">This file is larger than ${String(Math.floor(MAX_INLINE_PREVIEW_BYTES / 1024))} KiB. Use <strong>Raw</strong> or <strong>Download</strong> instead.</p>
|
|
7890
|
+
</div>` : `<pre id="previewFallback" class="preview-plain">${previewPlainHtml}</pre>
|
|
7891
|
+
<div id="previewEditor" hidden></div>`}
|
|
7892
|
+
</section>
|
|
7893
|
+
</main>
|
|
7894
|
+
${previewUnavailable ? "" : `<script src="${ACE_CDN_BASE}/ace.min.js"></script>
|
|
7895
|
+
<script>
|
|
7896
|
+
const targetLineNumber = ${line ?? "null"};
|
|
7897
|
+
const targetColumnNumber = ${column ?? "null"};
|
|
7898
|
+
const previewFallback = document.getElementById('previewFallback');
|
|
7899
|
+
const previewTargetLine = document.getElementById('previewTargetLine');
|
|
7900
|
+
const previewEditor = document.getElementById('previewEditor');
|
|
7901
|
+
${renderAceLineTargetScript()}
|
|
7902
|
+
if (previewTargetLine) {
|
|
7903
|
+
previewTargetLine.scrollIntoView({ block: 'center' });
|
|
7904
|
+
}
|
|
7905
|
+
if (window.ace && previewEditor) {
|
|
7906
|
+
previewEditor.hidden = false;
|
|
7907
|
+
if (previewFallback) previewFallback.hidden = true;
|
|
7908
|
+
ace.config.set('basePath', '${ACE_CDN_BASE}/');
|
|
7909
|
+
const editor = ace.edit('previewEditor');
|
|
7910
|
+
applyAceTheme(editor);
|
|
7911
|
+
editor.session.setMode('ace/mode/${escapeHtml(metadata.language.aceMode)}');
|
|
7912
|
+
editor.session.setUseWorker(false);
|
|
7913
|
+
editor.setValue(${safePreviewLiteral}, -1);
|
|
7914
|
+
editor.setOptions({
|
|
7915
|
+
readOnly: true,
|
|
7916
|
+
highlightActiveLine: !!targetLineNumber,
|
|
7917
|
+
highlightGutterLine: false,
|
|
7918
|
+
showPrintMargin: false,
|
|
7919
|
+
fontSize: '13px',
|
|
7920
|
+
wrap: true,
|
|
7921
|
+
behavioursEnabled: false,
|
|
7922
|
+
displayIndentGuides: true,
|
|
7923
|
+
});
|
|
7924
|
+
editor.renderer.setShowGutter(true);
|
|
7925
|
+
editor.renderer.$cursorLayer.element.style.display = 'none';
|
|
7926
|
+
if (targetLineNumber) {
|
|
7927
|
+
selectTargetLine(editor, targetLineNumber, targetColumnNumber);
|
|
7928
|
+
}
|
|
7929
|
+
window.addEventListener('codex-local-browse-themechange', function() {
|
|
7930
|
+
applyAceTheme(editor);
|
|
7931
|
+
});
|
|
7932
|
+
editor.resize();
|
|
7933
|
+
}
|
|
7934
|
+
</script>`}
|
|
7935
|
+
</body>
|
|
7936
|
+
</html>`;
|
|
7937
|
+
}
|
|
7938
|
+
async function createTextEditorHtml(localPath, options) {
|
|
7939
|
+
const newProjectName = normalizeNewProjectName(options?.newProjectName ?? "");
|
|
7940
|
+
const line = normalizeLineTarget(options?.line);
|
|
7941
|
+
const column = line ? normalizeLineTarget(options?.column) : null;
|
|
7942
|
+
const metadata = await getLocalTextFileMetadata(localPath);
|
|
7943
|
+
if (!metadata) {
|
|
7944
|
+
throw new Error("Only text-like files are editable.");
|
|
7945
|
+
}
|
|
7471
7946
|
const content = await readFile4(localPath, "utf8");
|
|
7472
7947
|
const parentPath = dirname2(localPath);
|
|
7473
|
-
const language = languageForPath(localPath);
|
|
7474
7948
|
const safeContentLiteral = escapeForInlineScriptString(content);
|
|
7475
7949
|
return `<!doctype html>
|
|
7476
7950
|
<html lang="en">
|
|
@@ -7478,49 +7952,64 @@ async function createTextEditorHtml(localPath) {
|
|
|
7478
7952
|
<meta charset="utf-8" />
|
|
7479
7953
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7480
7954
|
<title>Edit ${escapeHtml(localPath)}</title>
|
|
7955
|
+
<script>${renderStandaloneThemeBootstrapScript()}</script>
|
|
7481
7956
|
<style>
|
|
7957
|
+
${renderStandaloneThemeCss()}
|
|
7482
7958
|
html, body { width: 100%; height: 100%; margin: 0; }
|
|
7483
|
-
body { font-family: ui-monospace, Menlo, Monaco, monospace; background:
|
|
7484
|
-
.toolbar { position: sticky; top: 0; z-index: 10; display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; background:
|
|
7959
|
+
body { font-family: ui-monospace, Menlo, Monaco, monospace; background: var(--lb-bg); color: var(--lb-text); display: flex; flex-direction: column; overflow: hidden; }
|
|
7960
|
+
.toolbar { position: sticky; top: 0; z-index: 10; display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; background: var(--lb-bg); border-bottom: 1px solid var(--lb-border); }
|
|
7485
7961
|
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
|
7486
|
-
button, a { background:
|
|
7962
|
+
button, a { background: var(--lb-accent-bg); color: var(--lb-button-text); border: 1px solid var(--lb-accent-border); padding: 6px 10px; border-radius: 6px; text-decoration: none; cursor: pointer; }
|
|
7487
7963
|
button:hover, a:hover { filter: brightness(1.08); }
|
|
7488
7964
|
#editor { flex: 1 1 auto; min-height: 0; width: 100%; border: none; overflow: hidden; }
|
|
7489
|
-
#status { margin-left: 8px; color:
|
|
7490
|
-
.ace_editor {
|
|
7491
|
-
.
|
|
7492
|
-
.ace_marker-layer .
|
|
7493
|
-
.
|
|
7494
|
-
.meta { opacity: 0.9; font-size: 12px; overflow-wrap: anywhere; }
|
|
7965
|
+
#status { margin-left: 8px; color: var(--lb-text-muted); }
|
|
7966
|
+
.ace_editor { width: 100% !important; height: 100% !important; }
|
|
7967
|
+
.ace_marker-layer .ace_active-line { background: var(--lb-target-line) !important; }
|
|
7968
|
+
.ace_marker-layer .ace_selection { background: var(--lb-selection-strong) !important; }
|
|
7969
|
+
.meta { opacity: 0.9; font-size: 12px; overflow-wrap: anywhere; color: var(--lb-text-muted); }
|
|
7495
7970
|
</style>
|
|
7496
7971
|
</head>
|
|
7497
7972
|
<body>
|
|
7498
7973
|
<div class="toolbar">
|
|
7499
7974
|
<div class="row">
|
|
7500
|
-
<a href="${escapeHtml(toBrowseHref(parentPath))}">Back</a>
|
|
7975
|
+
<a href="${escapeHtml(toBrowseHref(parentPath, { newProjectName }))}">Back</a>
|
|
7976
|
+
<a href="${escapeHtml(toBrowseHref(localPath, { newProjectName, line, column }))}">Preview</a>
|
|
7977
|
+
<a href="${escapeHtml(toRawFileHref(localPath))}" target="_blank" rel="noopener noreferrer">Raw</a>
|
|
7978
|
+
<a href="${escapeHtml(toRawFileHref(localPath, { download: true }))}">Download</a>
|
|
7501
7979
|
<button id="saveBtn" type="button">Save</button>
|
|
7502
7980
|
<span id="status"></span>
|
|
7503
7981
|
</div>
|
|
7504
|
-
<div class="meta">${escapeHtml(localPath)
|
|
7982
|
+
<div class="meta">${escapeHtml([localPath, metadata.language.label, formatLineTargetLabel(line, column)].filter(Boolean).join(" \xB7 "))}</div>
|
|
7505
7983
|
</div>
|
|
7506
7984
|
<div id="editor"></div>
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7985
|
+
<script src="${ACE_CDN_BASE}/ace.min.js"></script>
|
|
7986
|
+
<script>
|
|
7987
|
+
const targetLineNumber = ${line ?? "null"};
|
|
7988
|
+
const targetColumnNumber = ${column ?? "null"};
|
|
7989
|
+
const saveBtn = document.getElementById('saveBtn');
|
|
7990
|
+
const status = document.getElementById('status');
|
|
7991
|
+
${renderAceLineTargetScript()}
|
|
7992
|
+
ace.config.set('basePath', '${ACE_CDN_BASE}/');
|
|
7993
|
+
const editor = ace.edit('editor');
|
|
7994
|
+
applyAceTheme(editor);
|
|
7995
|
+
editor.session.setMode('ace/mode/${escapeHtml(metadata.language.aceMode)}');
|
|
7996
|
+
editor.session.setUseWorker(false);
|
|
7997
|
+
editor.setValue(${safeContentLiteral}, -1);
|
|
7515
7998
|
editor.setOptions({
|
|
7516
7999
|
fontSize: '13px',
|
|
7517
8000
|
wrap: true,
|
|
7518
8001
|
showPrintMargin: false,
|
|
7519
8002
|
useSoftTabs: true,
|
|
7520
8003
|
tabSize: 2,
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
8004
|
+
behavioursEnabled: true,
|
|
8005
|
+
});
|
|
8006
|
+
if (targetLineNumber) {
|
|
8007
|
+
selectTargetLine(editor, targetLineNumber, targetColumnNumber);
|
|
8008
|
+
}
|
|
8009
|
+
window.addEventListener('codex-local-browse-themechange', function() {
|
|
8010
|
+
applyAceTheme(editor);
|
|
8011
|
+
});
|
|
8012
|
+
editor.resize();
|
|
7524
8013
|
|
|
7525
8014
|
saveBtn.addEventListener('click', async () => {
|
|
7526
8015
|
status.textContent = 'Saving...';
|
|
@@ -7586,6 +8075,34 @@ function readWildcardPathParam(value) {
|
|
|
7586
8075
|
if (Array.isArray(value)) return value.join("/");
|
|
7587
8076
|
return "";
|
|
7588
8077
|
}
|
|
8078
|
+
function readBooleanQueryFlag(value) {
|
|
8079
|
+
return typeof value === "string" && ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
8080
|
+
}
|
|
8081
|
+
function readPositiveIntegerQueryParam(value) {
|
|
8082
|
+
if (typeof value !== "string") return null;
|
|
8083
|
+
const parsed = Number(value);
|
|
8084
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
8085
|
+
}
|
|
8086
|
+
function localFileErrorResponse(error) {
|
|
8087
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code ?? "") : "";
|
|
8088
|
+
const statusCode = typeof error === "object" && error !== null && "statusCode" in error ? Number(error.statusCode) : NaN;
|
|
8089
|
+
if (code === "ENOENT" || statusCode === 404) {
|
|
8090
|
+
return { status: 404, body: { error: "File not found." } };
|
|
8091
|
+
}
|
|
8092
|
+
if (code === "EACCES" || code === "EPERM" || statusCode === 403) {
|
|
8093
|
+
return { status: 403, body: { error: "Access denied." } };
|
|
8094
|
+
}
|
|
8095
|
+
return { status: 500, body: { error: "Failed to read file." } };
|
|
8096
|
+
}
|
|
8097
|
+
function sendLocalFileJsonError(res, error) {
|
|
8098
|
+
if (res.headersSent) return;
|
|
8099
|
+
const response = localFileErrorResponse(error);
|
|
8100
|
+
res.status(response.status).json(response.body);
|
|
8101
|
+
}
|
|
8102
|
+
function attachmentContentDisposition(pathValue) {
|
|
8103
|
+
const fileName = basename5(pathValue).replace(/["\\]/gu, "_");
|
|
8104
|
+
return `attachment; filename="${fileName}"`;
|
|
8105
|
+
}
|
|
7589
8106
|
function createServer(options = {}) {
|
|
7590
8107
|
const app = express();
|
|
7591
8108
|
const bridge = createCodexBridgeMiddleware();
|
|
@@ -7613,19 +8130,87 @@ function createServer(options = {}) {
|
|
|
7613
8130
|
if (!res.headersSent) res.status(404).json({ error: "Image file not found." });
|
|
7614
8131
|
});
|
|
7615
8132
|
});
|
|
7616
|
-
app.
|
|
8133
|
+
app.post("/codex-api/local-paths/probe", express.json({ limit: "64kb" }), async (req, res) => {
|
|
8134
|
+
const body = req.body && typeof req.body === "object" && !Array.isArray(req.body) ? req.body : {};
|
|
8135
|
+
const rawPaths = Array.isArray(body.paths) ? body.paths : [];
|
|
8136
|
+
const normalizedPaths = [];
|
|
8137
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8138
|
+
for (const candidate of rawPaths.slice(0, 200)) {
|
|
8139
|
+
if (typeof candidate !== "string") continue;
|
|
8140
|
+
const normalized = normalizeLocalPath(candidate);
|
|
8141
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
8142
|
+
seen.add(normalized);
|
|
8143
|
+
normalizedPaths.push(normalized);
|
|
8144
|
+
}
|
|
8145
|
+
const entries = await Promise.all(normalizedPaths.map(async (pathValue) => {
|
|
8146
|
+
if (!isAbsolute3(pathValue)) {
|
|
8147
|
+
return {
|
|
8148
|
+
path: pathValue,
|
|
8149
|
+
exists: false,
|
|
8150
|
+
isDirectory: false
|
|
8151
|
+
};
|
|
8152
|
+
}
|
|
8153
|
+
try {
|
|
8154
|
+
const fileStat = await stat6(pathValue);
|
|
8155
|
+
return {
|
|
8156
|
+
path: pathValue,
|
|
8157
|
+
exists: true,
|
|
8158
|
+
isDirectory: fileStat.isDirectory()
|
|
8159
|
+
};
|
|
8160
|
+
} catch {
|
|
8161
|
+
return {
|
|
8162
|
+
path: pathValue,
|
|
8163
|
+
exists: false,
|
|
8164
|
+
isDirectory: false
|
|
8165
|
+
};
|
|
8166
|
+
}
|
|
8167
|
+
}));
|
|
8168
|
+
res.status(200).json({ data: { entries } });
|
|
8169
|
+
});
|
|
8170
|
+
app.get("/codex-local-file", async (req, res) => {
|
|
7617
8171
|
const rawPath = typeof req.query.path === "string" ? req.query.path : "";
|
|
7618
8172
|
const localPath = normalizeLocalPath(rawPath);
|
|
8173
|
+
const wantsDownload = readBooleanQueryFlag(req.query.download);
|
|
7619
8174
|
if (!localPath || !isAbsolute3(localPath)) {
|
|
7620
8175
|
res.status(400).json({ error: "Expected absolute local file path." });
|
|
7621
8176
|
return;
|
|
7622
8177
|
}
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
8178
|
+
try {
|
|
8179
|
+
const fileStat = await stat6(localPath);
|
|
8180
|
+
if (!fileStat.isFile()) {
|
|
8181
|
+
res.status(400).json({ error: "Expected file path." });
|
|
8182
|
+
return;
|
|
8183
|
+
}
|
|
8184
|
+
const textMetadata = await getLocalTextFileMetadata(localPath);
|
|
8185
|
+
res.setHeader("Cache-Control", "private, no-store");
|
|
8186
|
+
if (wantsDownload) {
|
|
8187
|
+
res.setHeader("Content-Disposition", attachmentContentDisposition(localPath));
|
|
8188
|
+
res.sendFile(localPath, { dotfiles: "allow" }, (error) => {
|
|
8189
|
+
if (!error) return;
|
|
8190
|
+
sendLocalFileJsonError(res, error);
|
|
8191
|
+
});
|
|
8192
|
+
return;
|
|
8193
|
+
}
|
|
8194
|
+
if (textMetadata) {
|
|
8195
|
+
const stream = createReadStream2(localPath, { encoding: "utf8" });
|
|
8196
|
+
stream.on("open", () => {
|
|
8197
|
+
res.status(200).type("text/plain; charset=utf-8");
|
|
8198
|
+
});
|
|
8199
|
+
stream.on("error", (error) => {
|
|
8200
|
+
stream.destroy();
|
|
8201
|
+
sendLocalFileJsonError(res, error);
|
|
8202
|
+
});
|
|
8203
|
+
stream.pipe(res);
|
|
8204
|
+
return;
|
|
8205
|
+
}
|
|
8206
|
+
res.setHeader("Content-Disposition", "inline");
|
|
8207
|
+
res.sendFile(localPath, { dotfiles: "allow" }, (error) => {
|
|
8208
|
+
if (!error) return;
|
|
8209
|
+
sendLocalFileJsonError(res, error);
|
|
8210
|
+
});
|
|
8211
|
+
} catch (error) {
|
|
8212
|
+
sendLocalFileJsonError(res, error);
|
|
8213
|
+
}
|
|
7629
8214
|
});
|
|
7630
8215
|
app.get("/codex-local-directories", async (req, res) => {
|
|
7631
8216
|
const rawPath = typeof req.query.path === "string" ? req.query.path : "";
|
|
@@ -7651,6 +8236,8 @@ function createServer(options = {}) {
|
|
|
7651
8236
|
const rawPath = readWildcardPathParam(req.params.path);
|
|
7652
8237
|
const localPath = decodeBrowsePath(`/${rawPath}`);
|
|
7653
8238
|
const newProjectName = typeof req.query.newProjectName === "string" ? req.query.newProjectName : "";
|
|
8239
|
+
const line = readPositiveIntegerQueryParam(req.query.line);
|
|
8240
|
+
const column = line ? readPositiveIntegerQueryParam(req.query.column) : null;
|
|
7654
8241
|
if (!localPath || !isAbsolute3(localPath)) {
|
|
7655
8242
|
res.status(400).json({ error: "Expected absolute local file path." });
|
|
7656
8243
|
return;
|
|
@@ -7663,6 +8250,13 @@ function createServer(options = {}) {
|
|
|
7663
8250
|
res.status(200).type("text/html; charset=utf-8").send(html);
|
|
7664
8251
|
return;
|
|
7665
8252
|
}
|
|
8253
|
+
const textMetadata = await getLocalTextFileMetadata(localPath);
|
|
8254
|
+
if (textMetadata) {
|
|
8255
|
+
const html = await createTextPreviewHtml(localPath, { newProjectName, line, column });
|
|
8256
|
+
res.status(200).type("text/html; charset=utf-8").send(html);
|
|
8257
|
+
return;
|
|
8258
|
+
}
|
|
8259
|
+
res.setHeader("Content-Disposition", "attachment");
|
|
7666
8260
|
res.sendFile(localPath, { dotfiles: "allow" }, (error) => {
|
|
7667
8261
|
if (!error) return;
|
|
7668
8262
|
if (!res.headersSent) res.status(404).json({ error: "File not found." });
|
|
@@ -7674,6 +8268,9 @@ function createServer(options = {}) {
|
|
|
7674
8268
|
app.get("/codex-local-edit/*path", async (req, res) => {
|
|
7675
8269
|
const rawPath = readWildcardPathParam(req.params.path);
|
|
7676
8270
|
const localPath = decodeBrowsePath(`/${rawPath}`);
|
|
8271
|
+
const newProjectName = typeof req.query.newProjectName === "string" ? req.query.newProjectName : "";
|
|
8272
|
+
const line = readPositiveIntegerQueryParam(req.query.line);
|
|
8273
|
+
const column = line ? readPositiveIntegerQueryParam(req.query.column) : null;
|
|
7677
8274
|
if (!localPath || !isAbsolute3(localPath)) {
|
|
7678
8275
|
res.status(400).json({ error: "Expected absolute local file path." });
|
|
7679
8276
|
return;
|
|
@@ -7684,7 +8281,11 @@ function createServer(options = {}) {
|
|
|
7684
8281
|
res.status(400).json({ error: "Expected file path." });
|
|
7685
8282
|
return;
|
|
7686
8283
|
}
|
|
7687
|
-
|
|
8284
|
+
if (!await isTextEditableFile(localPath)) {
|
|
8285
|
+
res.status(415).json({ error: "Only text-like files are editable." });
|
|
8286
|
+
return;
|
|
8287
|
+
}
|
|
8288
|
+
const html = await createTextEditorHtml(localPath, { newProjectName, line, column });
|
|
7688
8289
|
res.status(200).type("text/html; charset=utf-8").send(html);
|
|
7689
8290
|
} catch {
|
|
7690
8291
|
res.status(404).json({ error: "File not found." });
|