docdex 0.2.60 → 0.2.62
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/CHANGELOG.md +9 -0
- package/assets/agents.md +1 -1
- package/lib/install.js +146 -8
- package/lib/postinstall_setup.js +98 -32
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.62
|
|
4
|
+
- Expand the packaged personal-preferences surface with claim, snapshot, feedback, and mind-clone flows across HTTP, CLI, and MCP, including review/override/forget controls and clone regression coverage.
|
|
5
|
+
- Harden the packaged installer's local-binary path selection by validating `docdexd --version` output before using explicit or fallback local binaries and by avoiding stale repo-local binaries during registry installs.
|
|
6
|
+
|
|
7
|
+
## 0.2.61
|
|
8
|
+
- Add the optional personal-preferences memory subsystem with local capture, background digestion, and packaged HTTP/CLI/MCP controls for status, search, processing, export, redaction, deletion, and purge flows.
|
|
9
|
+
- Align the packaged personal-preferences surface with the top-level `[personal_preferences]` config, richer lineage/materialization, review and retention controls, bounded chat-context injection, and supported-client transcript scanning.
|
|
10
|
+
- Harden installer-managed client config rewrites so whitespace-padded Docdex keys and section names are normalized instead of duplicated on reinstall.
|
|
11
|
+
|
|
3
12
|
## 0.2.60
|
|
4
13
|
- Deduplicate installer-managed Docdex client config on reinstall: JSON client configs now collapse stale `docdex` entries, Codex TOML converges to one canonical Docdex entry, and packaged Docdex instruction blocks replace older Codex/Gemini/Claude prompt blocks instead of duplicating them.
|
|
5
14
|
- Update the packaged daemon dependency set to remove the vulnerable `rustls-webpki` chain that caused the nightly security audit failure.
|
package/assets/agents.md
CHANGED
package/lib/install.js
CHANGED
|
@@ -219,6 +219,12 @@ function getVersion() {
|
|
|
219
219
|
return version;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
function normalizeVersion(value) {
|
|
223
|
+
return String(value || "")
|
|
224
|
+
.trim()
|
|
225
|
+
.replace(/^v/i, "");
|
|
226
|
+
}
|
|
227
|
+
|
|
222
228
|
function requestOptions() {
|
|
223
229
|
const headers = { "User-Agent": USER_AGENT };
|
|
224
230
|
const token = process.env.DOCDEX_GITHUB_TOKEN || process.env.GITHUB_TOKEN;
|
|
@@ -665,11 +671,7 @@ function shouldPreferLocalInstall({ env, localBinaryPath, pathModule, localRepoR
|
|
|
665
671
|
if (parseEnvBool(env?.[LOCAL_FALLBACK_ENV]) === false) return false;
|
|
666
672
|
if (env?.[LOCAL_BINARY_ENV]) return true;
|
|
667
673
|
if (env?.npm_lifecycle_event !== "postinstall") return false;
|
|
668
|
-
|
|
669
|
-
if (!env?.INIT_CWD || !localRepoRoot) return false;
|
|
670
|
-
const initCwd = pathModule.resolve(env.INIT_CWD);
|
|
671
|
-
const repoRoot = pathModule.resolve(localRepoRoot);
|
|
672
|
-
return initCwd === repoRoot || initCwd.startsWith(`${repoRoot}${pathModule.sep}`);
|
|
674
|
+
return isLocalInstallRequest({ env, pathModule });
|
|
673
675
|
}
|
|
674
676
|
|
|
675
677
|
function resolveLocalBinaryCandidate({
|
|
@@ -746,6 +748,100 @@ async function installFromLocalBinary({
|
|
|
746
748
|
return { binaryPath: destPath, outcome: "local", outcomeCode: "local" };
|
|
747
749
|
}
|
|
748
750
|
|
|
751
|
+
function parseBinaryVersionOutput(output) {
|
|
752
|
+
const text = String(output || "").trim();
|
|
753
|
+
if (!text) return null;
|
|
754
|
+
const taggedMatch = text.match(/\bdocdexd\s+v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z._-]+)?)\b/i);
|
|
755
|
+
if (taggedMatch?.[1]) return normalizeVersion(taggedMatch[1]);
|
|
756
|
+
const genericMatch = text.match(/\bv?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z._-]+)?)\b/);
|
|
757
|
+
if (genericMatch?.[1]) return normalizeVersion(genericMatch[1]);
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function probeBinaryVersion({
|
|
762
|
+
binaryPath,
|
|
763
|
+
spawnSyncFn = spawnSync
|
|
764
|
+
}) {
|
|
765
|
+
if (!binaryPath) {
|
|
766
|
+
return { version: null, raw: "", error: "missing_binary" };
|
|
767
|
+
}
|
|
768
|
+
const result = spawnSyncFn(binaryPath, ["--version"], {
|
|
769
|
+
encoding: "utf8"
|
|
770
|
+
});
|
|
771
|
+
const raw = [result?.stdout, result?.stderr].filter(Boolean).join("\n").trim();
|
|
772
|
+
if (result?.error) {
|
|
773
|
+
return {
|
|
774
|
+
version: null,
|
|
775
|
+
raw,
|
|
776
|
+
error: result.error?.message || String(result.error)
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
if (typeof result?.status === "number" && result.status !== 0) {
|
|
780
|
+
return {
|
|
781
|
+
version: null,
|
|
782
|
+
raw,
|
|
783
|
+
error: raw || `exit_${result.status}`
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
const version = parseBinaryVersionOutput(raw);
|
|
787
|
+
if (!version) {
|
|
788
|
+
return { version: null, raw, error: "version_unparseable" };
|
|
789
|
+
}
|
|
790
|
+
return { version, raw, error: null };
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function validateLocalBinaryVersion({
|
|
794
|
+
binaryPath,
|
|
795
|
+
expectedVersion,
|
|
796
|
+
spawnSyncFn = spawnSync
|
|
797
|
+
}) {
|
|
798
|
+
const probed = probeBinaryVersion({ binaryPath, spawnSyncFn });
|
|
799
|
+
if (!probed.version) {
|
|
800
|
+
return {
|
|
801
|
+
ok: false,
|
|
802
|
+
reason: probed.error || "version_probe_failed",
|
|
803
|
+
expectedVersion,
|
|
804
|
+
detectedVersion: null,
|
|
805
|
+
raw: probed.raw || ""
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
if (normalizeVersion(probed.version) !== normalizeVersion(expectedVersion)) {
|
|
809
|
+
return {
|
|
810
|
+
ok: false,
|
|
811
|
+
reason: "version_mismatch",
|
|
812
|
+
expectedVersion,
|
|
813
|
+
detectedVersion: probed.version,
|
|
814
|
+
raw: probed.raw || ""
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
return {
|
|
818
|
+
ok: true,
|
|
819
|
+
reason: "matched",
|
|
820
|
+
expectedVersion,
|
|
821
|
+
detectedVersion: probed.version,
|
|
822
|
+
raw: probed.raw || ""
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function buildLocalBinaryVersionError({ binaryPath, validation, explicitEnvOverride = false } = {}) {
|
|
827
|
+
const expected = validation?.expectedVersion || "unknown";
|
|
828
|
+
const detected = validation?.detectedVersion || "unknown";
|
|
829
|
+
const sourceName = explicitEnvOverride ? "DOCDEX_LOCAL_BINARY" : "local Docdex binary";
|
|
830
|
+
const probeHint = validation?.reason === "version_mismatch"
|
|
831
|
+
? `expected ${expected} but found ${detected}`
|
|
832
|
+
: `version probe failed (${validation?.reason || "unknown"})`;
|
|
833
|
+
return new InstallerConfigError(
|
|
834
|
+
`${sourceName} is stale or invalid: ${probeHint}`,
|
|
835
|
+
{
|
|
836
|
+
expectedVersion: expected,
|
|
837
|
+
detectedVersion: validation?.detectedVersion || null,
|
|
838
|
+
binaryPath: binaryPath || null,
|
|
839
|
+
explicitEnvOverride,
|
|
840
|
+
probeOutput: validation?.raw || null
|
|
841
|
+
}
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
|
|
749
845
|
async function maybeInstallLocalFallback({
|
|
750
846
|
err,
|
|
751
847
|
env,
|
|
@@ -761,7 +857,8 @@ async function maybeInstallLocalFallback({
|
|
|
761
857
|
writeJsonFileAtomicFn,
|
|
762
858
|
logger,
|
|
763
859
|
localRepoRoot,
|
|
764
|
-
localBinaryPath
|
|
860
|
+
localBinaryPath,
|
|
861
|
+
spawnSyncFn = spawnSync
|
|
765
862
|
}) {
|
|
766
863
|
if (!err || err.code !== "DOCDEX_CHECKSUM_UNUSABLE") return null;
|
|
767
864
|
const allowFallback = parseEnvBool(env[LOCAL_FALLBACK_ENV]);
|
|
@@ -778,6 +875,18 @@ async function maybeInstallLocalFallback({
|
|
|
778
875
|
});
|
|
779
876
|
if (!candidate) return null;
|
|
780
877
|
|
|
878
|
+
const validation = validateLocalBinaryVersion({
|
|
879
|
+
binaryPath: candidate,
|
|
880
|
+
expectedVersion: version,
|
|
881
|
+
spawnSyncFn
|
|
882
|
+
});
|
|
883
|
+
if (!validation.ok) {
|
|
884
|
+
logger?.warn?.(
|
|
885
|
+
`[docdex] local fallback skipped for ${candidate}: expected ${version}, detected ${validation.detectedVersion || "unknown"} (${validation.reason}).`
|
|
886
|
+
);
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
|
|
781
890
|
return installFromLocalBinary({
|
|
782
891
|
fsModule,
|
|
783
892
|
pathModule,
|
|
@@ -1780,6 +1889,7 @@ async function runInstaller(options) {
|
|
|
1780
1889
|
const artifactNameFn = opts.artifactNameFn || artifactName;
|
|
1781
1890
|
const assetPatternForPlatformKeyFn = opts.assetPatternForPlatformKeyFn || assetPatternForPlatformKey;
|
|
1782
1891
|
const sha256FileFn = opts.sha256FileFn || sha256File;
|
|
1892
|
+
const spawnSyncFn = opts.spawnSyncFn || spawnSync;
|
|
1783
1893
|
const writeJsonFileAtomicFn = opts.writeJsonFileAtomicFn || writeJsonFileAtomic;
|
|
1784
1894
|
const restartFn = opts.restartFn;
|
|
1785
1895
|
const localRepoRoot =
|
|
@@ -1853,6 +1963,18 @@ async function runInstaller(options) {
|
|
|
1853
1963
|
|
|
1854
1964
|
const forceLocalBinary = Boolean(env?.[LOCAL_BINARY_ENV]);
|
|
1855
1965
|
if (forceLocalBinary && localBinaryPath) {
|
|
1966
|
+
const validation = validateLocalBinaryVersion({
|
|
1967
|
+
binaryPath: localBinaryPath,
|
|
1968
|
+
expectedVersion: version,
|
|
1969
|
+
spawnSyncFn
|
|
1970
|
+
});
|
|
1971
|
+
if (!validation.ok) {
|
|
1972
|
+
throw buildLocalBinaryVersionError({
|
|
1973
|
+
binaryPath: localBinaryPath,
|
|
1974
|
+
validation,
|
|
1975
|
+
explicitEnvOverride: true
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1856
1978
|
const localInstall = await installFromLocalBinary({
|
|
1857
1979
|
fsModule,
|
|
1858
1980
|
pathModule,
|
|
@@ -1871,6 +1993,18 @@ async function runInstaller(options) {
|
|
|
1871
1993
|
}
|
|
1872
1994
|
|
|
1873
1995
|
if (preferLocal) {
|
|
1996
|
+
const validation = validateLocalBinaryVersion({
|
|
1997
|
+
binaryPath: localBinaryPath,
|
|
1998
|
+
expectedVersion: version,
|
|
1999
|
+
spawnSyncFn
|
|
2000
|
+
});
|
|
2001
|
+
if (!validation.ok) {
|
|
2002
|
+
throw buildLocalBinaryVersionError({
|
|
2003
|
+
binaryPath: localBinaryPath,
|
|
2004
|
+
validation,
|
|
2005
|
+
explicitEnvOverride: false
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
1874
2008
|
const localInstall = await installFromLocalBinary({
|
|
1875
2009
|
fsModule,
|
|
1876
2010
|
pathModule,
|
|
@@ -1951,7 +2085,8 @@ async function runInstaller(options) {
|
|
|
1951
2085
|
writeJsonFileAtomicFn,
|
|
1952
2086
|
logger,
|
|
1953
2087
|
localRepoRoot,
|
|
1954
|
-
localBinaryPath
|
|
2088
|
+
localBinaryPath,
|
|
2089
|
+
spawnSyncFn
|
|
1955
2090
|
});
|
|
1956
2091
|
if (fallback) {
|
|
1957
2092
|
return fallback;
|
|
@@ -2666,5 +2801,8 @@ module.exports = {
|
|
|
2666
2801
|
ChecksumResolutionError,
|
|
2667
2802
|
runInstaller,
|
|
2668
2803
|
describeFatalError,
|
|
2669
|
-
handleFatal
|
|
2804
|
+
handleFatal,
|
|
2805
|
+
parseBinaryVersionOutput,
|
|
2806
|
+
probeBinaryVersion,
|
|
2807
|
+
validateLocalBinaryVersion
|
|
2670
2808
|
};
|
package/lib/postinstall_setup.js
CHANGED
|
@@ -1102,20 +1102,39 @@ function upsertMcpServerJson(pathname, url, options = {}) {
|
|
|
1102
1102
|
: {};
|
|
1103
1103
|
const isPlainObject = (entry) =>
|
|
1104
1104
|
typeof entry === "object" && entry != null && !Array.isArray(entry);
|
|
1105
|
+
const normalizeManagedServerName = (value) =>
|
|
1106
|
+
typeof value === "string" ? value.trim().toLowerCase() : null;
|
|
1107
|
+
const collectDocdexSectionEntry = (section) => {
|
|
1108
|
+
if (!isPlainObject(section)) return {};
|
|
1109
|
+
for (const [key, value] of Object.entries(section)) {
|
|
1110
|
+
if (normalizeManagedServerName(key) === "docdex" && isPlainObject(value)) {
|
|
1111
|
+
return { ...value };
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
return {};
|
|
1115
|
+
};
|
|
1105
1116
|
const removeDocdexFromSection = (key) => {
|
|
1106
1117
|
const section = root[key];
|
|
1107
1118
|
if (Array.isArray(section)) {
|
|
1108
|
-
const filtered = section.filter(
|
|
1119
|
+
const filtered = section.filter(
|
|
1120
|
+
(entry) => normalizeManagedServerName(entry?.name) !== "docdex"
|
|
1121
|
+
);
|
|
1109
1122
|
if (filtered.length !== section.length) {
|
|
1110
1123
|
root[key] = filtered;
|
|
1111
1124
|
}
|
|
1112
1125
|
return;
|
|
1113
1126
|
}
|
|
1114
|
-
if (!isPlainObject(section)
|
|
1127
|
+
if (!isPlainObject(section)) {
|
|
1115
1128
|
return;
|
|
1116
1129
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1130
|
+
let removed = false;
|
|
1131
|
+
for (const configKey of Object.keys(section)) {
|
|
1132
|
+
if (normalizeManagedServerName(configKey) === "docdex") {
|
|
1133
|
+
delete section[configKey];
|
|
1134
|
+
removed = true;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
if (removed && Object.keys(section).length === 0) {
|
|
1119
1138
|
delete root[key];
|
|
1120
1139
|
}
|
|
1121
1140
|
};
|
|
@@ -1133,7 +1152,7 @@ function upsertMcpServerJson(pathname, url, options = {}) {
|
|
|
1133
1152
|
let insertIndex = -1;
|
|
1134
1153
|
let current = {};
|
|
1135
1154
|
for (const entry of root.mcpServers) {
|
|
1136
|
-
if (entry
|
|
1155
|
+
if (normalizeManagedServerName(entry?.name) === "docdex") {
|
|
1137
1156
|
if (insertIndex === -1) {
|
|
1138
1157
|
insertIndex = nextEntries.length;
|
|
1139
1158
|
current = isPlainObject(entry) ? { ...entry } : {};
|
|
@@ -1156,9 +1175,12 @@ function upsertMcpServerJson(pathname, url, options = {}) {
|
|
|
1156
1175
|
if (!picked) {
|
|
1157
1176
|
root[sectionKey] = {};
|
|
1158
1177
|
}
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1178
|
+
const current = collectDocdexSectionEntry(root[sectionKey]);
|
|
1179
|
+
removeDocdexFromSection(sectionKey);
|
|
1180
|
+
if (!root[sectionKey] || !isPlainObject(root[sectionKey])) {
|
|
1181
|
+
root[sectionKey] = {};
|
|
1182
|
+
}
|
|
1183
|
+
root[sectionKey].docdex = { ...current, ...extra, url };
|
|
1162
1184
|
removeDocdexFromSection(sectionKey === "mcpServers" ? "mcp_servers" : "mcpServers");
|
|
1163
1185
|
}
|
|
1164
1186
|
if (JSON.stringify(root) === before) return false;
|
|
@@ -1173,7 +1195,15 @@ function upsertZedConfig(pathname, url) {
|
|
|
1173
1195
|
if (!root.experimental_mcp_servers || typeof root.experimental_mcp_servers !== "object" || Array.isArray(root.experimental_mcp_servers)) {
|
|
1174
1196
|
root.experimental_mcp_servers = {};
|
|
1175
1197
|
}
|
|
1176
|
-
const
|
|
1198
|
+
const normalizeManagedServerName = (value) =>
|
|
1199
|
+
typeof value === "string" ? value.trim().toLowerCase() : null;
|
|
1200
|
+
let current = root.experimental_mcp_servers.docdex;
|
|
1201
|
+
for (const key of Object.keys(root.experimental_mcp_servers)) {
|
|
1202
|
+
if (normalizeManagedServerName(key) === "docdex" && key !== "docdex") {
|
|
1203
|
+
if (current == null) current = root.experimental_mcp_servers[key];
|
|
1204
|
+
delete root.experimental_mcp_servers[key];
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1177
1207
|
if (current && current.url === url) return false;
|
|
1178
1208
|
root.experimental_mcp_servers.docdex = { url };
|
|
1179
1209
|
writeJson(pathname, root);
|
|
@@ -1182,16 +1212,52 @@ function upsertZedConfig(pathname, url) {
|
|
|
1182
1212
|
|
|
1183
1213
|
function upsertCodexConfig(pathname, url) {
|
|
1184
1214
|
const codexTimeoutSec = 300;
|
|
1185
|
-
const hasSection = (contents, section) =>
|
|
1186
|
-
new RegExp(`^\\s*\\[${section}\\]\\s*$`, "m").test(contents);
|
|
1187
|
-
const hasNestedMcpServers = (contents) =>
|
|
1188
|
-
/^\s*\[mcp_servers\.[^\]]+\]\s*$/m.test(contents);
|
|
1189
1215
|
const legacyInstructionPath = "~/.docdex/agents.md";
|
|
1190
1216
|
const parseTomlString = (value) => {
|
|
1191
1217
|
const trimmed = value.trim();
|
|
1192
1218
|
const quoted = trimmed.match(/^"(.*)"$/) || trimmed.match(/^'(.*)'$/);
|
|
1193
1219
|
return quoted ? quoted[1] : trimmed;
|
|
1194
1220
|
};
|
|
1221
|
+
const normalizeTomlKeyPart = (value) => parseTomlString(value).trim();
|
|
1222
|
+
const parseTomlHeaderParts = (line) => {
|
|
1223
|
+
const match = line.match(/^\s*\[([^\]]+)\]\s*$/);
|
|
1224
|
+
if (!match) return null;
|
|
1225
|
+
return match[1].split(".").map(normalizeTomlKeyPart).filter((part) => part.length > 0);
|
|
1226
|
+
};
|
|
1227
|
+
const hasSection = (contents, section) => {
|
|
1228
|
+
const expectedParts = section.split(".").map(normalizeTomlKeyPart);
|
|
1229
|
+
return contents
|
|
1230
|
+
.split(/\r?\n/)
|
|
1231
|
+
.some((line) => {
|
|
1232
|
+
const parts = parseTomlHeaderParts(line);
|
|
1233
|
+
return (
|
|
1234
|
+
parts &&
|
|
1235
|
+
parts.length === expectedParts.length &&
|
|
1236
|
+
parts.every((part, index) => part === expectedParts[index])
|
|
1237
|
+
);
|
|
1238
|
+
});
|
|
1239
|
+
};
|
|
1240
|
+
const isTomlSectionLine = (line) => parseTomlHeaderParts(line) != null;
|
|
1241
|
+
const isMcpServersSectionLine = (line) => {
|
|
1242
|
+
const parts = parseTomlHeaderParts(line);
|
|
1243
|
+
return !!parts && parts.length === 1 && parts[0] === "mcp_servers";
|
|
1244
|
+
};
|
|
1245
|
+
const isDocdexNestedMcpServerSectionLine = (line) => {
|
|
1246
|
+
const parts = parseTomlHeaderParts(line);
|
|
1247
|
+
return !!parts && parts.length === 2 && parts[0] === "mcp_servers" && parts[1] === "docdex";
|
|
1248
|
+
};
|
|
1249
|
+
const hasNestedMcpServers = (contents) =>
|
|
1250
|
+
contents
|
|
1251
|
+
.split(/\r?\n/)
|
|
1252
|
+
.some((line) => {
|
|
1253
|
+
const parts = parseTomlHeaderParts(line);
|
|
1254
|
+
return !!parts && parts.length > 1 && parts[0] === "mcp_servers";
|
|
1255
|
+
});
|
|
1256
|
+
const parseTomlAssignmentKey = (line) => {
|
|
1257
|
+
const match = line.match(/^\s*([^=]+?)\s*=/);
|
|
1258
|
+
return match ? normalizeTomlKeyPart(match[1]) : null;
|
|
1259
|
+
};
|
|
1260
|
+
const isDocdexAssignmentLine = (line) => parseTomlAssignmentKey(line) === "docdex";
|
|
1195
1261
|
const migrateLegacyMcpServers = (contents) => {
|
|
1196
1262
|
if (!/\[\[mcp_servers\]\]/m.test(contents)) {
|
|
1197
1263
|
return { contents, migrated: false };
|
|
@@ -1256,8 +1322,7 @@ function upsertCodexConfig(pathname, url) {
|
|
|
1256
1322
|
["startup_timeout_sec", `${codexTimeoutSec}`]
|
|
1257
1323
|
];
|
|
1258
1324
|
const lines = contents.split(/\r?\n/);
|
|
1259
|
-
|
|
1260
|
-
let start = lines.findIndex((line) => headerRe.test(line));
|
|
1325
|
+
let start = lines.findIndex((line) => isDocdexNestedMcpServerSectionLine(line));
|
|
1261
1326
|
if (start === -1) {
|
|
1262
1327
|
if (lines.length && lines[lines.length - 1].trim()) lines.push("");
|
|
1263
1328
|
lines.push("[mcp_servers.docdex]");
|
|
@@ -1267,7 +1332,7 @@ function upsertCodexConfig(pathname, url) {
|
|
|
1267
1332
|
return { contents: lines.join("\n"), updated: true };
|
|
1268
1333
|
}
|
|
1269
1334
|
let end = start + 1;
|
|
1270
|
-
while (end < lines.length &&
|
|
1335
|
+
while (end < lines.length && !isTomlSectionLine(lines[end])) {
|
|
1271
1336
|
end += 1;
|
|
1272
1337
|
}
|
|
1273
1338
|
let updated = false;
|
|
@@ -1275,7 +1340,7 @@ function upsertCodexConfig(pathname, url) {
|
|
|
1275
1340
|
const lineValue = `${key} = ${value}`;
|
|
1276
1341
|
let keyIndex = -1;
|
|
1277
1342
|
for (let i = start + 1; i < end; i += 1) {
|
|
1278
|
-
if (
|
|
1343
|
+
if (parseTomlAssignmentKey(lines[i]) === key) {
|
|
1279
1344
|
keyIndex = i;
|
|
1280
1345
|
break;
|
|
1281
1346
|
}
|
|
@@ -1296,8 +1361,7 @@ function upsertCodexConfig(pathname, url) {
|
|
|
1296
1361
|
const entryLine =
|
|
1297
1362
|
`docdex = { url = "${urlValue}", tool_timeout_sec = ${codexTimeoutSec}, startup_timeout_sec = ${codexTimeoutSec} }`;
|
|
1298
1363
|
const lines = contents.split(/\r?\n/);
|
|
1299
|
-
const
|
|
1300
|
-
const start = lines.findIndex((line) => headerRe.test(line));
|
|
1364
|
+
const start = lines.findIndex((line) => isMcpServersSectionLine(line));
|
|
1301
1365
|
if (start === -1) {
|
|
1302
1366
|
if (lines.length && lines[lines.length - 1].trim()) lines.push("");
|
|
1303
1367
|
lines.push("[mcp_servers]");
|
|
@@ -1305,13 +1369,13 @@ function upsertCodexConfig(pathname, url) {
|
|
|
1305
1369
|
return { contents: lines.join("\n"), updated: true };
|
|
1306
1370
|
}
|
|
1307
1371
|
let end = start + 1;
|
|
1308
|
-
while (end < lines.length &&
|
|
1372
|
+
while (end < lines.length && !isTomlSectionLine(lines[end])) {
|
|
1309
1373
|
end += 1;
|
|
1310
1374
|
}
|
|
1311
1375
|
let updated = false;
|
|
1312
1376
|
const docdexLines = [];
|
|
1313
1377
|
for (let i = start + 1; i < end; i += 1) {
|
|
1314
|
-
if (
|
|
1378
|
+
if (isDocdexAssignmentLine(lines[i])) {
|
|
1315
1379
|
docdexLines.push(i);
|
|
1316
1380
|
}
|
|
1317
1381
|
}
|
|
@@ -1349,10 +1413,10 @@ function upsertCodexConfig(pathname, url) {
|
|
|
1349
1413
|
};
|
|
1350
1414
|
|
|
1351
1415
|
for (const line of lines) {
|
|
1352
|
-
const section = line
|
|
1416
|
+
const section = parseTomlHeaderParts(line);
|
|
1353
1417
|
if (section) {
|
|
1354
1418
|
flushFeatures();
|
|
1355
|
-
if (section[
|
|
1419
|
+
if (section.length === 1 && section[0] === "features") {
|
|
1356
1420
|
inFeatures = true;
|
|
1357
1421
|
buffer = [line];
|
|
1358
1422
|
continue;
|
|
@@ -1384,13 +1448,13 @@ function upsertCodexConfig(pathname, url) {
|
|
|
1384
1448
|
let inRootSection = false;
|
|
1385
1449
|
let updated = false;
|
|
1386
1450
|
for (const line of lines) {
|
|
1387
|
-
const section = line
|
|
1451
|
+
const section = parseTomlHeaderParts(line);
|
|
1388
1452
|
if (section) {
|
|
1389
|
-
inRootSection = section[
|
|
1453
|
+
inRootSection = section.length === 1 && section[0] === "mcp_servers";
|
|
1390
1454
|
output.push(line);
|
|
1391
1455
|
continue;
|
|
1392
1456
|
}
|
|
1393
|
-
if (inRootSection &&
|
|
1457
|
+
if (inRootSection && isDocdexAssignmentLine(line)) {
|
|
1394
1458
|
updated = true;
|
|
1395
1459
|
continue;
|
|
1396
1460
|
}
|
|
@@ -1404,12 +1468,12 @@ function upsertCodexConfig(pathname, url) {
|
|
|
1404
1468
|
let inRootSection = false;
|
|
1405
1469
|
let count = 0;
|
|
1406
1470
|
for (const line of lines) {
|
|
1407
|
-
const section = line
|
|
1471
|
+
const section = parseTomlHeaderParts(line);
|
|
1408
1472
|
if (section) {
|
|
1409
|
-
inRootSection = section[
|
|
1473
|
+
inRootSection = section.length === 1 && section[0] === "mcp_servers";
|
|
1410
1474
|
continue;
|
|
1411
1475
|
}
|
|
1412
|
-
if (inRootSection &&
|
|
1476
|
+
if (inRootSection && isDocdexAssignmentLine(line)) {
|
|
1413
1477
|
count += 1;
|
|
1414
1478
|
}
|
|
1415
1479
|
}
|
|
@@ -1422,7 +1486,7 @@ function upsertCodexConfig(pathname, url) {
|
|
|
1422
1486
|
let skipping = false;
|
|
1423
1487
|
let updated = false;
|
|
1424
1488
|
for (const line of lines) {
|
|
1425
|
-
const isSection =
|
|
1489
|
+
const isSection = isTomlSectionLine(line);
|
|
1426
1490
|
if (skipping) {
|
|
1427
1491
|
if (isSection) {
|
|
1428
1492
|
skipping = false;
|
|
@@ -1430,7 +1494,7 @@ function upsertCodexConfig(pathname, url) {
|
|
|
1430
1494
|
continue;
|
|
1431
1495
|
}
|
|
1432
1496
|
}
|
|
1433
|
-
if (
|
|
1497
|
+
if (isDocdexNestedMcpServerSectionLine(line)) {
|
|
1434
1498
|
skipping = true;
|
|
1435
1499
|
updated = true;
|
|
1436
1500
|
continue;
|
|
@@ -1441,7 +1505,9 @@ function upsertCodexConfig(pathname, url) {
|
|
|
1441
1505
|
};
|
|
1442
1506
|
|
|
1443
1507
|
const countNestedDocdexSections = (text) =>
|
|
1444
|
-
|
|
1508
|
+
text
|
|
1509
|
+
.split(/\r?\n/)
|
|
1510
|
+
.filter((line) => isDocdexNestedMcpServerSectionLine(line)).length;
|
|
1445
1511
|
|
|
1446
1512
|
let contents = "";
|
|
1447
1513
|
if (fs.existsSync(pathname)) {
|