codexui-android 0.1.85 → 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-BYXF27M-.js → ReviewPane-Blnhnm6J.js} +2 -2
- package/dist/assets/{ReviewPane-BYXF27M-.js.map → ReviewPane-Blnhnm6J.js.map} +1 -1
- package/dist/assets/{SkillsHub-GpJ2Lrm3.js → SkillsHub-C93DMbLM.js} +2 -2
- package/dist/assets/{SkillsHub-GpJ2Lrm3.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-JQMCS7KJ.js → chunk-PUR7OUAG.js} +41 -36
- package/dist-cli/chunk-PUR7OUAG.js.map +1 -0
- package/dist-cli/index.js +724 -139
- package/dist-cli/index.js.map +1 -1
- package/dist-cli/instrument.js +1 -1
- package/package.json +10 -4
- package/dist/assets/ThreadConversation-BNp42k0F.js +0 -40
- package/dist/assets/ThreadConversation-BNp42k0F.js.map +0 -1
- package/dist/assets/index-CWwHJTfC.js +0 -92
- package/dist/assets/index-CWwHJTfC.js.map +0 -1
- package/dist-cli/chunk-JQMCS7KJ.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
|
|
@@ -3778,6 +3779,78 @@ function spawnSyncCommand(command, args = [], options = {}) {
|
|
|
3778
3779
|
var PROVIDER_MODELS_FETCH_TIMEOUT_MS = 5e3;
|
|
3779
3780
|
var THREAD_RESPONSE_TURN_LIMIT = 10;
|
|
3780
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
|
+
}
|
|
3781
3854
|
function asRecord5(value) {
|
|
3782
3855
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
3783
3856
|
}
|
|
@@ -5965,10 +6038,21 @@ async function loadAllThreadsForSearch(appServer) {
|
|
|
5965
6038
|
}
|
|
5966
6039
|
cursor = typeof response?.nextCursor === "string" && response.nextCursor.length > 0 ? response.nextCursor : null;
|
|
5967
6040
|
} while (cursor);
|
|
5968
|
-
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);
|
|
5969
6053
|
const concurrency = 4;
|
|
5970
|
-
for (let offset = 0; offset <
|
|
5971
|
-
const batch =
|
|
6054
|
+
for (let offset = 0; offset < fullTextThreads.length; offset += concurrency) {
|
|
6055
|
+
const batch = fullTextThreads.slice(offset, offset + concurrency);
|
|
5972
6056
|
const loaded = await Promise.all(batch.map(async (thread) => {
|
|
5973
6057
|
try {
|
|
5974
6058
|
const readResponse = await appServer.rpc("thread/read", {
|
|
@@ -5977,27 +6061,23 @@ async function loadAllThreadsForSearch(appServer) {
|
|
|
5977
6061
|
});
|
|
5978
6062
|
const messageText = extractThreadMessageText(readResponse);
|
|
5979
6063
|
const searchableText = [thread.title, thread.preview, messageText].filter(Boolean).join("\n");
|
|
5980
|
-
return {
|
|
6064
|
+
return [thread.id, {
|
|
5981
6065
|
id: thread.id,
|
|
5982
6066
|
title: thread.title,
|
|
5983
6067
|
preview: thread.preview,
|
|
5984
6068
|
messageText,
|
|
5985
6069
|
searchableText
|
|
5986
|
-
};
|
|
6070
|
+
}];
|
|
5987
6071
|
} catch {
|
|
5988
|
-
|
|
5989
|
-
return {
|
|
5990
|
-
id: thread.id,
|
|
5991
|
-
title: thread.title,
|
|
5992
|
-
preview: thread.preview,
|
|
5993
|
-
messageText: "",
|
|
5994
|
-
searchableText
|
|
5995
|
-
};
|
|
6072
|
+
return null;
|
|
5996
6073
|
}
|
|
5997
6074
|
}));
|
|
5998
|
-
|
|
6075
|
+
for (const row of loaded) {
|
|
6076
|
+
if (!row) continue;
|
|
6077
|
+
docsById.set(row[0], row[1]);
|
|
6078
|
+
}
|
|
5999
6079
|
}
|
|
6000
|
-
return
|
|
6080
|
+
return Array.from(docsById.values());
|
|
6001
6081
|
});
|
|
6002
6082
|
}
|
|
6003
6083
|
async function buildThreadSearchIndex(appServer) {
|
|
@@ -6030,6 +6110,42 @@ function createCodexBridgeMiddleware() {
|
|
|
6030
6110
|
}).catch(() => {
|
|
6031
6111
|
});
|
|
6032
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);
|
|
6033
6149
|
try {
|
|
6034
6150
|
if (!req.url) {
|
|
6035
6151
|
next();
|
|
@@ -7165,76 +7281,92 @@ function createAuthSession(password) {
|
|
|
7165
7281
|
}
|
|
7166
7282
|
|
|
7167
7283
|
// src/server/localBrowseUi.ts
|
|
7168
|
-
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";
|
|
7169
7285
|
import { open, readFile as readFile4, readdir as readdir3, stat as stat5 } from "fs/promises";
|
|
7170
|
-
var
|
|
7171
|
-
"
|
|
7172
|
-
"
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
"
|
|
7176
|
-
"
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
"
|
|
7180
|
-
".
|
|
7181
|
-
"
|
|
7182
|
-
".
|
|
7183
|
-
".
|
|
7184
|
-
".
|
|
7185
|
-
".
|
|
7186
|
-
".
|
|
7187
|
-
".
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
".
|
|
7191
|
-
".
|
|
7192
|
-
".
|
|
7193
|
-
".
|
|
7194
|
-
".
|
|
7195
|
-
".
|
|
7196
|
-
".
|
|
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]
|
|
7304
|
+
]);
|
|
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]
|
|
7197
7352
|
]);
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
case ".jsx":
|
|
7206
|
-
return "javascript";
|
|
7207
|
-
case ".tsx":
|
|
7208
|
-
return "typescript";
|
|
7209
|
-
case ".py":
|
|
7210
|
-
return "python";
|
|
7211
|
-
case ".sh":
|
|
7212
|
-
return "sh";
|
|
7213
|
-
case ".css":
|
|
7214
|
-
case ".scss":
|
|
7215
|
-
return "css";
|
|
7216
|
-
case ".html":
|
|
7217
|
-
case ".htm":
|
|
7218
|
-
return "html";
|
|
7219
|
-
case ".json":
|
|
7220
|
-
return "json";
|
|
7221
|
-
case ".md":
|
|
7222
|
-
return "markdown";
|
|
7223
|
-
case ".yaml":
|
|
7224
|
-
case ".yml":
|
|
7225
|
-
return "yaml";
|
|
7226
|
-
case ".xml":
|
|
7227
|
-
return "xml";
|
|
7228
|
-
case ".sql":
|
|
7229
|
-
return "sql";
|
|
7230
|
-
case ".toml":
|
|
7231
|
-
return "ini";
|
|
7232
|
-
case ".ini":
|
|
7233
|
-
case ".conf":
|
|
7234
|
-
return "ini";
|
|
7235
|
-
default:
|
|
7236
|
-
return "plaintext";
|
|
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 };
|
|
7237
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 };
|
|
7368
|
+
}
|
|
7369
|
+
return { language: TEXT_LANGUAGE, recognized: false };
|
|
7238
7370
|
}
|
|
7239
7371
|
function normalizeLocalPath(rawPath) {
|
|
7240
7372
|
const trimmed = rawPath.trim();
|
|
@@ -7257,7 +7389,7 @@ function decodeBrowsePath(rawPath) {
|
|
|
7257
7389
|
}
|
|
7258
7390
|
}
|
|
7259
7391
|
function isTextEditablePath(pathValue) {
|
|
7260
|
-
return
|
|
7392
|
+
return getLanguageConfigForPath(pathValue).recognized;
|
|
7261
7393
|
}
|
|
7262
7394
|
function isHiddenName(value) {
|
|
7263
7395
|
return value.startsWith(".");
|
|
@@ -7291,25 +7423,157 @@ async function isTextEditableFile(localPath) {
|
|
|
7291
7423
|
return false;
|
|
7292
7424
|
}
|
|
7293
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
|
+
}
|
|
7294
7447
|
function escapeHtml(value) {
|
|
7295
7448
|
return value.replace(/&/gu, "&").replace(/</gu, "<").replace(/>/gu, ">").replace(/"/gu, """).replace(/'/gu, "'");
|
|
7296
7449
|
}
|
|
7297
7450
|
function normalizeNewProjectName(value) {
|
|
7298
7451
|
return value.trim().replace(/[\\/]+/gu, "").trim();
|
|
7299
7452
|
}
|
|
7300
|
-
function
|
|
7301
|
-
|
|
7302
|
-
const query = normalizedName ? `?newProjectName=${encodeURIComponent(normalizedName)}` : "";
|
|
7303
|
-
return `/codex-local-browse${encodeURI(pathValue)}${query}`;
|
|
7453
|
+
function normalizeLineTarget(value) {
|
|
7454
|
+
return Number.isInteger(value) && Number(value) > 0 ? Number(value) : null;
|
|
7304
7455
|
}
|
|
7305
|
-
function
|
|
7306
|
-
const
|
|
7307
|
-
const
|
|
7308
|
-
|
|
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()}`;
|
|
7309
7479
|
}
|
|
7310
7480
|
function escapeForInlineScriptString(value) {
|
|
7311
7481
|
return JSON.stringify(value).replace(/<\//gu, "<\\/").replace(/<!--/gu, "<\\!--").replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
|
|
7312
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
|
+
}
|
|
7313
7577
|
async function getDirectoryItems(localPath) {
|
|
7314
7578
|
const entries = await readdir3(localPath, { withFileTypes: true });
|
|
7315
7579
|
const withMeta = await Promise.all(entries.map(async (entry) => {
|
|
@@ -7359,6 +7623,64 @@ function actionButtonsHtml(localPath, newProjectName) {
|
|
|
7359
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>`;
|
|
7360
7624
|
return `${createButton}${openButton}`;
|
|
7361
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
|
+
}
|
|
7362
7684
|
async function getLocalDirectoryListing(localPath, options = {}) {
|
|
7363
7685
|
const entries = await readdir3(localPath, { withFileTypes: true });
|
|
7364
7686
|
const directories = entries.filter((entry) => entry.isDirectory()).map((entry) => ({
|
|
@@ -7382,10 +7704,10 @@ async function createDirectoryListingHtml(localPath, options) {
|
|
|
7382
7704
|
const parentPath = dirname2(localPath);
|
|
7383
7705
|
const rows = items.map((item) => {
|
|
7384
7706
|
const suffix = item.isDirectory ? "/" : "";
|
|
7385
|
-
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>` : "";
|
|
7386
|
-
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>`;
|
|
7387
7709
|
}).join("\n");
|
|
7388
|
-
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>` : "";
|
|
7389
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>` : "";
|
|
7390
7712
|
const actionButtons = actionButtonsHtml(localPath, newProjectName);
|
|
7391
7713
|
return `<!doctype html>
|
|
@@ -7394,35 +7716,37 @@ async function createDirectoryListingHtml(localPath, options) {
|
|
|
7394
7716
|
<meta charset="utf-8" />
|
|
7395
7717
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7396
7718
|
<title>Index of ${escapeHtml(localPath)}</title>
|
|
7719
|
+
<script>${renderStandaloneThemeBootstrapScript()}</script>
|
|
7397
7720
|
<style>
|
|
7398
|
-
|
|
7399
|
-
|
|
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; }
|
|
7400
7724
|
a:hover { text-decoration: underline; }
|
|
7401
7725
|
ul { list-style: none; padding: 0; margin: 12px 0 0; display: flex; flex-direction: column; gap: 8px; }
|
|
7402
7726
|
.file-row { display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: center; gap: 10px; }
|
|
7403
|
-
.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); }
|
|
7404
7728
|
.header-actions { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
|
|
7405
|
-
.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); }
|
|
7406
7730
|
.header-parent-link:hover { text-decoration: none; filter: brightness(1.08); }
|
|
7407
7731
|
.header-open-btn {
|
|
7408
7732
|
height: 42px;
|
|
7409
7733
|
padding: 0 14px;
|
|
7410
|
-
border: 1px solid
|
|
7734
|
+
border: 1px solid var(--lb-primary-border);
|
|
7411
7735
|
border-radius: 10px;
|
|
7412
|
-
background:
|
|
7413
|
-
color:
|
|
7736
|
+
background: var(--lb-primary-bg);
|
|
7737
|
+
color: var(--lb-primary-text);
|
|
7414
7738
|
font-weight: 700;
|
|
7415
7739
|
letter-spacing: 0.01em;
|
|
7416
7740
|
cursor: pointer;
|
|
7417
|
-
box-shadow:
|
|
7741
|
+
box-shadow: var(--lb-shadow);
|
|
7418
7742
|
}
|
|
7419
7743
|
.header-open-btn:hover { filter: brightness(1.08); }
|
|
7420
7744
|
.header-open-btn:disabled { opacity: 0.6; cursor: default; }
|
|
7421
|
-
.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; }
|
|
7422
7746
|
.row-actions { display: inline-flex; align-items: center; gap: 8px; min-width: 42px; justify-content: flex-end; }
|
|
7423
|
-
.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; }
|
|
7424
7748
|
.icon-btn:hover { filter: brightness(1.08); text-decoration: none; }
|
|
7425
|
-
.status { margin: 10px 0 0; color:
|
|
7749
|
+
.status { margin: 10px 0 0; color: var(--lb-text-muted); min-height: 1.25em; }
|
|
7426
7750
|
h1 { font-size: 18px; margin: 0; word-break: break-all; }
|
|
7427
7751
|
@media (max-width: 640px) {
|
|
7428
7752
|
body { margin: 12px; }
|
|
@@ -7483,10 +7807,144 @@ async function createDirectoryListingHtml(localPath, options) {
|
|
|
7483
7807
|
</body>
|
|
7484
7808
|
</html>`;
|
|
7485
7809
|
}
|
|
7486
|
-
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
|
+
}
|
|
7487
7946
|
const content = await readFile4(localPath, "utf8");
|
|
7488
7947
|
const parentPath = dirname2(localPath);
|
|
7489
|
-
const language = languageForPath(localPath);
|
|
7490
7948
|
const safeContentLiteral = escapeForInlineScriptString(content);
|
|
7491
7949
|
return `<!doctype html>
|
|
7492
7950
|
<html lang="en">
|
|
@@ -7494,49 +7952,64 @@ async function createTextEditorHtml(localPath) {
|
|
|
7494
7952
|
<meta charset="utf-8" />
|
|
7495
7953
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7496
7954
|
<title>Edit ${escapeHtml(localPath)}</title>
|
|
7955
|
+
<script>${renderStandaloneThemeBootstrapScript()}</script>
|
|
7497
7956
|
<style>
|
|
7957
|
+
${renderStandaloneThemeCss()}
|
|
7498
7958
|
html, body { width: 100%; height: 100%; margin: 0; }
|
|
7499
|
-
body { font-family: ui-monospace, Menlo, Monaco, monospace; background:
|
|
7500
|
-
.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); }
|
|
7501
7961
|
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
|
7502
|
-
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; }
|
|
7503
7963
|
button:hover, a:hover { filter: brightness(1.08); }
|
|
7504
7964
|
#editor { flex: 1 1 auto; min-height: 0; width: 100%; border: none; overflow: hidden; }
|
|
7505
|
-
#status { margin-left: 8px; color:
|
|
7506
|
-
.ace_editor {
|
|
7507
|
-
.
|
|
7508
|
-
.ace_marker-layer .
|
|
7509
|
-
.
|
|
7510
|
-
.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); }
|
|
7511
7970
|
</style>
|
|
7512
7971
|
</head>
|
|
7513
7972
|
<body>
|
|
7514
7973
|
<div class="toolbar">
|
|
7515
7974
|
<div class="row">
|
|
7516
|
-
<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>
|
|
7517
7979
|
<button id="saveBtn" type="button">Save</button>
|
|
7518
7980
|
<span id="status"></span>
|
|
7519
7981
|
</div>
|
|
7520
|
-
<div class="meta">${escapeHtml(localPath)
|
|
7982
|
+
<div class="meta">${escapeHtml([localPath, metadata.language.label, formatLineTargetLabel(line, column)].filter(Boolean).join(" \xB7 "))}</div>
|
|
7521
7983
|
</div>
|
|
7522
7984
|
<div id="editor"></div>
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
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);
|
|
7531
7998
|
editor.setOptions({
|
|
7532
7999
|
fontSize: '13px',
|
|
7533
8000
|
wrap: true,
|
|
7534
8001
|
showPrintMargin: false,
|
|
7535
8002
|
useSoftTabs: true,
|
|
7536
8003
|
tabSize: 2,
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
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();
|
|
7540
8013
|
|
|
7541
8014
|
saveBtn.addEventListener('click', async () => {
|
|
7542
8015
|
status.textContent = 'Saving...';
|
|
@@ -7602,6 +8075,34 @@ function readWildcardPathParam(value) {
|
|
|
7602
8075
|
if (Array.isArray(value)) return value.join("/");
|
|
7603
8076
|
return "";
|
|
7604
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
|
+
}
|
|
7605
8106
|
function createServer(options = {}) {
|
|
7606
8107
|
const app = express();
|
|
7607
8108
|
const bridge = createCodexBridgeMiddleware();
|
|
@@ -7629,19 +8130,87 @@ function createServer(options = {}) {
|
|
|
7629
8130
|
if (!res.headersSent) res.status(404).json({ error: "Image file not found." });
|
|
7630
8131
|
});
|
|
7631
8132
|
});
|
|
7632
|
-
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) => {
|
|
7633
8171
|
const rawPath = typeof req.query.path === "string" ? req.query.path : "";
|
|
7634
8172
|
const localPath = normalizeLocalPath(rawPath);
|
|
8173
|
+
const wantsDownload = readBooleanQueryFlag(req.query.download);
|
|
7635
8174
|
if (!localPath || !isAbsolute3(localPath)) {
|
|
7636
8175
|
res.status(400).json({ error: "Expected absolute local file path." });
|
|
7637
8176
|
return;
|
|
7638
8177
|
}
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
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
|
+
}
|
|
7645
8214
|
});
|
|
7646
8215
|
app.get("/codex-local-directories", async (req, res) => {
|
|
7647
8216
|
const rawPath = typeof req.query.path === "string" ? req.query.path : "";
|
|
@@ -7667,6 +8236,8 @@ function createServer(options = {}) {
|
|
|
7667
8236
|
const rawPath = readWildcardPathParam(req.params.path);
|
|
7668
8237
|
const localPath = decodeBrowsePath(`/${rawPath}`);
|
|
7669
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;
|
|
7670
8241
|
if (!localPath || !isAbsolute3(localPath)) {
|
|
7671
8242
|
res.status(400).json({ error: "Expected absolute local file path." });
|
|
7672
8243
|
return;
|
|
@@ -7679,6 +8250,13 @@ function createServer(options = {}) {
|
|
|
7679
8250
|
res.status(200).type("text/html; charset=utf-8").send(html);
|
|
7680
8251
|
return;
|
|
7681
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");
|
|
7682
8260
|
res.sendFile(localPath, { dotfiles: "allow" }, (error) => {
|
|
7683
8261
|
if (!error) return;
|
|
7684
8262
|
if (!res.headersSent) res.status(404).json({ error: "File not found." });
|
|
@@ -7690,6 +8268,9 @@ function createServer(options = {}) {
|
|
|
7690
8268
|
app.get("/codex-local-edit/*path", async (req, res) => {
|
|
7691
8269
|
const rawPath = readWildcardPathParam(req.params.path);
|
|
7692
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;
|
|
7693
8274
|
if (!localPath || !isAbsolute3(localPath)) {
|
|
7694
8275
|
res.status(400).json({ error: "Expected absolute local file path." });
|
|
7695
8276
|
return;
|
|
@@ -7700,7 +8281,11 @@ function createServer(options = {}) {
|
|
|
7700
8281
|
res.status(400).json({ error: "Expected file path." });
|
|
7701
8282
|
return;
|
|
7702
8283
|
}
|
|
7703
|
-
|
|
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 });
|
|
7704
8289
|
res.status(200).type("text/html; charset=utf-8").send(html);
|
|
7705
8290
|
} catch {
|
|
7706
8291
|
res.status(404).json({ error: "File not found." });
|