crbro-memory 2.10.0 → 2.11.0

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.
Files changed (205) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +256 -465
  3. package/SECURITY.md +114 -12
  4. package/bin/crbro.mjs +539 -300
  5. package/dist/build-id.json +10 -0
  6. package/dist/cli/common.js +211 -0
  7. package/dist/cli/export.js +522 -0
  8. package/dist/cli/import.js +683 -0
  9. package/dist/cli/markdown.js +313 -0
  10. package/dist/cli/mcpclient.js +107 -0
  11. package/dist/cli/serve.js +453 -0
  12. package/dist/cli/view.js +358 -0
  13. package/dist/cli/viewer-page.js +378 -0
  14. package/dist/daemon/daemon.js +119 -11
  15. package/dist/daemon/endpoint.js +222 -21
  16. package/dist/daemon/lines.js +102 -13
  17. package/dist/daemon/proxy.js +480 -125
  18. package/dist/daemon/surface.js +116 -0
  19. package/dist/daemon/trace.js +73 -0
  20. package/dist/engine/atrest.js +132 -0
  21. package/dist/engine/backup.js +139 -19
  22. package/dist/engine/brain.js +22 -6
  23. package/dist/engine/codexconfig.js +476 -0
  24. package/dist/engine/cortex.js +667 -232
  25. package/dist/engine/doctor.js +312 -14
  26. package/dist/engine/heat.js +81 -37
  27. package/dist/engine/hippocampus.js +18 -7
  28. package/dist/engine/hookrefresh.js +296 -0
  29. package/dist/engine/hookverify.js +15 -3
  30. package/dist/engine/installer.js +1022 -0
  31. package/dist/engine/installio.js +279 -0
  32. package/dist/engine/keychain.js +721 -162
  33. package/dist/engine/maintenance.js +355 -87
  34. package/dist/engine/modinstall.js +98 -45
  35. package/dist/engine/prefrontal.js +29 -6
  36. package/dist/engine/quarantine.js +204 -0
  37. package/dist/engine/secrets.js +41 -5
  38. package/dist/engine/shelf.js +25 -3
  39. package/dist/engine/synapses.js +183 -80
  40. package/dist/engine/triggers.js +141 -11
  41. package/dist/index.js +37 -3
  42. package/dist/mcp-surface.json +1 -0
  43. package/dist/miner/extractor.js +64 -17
  44. package/dist/miner/scheduler.js +144 -68
  45. package/dist/search/index.js +797 -176
  46. package/dist/search/semantic.js +208 -18
  47. package/dist/search/tokenize.js +44 -0
  48. package/dist/server.js +583 -373
  49. package/dist/sync/git.js +203 -72
  50. package/dist/sync/space.js +100 -25
  51. package/dist/sync/validate.js +204 -0
  52. package/dist/utils/budget.js +15 -2
  53. package/dist/utils/fs.js +40 -6
  54. package/dist/utils/ids.js +128 -14
  55. package/dist/utils/lock.js +94 -7
  56. package/hooks/crbro-codex.mjs +284 -0
  57. package/hooks/crbro-guard.mjs +281 -173
  58. package/hooks/crbro-hooks.shipped.json +20 -0
  59. package/hooks/crbro-lifecycle.mjs +16 -4
  60. package/hooks/crbro-subagent.mjs +180 -180
  61. package/package.json +9 -5
  62. package/dist/daemon/daemon.d.ts +0 -25
  63. package/dist/daemon/daemon.d.ts.map +0 -1
  64. package/dist/daemon/daemon.js.map +0 -1
  65. package/dist/daemon/endpoint.d.ts +0 -66
  66. package/dist/daemon/endpoint.d.ts.map +0 -1
  67. package/dist/daemon/endpoint.js.map +0 -1
  68. package/dist/daemon/lines.d.ts +0 -55
  69. package/dist/daemon/lines.d.ts.map +0 -1
  70. package/dist/daemon/lines.js.map +0 -1
  71. package/dist/daemon/proxy.d.ts +0 -55
  72. package/dist/daemon/proxy.d.ts.map +0 -1
  73. package/dist/daemon/proxy.js.map +0 -1
  74. package/dist/engine/backup.d.ts +0 -68
  75. package/dist/engine/backup.d.ts.map +0 -1
  76. package/dist/engine/backup.js.map +0 -1
  77. package/dist/engine/brain.d.ts +0 -79
  78. package/dist/engine/brain.d.ts.map +0 -1
  79. package/dist/engine/brain.js.map +0 -1
  80. package/dist/engine/cortex.d.ts +0 -682
  81. package/dist/engine/cortex.d.ts.map +0 -1
  82. package/dist/engine/cortex.js.map +0 -1
  83. package/dist/engine/dates.d.ts +0 -28
  84. package/dist/engine/dates.d.ts.map +0 -1
  85. package/dist/engine/dates.js.map +0 -1
  86. package/dist/engine/doctor.d.ts +0 -93
  87. package/dist/engine/doctor.d.ts.map +0 -1
  88. package/dist/engine/doctor.js.map +0 -1
  89. package/dist/engine/evidence.d.ts +0 -26
  90. package/dist/engine/evidence.d.ts.map +0 -1
  91. package/dist/engine/evidence.js.map +0 -1
  92. package/dist/engine/heat.d.ts +0 -28
  93. package/dist/engine/heat.d.ts.map +0 -1
  94. package/dist/engine/heat.js.map +0 -1
  95. package/dist/engine/hippocampus.d.ts +0 -60
  96. package/dist/engine/hippocampus.d.ts.map +0 -1
  97. package/dist/engine/hippocampus.js.map +0 -1
  98. package/dist/engine/hookverify.d.ts +0 -42
  99. package/dist/engine/hookverify.d.ts.map +0 -1
  100. package/dist/engine/hookverify.js.map +0 -1
  101. package/dist/engine/keychain.d.ts +0 -32
  102. package/dist/engine/keychain.d.ts.map +0 -1
  103. package/dist/engine/keychain.js.map +0 -1
  104. package/dist/engine/maintenance.d.ts +0 -218
  105. package/dist/engine/maintenance.d.ts.map +0 -1
  106. package/dist/engine/maintenance.js.map +0 -1
  107. package/dist/engine/modinstall.d.ts +0 -215
  108. package/dist/engine/modinstall.d.ts.map +0 -1
  109. package/dist/engine/modinstall.js.map +0 -1
  110. package/dist/engine/postmortem.d.ts +0 -81
  111. package/dist/engine/postmortem.d.ts.map +0 -1
  112. package/dist/engine/postmortem.js.map +0 -1
  113. package/dist/engine/prefrontal.d.ts +0 -64
  114. package/dist/engine/prefrontal.d.ts.map +0 -1
  115. package/dist/engine/prefrontal.js.map +0 -1
  116. package/dist/engine/projects.d.ts +0 -20
  117. package/dist/engine/projects.d.ts.map +0 -1
  118. package/dist/engine/projects.js.map +0 -1
  119. package/dist/engine/secrets.d.ts +0 -30
  120. package/dist/engine/secrets.d.ts.map +0 -1
  121. package/dist/engine/secrets.js.map +0 -1
  122. package/dist/engine/shelf.d.ts +0 -155
  123. package/dist/engine/shelf.d.ts.map +0 -1
  124. package/dist/engine/shelf.js.map +0 -1
  125. package/dist/engine/source.d.ts +0 -6
  126. package/dist/engine/source.d.ts.map +0 -1
  127. package/dist/engine/source.js.map +0 -1
  128. package/dist/engine/synapses.d.ts +0 -82
  129. package/dist/engine/synapses.d.ts.map +0 -1
  130. package/dist/engine/synapses.js.map +0 -1
  131. package/dist/engine/triggers.d.ts +0 -53
  132. package/dist/engine/triggers.d.ts.map +0 -1
  133. package/dist/engine/triggers.js.map +0 -1
  134. package/dist/engine/usage.d.ts +0 -61
  135. package/dist/engine/usage.d.ts.map +0 -1
  136. package/dist/engine/usage.js.map +0 -1
  137. package/dist/index.d.ts +0 -2
  138. package/dist/index.d.ts.map +0 -1
  139. package/dist/index.js.map +0 -1
  140. package/dist/miner/extractor.d.ts +0 -12
  141. package/dist/miner/extractor.d.ts.map +0 -1
  142. package/dist/miner/extractor.js.map +0 -1
  143. package/dist/miner/index.d.ts +0 -61
  144. package/dist/miner/index.d.ts.map +0 -1
  145. package/dist/miner/index.js.map +0 -1
  146. package/dist/miner/scheduler.d.ts +0 -30
  147. package/dist/miner/scheduler.d.ts.map +0 -1
  148. package/dist/miner/scheduler.js.map +0 -1
  149. package/dist/miner/types.d.ts +0 -11
  150. package/dist/miner/types.d.ts.map +0 -1
  151. package/dist/miner/types.js.map +0 -1
  152. package/dist/search/index.d.ts +0 -328
  153. package/dist/search/index.d.ts.map +0 -1
  154. package/dist/search/index.js.map +0 -1
  155. package/dist/search/quality.d.ts +0 -18
  156. package/dist/search/quality.d.ts.map +0 -1
  157. package/dist/search/quality.js.map +0 -1
  158. package/dist/search/semantic.d.ts +0 -80
  159. package/dist/search/semantic.d.ts.map +0 -1
  160. package/dist/search/semantic.js.map +0 -1
  161. package/dist/search/synonyms.d.ts +0 -2
  162. package/dist/search/synonyms.d.ts.map +0 -1
  163. package/dist/search/synonyms.js.map +0 -1
  164. package/dist/search/tokenize.d.ts +0 -39
  165. package/dist/search/tokenize.d.ts.map +0 -1
  166. package/dist/search/tokenize.js.map +0 -1
  167. package/dist/server.d.ts +0 -41
  168. package/dist/server.d.ts.map +0 -1
  169. package/dist/server.js.map +0 -1
  170. package/dist/sync/git.d.ts +0 -36
  171. package/dist/sync/git.d.ts.map +0 -1
  172. package/dist/sync/git.js.map +0 -1
  173. package/dist/sync/materialize.d.ts +0 -36
  174. package/dist/sync/materialize.d.ts.map +0 -1
  175. package/dist/sync/materialize.js.map +0 -1
  176. package/dist/sync/ops.d.ts +0 -147
  177. package/dist/sync/ops.d.ts.map +0 -1
  178. package/dist/sync/ops.js.map +0 -1
  179. package/dist/sync/space.d.ts +0 -118
  180. package/dist/sync/space.d.ts.map +0 -1
  181. package/dist/sync/space.js.map +0 -1
  182. package/dist/types/index.d.ts +0 -369
  183. package/dist/types/index.d.ts.map +0 -1
  184. package/dist/types/index.js.map +0 -1
  185. package/dist/utils/budget.d.ts +0 -44
  186. package/dist/utils/budget.d.ts.map +0 -1
  187. package/dist/utils/budget.js.map +0 -1
  188. package/dist/utils/fs.d.ts +0 -59
  189. package/dist/utils/fs.d.ts.map +0 -1
  190. package/dist/utils/fs.js.map +0 -1
  191. package/dist/utils/hash.d.ts +0 -20
  192. package/dist/utils/hash.d.ts.map +0 -1
  193. package/dist/utils/hash.js.map +0 -1
  194. package/dist/utils/ids.d.ts +0 -47
  195. package/dist/utils/ids.d.ts.map +0 -1
  196. package/dist/utils/ids.js.map +0 -1
  197. package/dist/utils/lock.d.ts +0 -13
  198. package/dist/utils/lock.d.ts.map +0 -1
  199. package/dist/utils/lock.js.map +0 -1
  200. package/dist/utils/transcripts.d.ts +0 -63
  201. package/dist/utils/transcripts.d.ts.map +0 -1
  202. package/dist/utils/transcripts.js.map +0 -1
  203. package/dist/version.d.ts +0 -26
  204. package/dist/version.d.ts.map +0 -1
  205. package/dist/version.js.map +0 -1
@@ -0,0 +1,476 @@
1
+ "use strict";
2
+ // ─── Codex's config.toml, edited a line at a time ────────────────
3
+ //
4
+ // Codex reads its MCP servers from <CODEX_HOME>/config.toml. CRBRO needs one
5
+ // table there, [mcp_servers.crbro], and two numbers in it: how long Codex
6
+ // waits for the server to start (startup_timeout_sec) and for a tool call
7
+ // (tool_timeout_sec). Without them a cold start — a fresh npx download, an
8
+ // index being rebuilt — runs past Codex's default and the session starts with
9
+ // no memory.
10
+ //
11
+ // There is no TOML parser in this package and none is added for this: the
12
+ // file is the user's, full of tables CRBRO has no business rewriting, and a
13
+ // parse-and-serialise round trip would reorder it, drop its comments and
14
+ // change its quoting. So this reads lines. A small scanner knows which lines
15
+ // are table headers at the top level — not inside a multi-line string or a
16
+ // multi-line array — and every change is an insertion or a removal of whole
17
+ // lines inside CRBRO's own table. What CRBRO does not recognise for sure (an
18
+ // inline `crbro = { … }`, dotted `mcp_servers.crbro.x = …` keys at the top)
19
+ // is reported and left alone. Line endings and a BOM are kept as found.
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.DEFAULT_TOOL_TIMEOUT = exports.DEFAULT_STARTUP_TIMEOUT = exports.TOOL_TIMEOUT_KEY = exports.STARTUP_TIMEOUT_KEY = exports.SERVER_NAME = void 0;
22
+ exports.splitDotted = splitDotted;
23
+ exports.scanToml = scanToml;
24
+ exports.findCrbroTable = findCrbroTable;
25
+ exports.tomlString = tomlString;
26
+ exports.tomlArray = tomlArray;
27
+ exports.readCodexServer = readCodexServer;
28
+ exports.pointsAtCrbro = pointsAtCrbro;
29
+ exports.ensureCodexServer = ensureCodexServer;
30
+ exports.removeCodexServer = removeCodexServer;
31
+ exports.SERVER_NAME = 'crbro';
32
+ exports.STARTUP_TIMEOUT_KEY = 'startup_timeout_sec';
33
+ exports.TOOL_TIMEOUT_KEY = 'tool_timeout_sec';
34
+ exports.DEFAULT_STARTUP_TIMEOUT = 60;
35
+ exports.DEFAULT_TOOL_TIMEOUT = 120;
36
+ /** The pieces of a dotted key or header: bare, "basic" or 'literal' segments. */
37
+ function splitDotted(src) {
38
+ const out = [];
39
+ let i = 0;
40
+ const s = src.trim();
41
+ while (i < s.length) {
42
+ while (s[i] === ' ' || s[i] === '\t')
43
+ i++;
44
+ if (s[i] === '"') {
45
+ let j = i + 1, seg = '';
46
+ for (; j < s.length && s[j] !== '"'; j++) {
47
+ if (s[j] === '\\' && j + 1 < s.length) {
48
+ seg += s[j + 1];
49
+ j++;
50
+ }
51
+ else
52
+ seg += s[j];
53
+ }
54
+ if (j >= s.length)
55
+ return null;
56
+ out.push(seg);
57
+ i = j + 1;
58
+ }
59
+ else if (s[i] === "'") {
60
+ const j = s.indexOf("'", i + 1);
61
+ if (j < 0)
62
+ return null;
63
+ out.push(s.slice(i + 1, j));
64
+ i = j + 1;
65
+ }
66
+ else {
67
+ const m = /^[A-Za-z0-9_-]+/.exec(s.slice(i));
68
+ if (!m)
69
+ return null;
70
+ out.push(m[0]);
71
+ i += m[0].length;
72
+ }
73
+ while (s[i] === ' ' || s[i] === '\t')
74
+ i++;
75
+ if (i < s.length) {
76
+ if (s[i] !== '.')
77
+ return null;
78
+ i++;
79
+ }
80
+ }
81
+ return out.length ? out : null;
82
+ }
83
+ /**
84
+ * Walks the file once and marks, for every line, whether it is a top-level
85
+ * table header and which key (if any) starts on it. Strings, comments,
86
+ * multi-line strings and arrays/inline tables spanning lines are followed so a
87
+ * `[` inside one is never taken for a header.
88
+ */
89
+ function scanToml(text) {
90
+ const raw = text.split(/\r?\n/);
91
+ const lines = raw.map(t => ({ text: t, header: null, arrayTable: false, key: null, valueEnd: -1 }));
92
+ let multi = null;
93
+ let depth = 0;
94
+ let valueStart = -1;
95
+ for (let li = 0; li < raw.length; li++) {
96
+ const t = raw[li];
97
+ let i = 0;
98
+ if (!multi && depth === 0) {
99
+ const trimmed = t.trim();
100
+ if (trimmed.startsWith('[')) {
101
+ const arr = trimmed.startsWith('[[');
102
+ const close = arr ? trimmed.indexOf(']]') : findClose(trimmed);
103
+ if (close > 0) {
104
+ const inner = trimmed.slice(arr ? 2 : 1, close);
105
+ const parts = splitDotted(inner);
106
+ if (parts) {
107
+ lines[li].header = parts;
108
+ lines[li].arrayTable = arr;
109
+ }
110
+ }
111
+ continue;
112
+ }
113
+ const km = /^\s*((?:[A-Za-z0-9_-]+|"(?:\\.|[^"\\])*"|'[^']*')(?:\s*\.\s*(?:[A-Za-z0-9_-]+|"(?:\\.|[^"\\])*"|'[^']*'))*)\s*=/.exec(t);
114
+ if (km) {
115
+ lines[li].key = km[1];
116
+ valueStart = li;
117
+ i = km[0].length;
118
+ }
119
+ else {
120
+ continue; // blank, comment, or something this scanner does not need
121
+ }
122
+ }
123
+ // Inside a value: follow strings, comments and brackets to the end of the line.
124
+ while (i < t.length) {
125
+ if (multi) {
126
+ const end = t.indexOf(multi, i);
127
+ if (end < 0) {
128
+ i = t.length;
129
+ break;
130
+ }
131
+ i = end + 3;
132
+ while (t[i] === multi[0])
133
+ i++; // """" closes with a quote inside
134
+ multi = null;
135
+ continue;
136
+ }
137
+ const c = t[i];
138
+ if (c === '#')
139
+ break;
140
+ if (t.startsWith('"""', i)) {
141
+ multi = '"""';
142
+ i += 3;
143
+ continue;
144
+ }
145
+ if (t.startsWith("'''", i)) {
146
+ multi = "'''";
147
+ i += 3;
148
+ continue;
149
+ }
150
+ if (c === '"') {
151
+ i++;
152
+ while (i < t.length && t[i] !== '"')
153
+ i += t[i] === '\\' ? 2 : 1;
154
+ i++;
155
+ continue;
156
+ }
157
+ if (c === "'") {
158
+ const end = t.indexOf("'", i + 1);
159
+ i = end < 0 ? t.length : end + 1;
160
+ continue;
161
+ }
162
+ if (c === '[' || c === '{')
163
+ depth++;
164
+ else if ((c === ']' || c === '}') && depth > 0)
165
+ depth--;
166
+ i++;
167
+ }
168
+ if (!multi && depth === 0 && valueStart >= 0) {
169
+ lines[valueStart].valueEnd = li;
170
+ valueStart = -1;
171
+ }
172
+ }
173
+ if (valueStart >= 0)
174
+ lines[valueStart].valueEnd = raw.length - 1;
175
+ return lines;
176
+ }
177
+ function findClose(header) {
178
+ let q = null;
179
+ for (let i = 1; i < header.length; i++) {
180
+ const c = header[i];
181
+ if (q) {
182
+ if (c === q)
183
+ q = null;
184
+ else if (c === '\\' && q === '"')
185
+ i++;
186
+ continue;
187
+ }
188
+ if (c === '"' || c === "'")
189
+ q = c;
190
+ else if (c === ']')
191
+ return i;
192
+ }
193
+ return -1;
194
+ }
195
+ function sameName(a, b) { return a === b; }
196
+ function findCrbroTable(text, name = exports.SERVER_NAME) {
197
+ const lines = scanToml(text);
198
+ const out = { table: null, subtables: [], keys: new Map(), uncertain: null };
199
+ const headers = lines.map((l, i) => ({ i, h: l.header })).filter(x => x.h);
200
+ const endOf = (start) => {
201
+ const next = headers.find(x => x.i > start);
202
+ let end = next ? next.i : lines.length;
203
+ // Comments and blank lines right before the next header belong to it, not to us.
204
+ while (end - 1 > start && /^\s*(#.*)?$/.test(lines[end - 1].text))
205
+ end--;
206
+ return end;
207
+ };
208
+ let current = null;
209
+ for (let i = 0; i < lines.length; i++) {
210
+ const l = lines[i];
211
+ if (l.header) {
212
+ const h = l.header;
213
+ current = null;
214
+ if (h.length >= 2 && h[0] === 'mcp_servers' && sameName(h[1], name)) {
215
+ if (l.arrayTable) {
216
+ out.uncertain = `[[${h.join('.')}]] is an array of tables`;
217
+ continue;
218
+ }
219
+ if (h.length === 2) {
220
+ if (out.table) {
221
+ out.uncertain = `[mcp_servers.${name}] appears twice`;
222
+ continue;
223
+ }
224
+ out.table = { start: i, end: endOf(i) };
225
+ current = 'main';
226
+ }
227
+ else {
228
+ out.subtables.push({ start: i, end: endOf(i) });
229
+ current = 'sub';
230
+ }
231
+ }
232
+ else if (h.length === 1 && h[0] === 'mcp_servers') {
233
+ current = 'servers';
234
+ }
235
+ continue;
236
+ }
237
+ if (!l.key)
238
+ continue;
239
+ const key = splitDotted(l.key);
240
+ if (!key)
241
+ continue;
242
+ if (current === 'main') {
243
+ out.keys.set(key.join('.'), i);
244
+ }
245
+ else if (current === 'servers' && key[0] === name) {
246
+ out.uncertain = `mcp_servers has ${name} as an inline key`;
247
+ }
248
+ else if (current === null && !headers.some(x => x.i < i) && key[0] === 'mcp_servers' && key[1] === name) {
249
+ out.uncertain = `mcp_servers.${name} is set with dotted keys`;
250
+ }
251
+ else if (current === null && !headers.some(x => x.i < i) && key[0] === 'mcp_servers' && key.length === 1) {
252
+ out.uncertain = 'mcp_servers is an inline table';
253
+ }
254
+ }
255
+ return out;
256
+ }
257
+ /** A TOML basic string: JSON's escapes are a subset TOML reads the same. */
258
+ function tomlString(value) {
259
+ return JSON.stringify(value);
260
+ }
261
+ function tomlArray(values) {
262
+ return `[${values.map(tomlString).join(', ')}]`;
263
+ }
264
+ /** The value of a single-line `key = value` as written, or null. */
265
+ function rawValue(line) {
266
+ const m = /^[^=]*=\s*(.*)$/.exec(line);
267
+ if (!m)
268
+ return null;
269
+ return stripComment(m[1]).trim();
270
+ }
271
+ function stripComment(s) {
272
+ let q = null;
273
+ for (let i = 0; i < s.length; i++) {
274
+ const c = s[i];
275
+ if (q) {
276
+ if (c === q)
277
+ q = null;
278
+ else if (c === '\\' && q === '"')
279
+ i++;
280
+ continue;
281
+ }
282
+ if (c === '"' || c === "'")
283
+ q = c;
284
+ else if (c === '#')
285
+ return s.slice(0, i);
286
+ }
287
+ return s;
288
+ }
289
+ /** A TOML string or array of strings read loosely, for display and for "does it point at CRBRO". */
290
+ function looseStrings(value) {
291
+ const out = [];
292
+ for (const m of value.matchAll(/"((?:\\.|[^"\\])*)"|'([^']*)'/g)) {
293
+ if (m[1] !== undefined) {
294
+ try {
295
+ out.push(JSON.parse(`"${m[1]}"`));
296
+ }
297
+ catch {
298
+ out.push(m[1]);
299
+ }
300
+ }
301
+ else
302
+ out.push(m[2]);
303
+ }
304
+ return out;
305
+ }
306
+ /** What config.toml says about CRBRO's server, read only. */
307
+ function readCodexServer(text, name = exports.SERVER_NAME) {
308
+ const t = findCrbroTable(text, name);
309
+ const lines = text.split(/\r?\n/);
310
+ const info = { present: !!t.table || !!t.uncertain, uncertain: t.uncertain, command: null, args: [],
311
+ startupTimeout: null, toolTimeout: null, enabled: null };
312
+ const valueOf = (key) => {
313
+ const at = t.keys.get(key);
314
+ if (at === undefined)
315
+ return null;
316
+ const scanned = scanToml(text)[at];
317
+ const end = scanned.valueEnd >= at ? scanned.valueEnd : at;
318
+ return rawValue(lines.slice(at, end + 1).join('\n'));
319
+ };
320
+ const cmd = valueOf('command');
321
+ if (cmd !== null)
322
+ info.command = looseStrings(cmd)[0] ?? null;
323
+ const args = valueOf('args');
324
+ if (args !== null)
325
+ info.args = looseStrings(args);
326
+ const num = (v) => (v !== null && /^[+]?\d+(\.\d+)?$/.test(v) ? Number(v) : null);
327
+ info.startupTimeout = num(valueOf(exports.STARTUP_TIMEOUT_KEY));
328
+ info.toolTimeout = num(valueOf(exports.TOOL_TIMEOUT_KEY));
329
+ const enabled = valueOf('enabled');
330
+ info.enabled = enabled === 'true' ? true : enabled === 'false' ? false : null;
331
+ return info;
332
+ }
333
+ /** True when a command line runs CRBRO: the npm package, or a stable engine folder. */
334
+ function pointsAtCrbro(command, args) {
335
+ const all = [command ?? '', ...args].join(' ').replace(/\\/g, '/').toLowerCase();
336
+ return /crbro-memory|\.crbro-engine|crbro\.mjs/.test(all);
337
+ }
338
+ function eolOf(text) { return /\r\n/.test(text) ? '\r\n' : '\n'; }
339
+ /**
340
+ * Makes sure [mcp_servers.crbro] exists and carries both timeouts. Adds only
341
+ * what is missing; a value the user set is kept and reported.
342
+ */
343
+ function ensureCodexServer(text, opts) {
344
+ const bom = text.charCodeAt(0) === 0xfeff;
345
+ const body = bom ? text.slice(1) : text;
346
+ const eol = eolOf(body);
347
+ const startup = opts.startupTimeout ?? exports.DEFAULT_STARTUP_TIMEOUT;
348
+ const tool = opts.toolTimeout ?? exports.DEFAULT_TOOL_TIMEOUT;
349
+ const found = findCrbroTable(body);
350
+ const result = { text, changed: false, actions: [], kept: [], refused: null };
351
+ if (found.uncertain) {
352
+ result.refused = `${found.uncertain}; add ${exports.STARTUP_TIMEOUT_KEY} = ${startup} and ${exports.TOOL_TIMEOUT_KEY} = ${tool} to it by hand.`;
353
+ return result;
354
+ }
355
+ const lines = body.split(/\r?\n/);
356
+ if (!found.table) {
357
+ const block = [
358
+ `[mcp_servers.${exports.SERVER_NAME}]`,
359
+ `command = ${tomlString(opts.command)}`,
360
+ `args = ${tomlArray(opts.args)}`,
361
+ `${exports.STARTUP_TIMEOUT_KEY} = ${startup}`,
362
+ `${exports.TOOL_TIMEOUT_KEY} = ${tool}`,
363
+ ];
364
+ // Appended at the end, after one blank line; the file keeps its last newline.
365
+ const hadFinalNewline = body.length === 0 || /\r?\n$/.test(body);
366
+ const trimmed = [...lines];
367
+ if (hadFinalNewline && trimmed.length && trimmed[trimmed.length - 1] === '')
368
+ trimmed.pop();
369
+ while (trimmed.length && trimmed[trimmed.length - 1].trim() === '')
370
+ trimmed.pop();
371
+ const out = trimmed.length ? [...trimmed, '', ...block] : block;
372
+ result.text = (bom ? '\ufeff' : '') + out.join(eol) + eol;
373
+ result.changed = true;
374
+ result.actions.push(`registered the MCP server [mcp_servers.${exports.SERVER_NAME}] (${[opts.command, ...opts.args].join(' ')})`);
375
+ result.actions.push(`${exports.STARTUP_TIMEOUT_KEY} = ${startup}, ${exports.TOOL_TIMEOUT_KEY} = ${tool}`);
376
+ return result;
377
+ }
378
+ const info = readCodexServer(body);
379
+ const scanned = scanToml(body);
380
+ const edits = [];
381
+ // Insertion point: right after the last key line (and its multi-line value) of the main table.
382
+ let insertAt = found.table.start + 1;
383
+ for (const at of found.keys.values()) {
384
+ const end = scanned[at].valueEnd >= at ? scanned[at].valueEnd : at;
385
+ insertAt = Math.max(insertAt, end + 1);
386
+ }
387
+ const additions = [];
388
+ if (opts.replaceCommand) {
389
+ const want = { command: opts.command, args: opts.args };
390
+ const same = info.command === want.command && JSON.stringify(info.args) === JSON.stringify(want.args);
391
+ if (!same) {
392
+ for (const key of ['command', 'args']) {
393
+ const at = found.keys.get(key);
394
+ const line = key === 'command' ? `command = ${tomlString(want.command)}` : `args = ${tomlArray(want.args)}`;
395
+ if (at === undefined) {
396
+ additions.push(line);
397
+ continue;
398
+ }
399
+ const end = scanned[at].valueEnd >= at ? scanned[at].valueEnd : at;
400
+ edits.push({ at, remove: end - at + 1, insert: [line] });
401
+ }
402
+ result.actions.push(`[mcp_servers.${exports.SERVER_NAME}] now runs ${[opts.command, ...opts.args].join(' ')} (was: ${[info.command ?? '?', ...info.args].join(' ')})`);
403
+ }
404
+ }
405
+ else if (!found.keys.has('command') && !found.keys.has('url')) {
406
+ additions.push(`command = ${tomlString(opts.command)}`, `args = ${tomlArray(opts.args)}`);
407
+ result.actions.push(`[mcp_servers.${exports.SERVER_NAME}] had no command: added ${[opts.command, ...opts.args].join(' ')}`);
408
+ }
409
+ if (found.keys.has(exports.STARTUP_TIMEOUT_KEY))
410
+ result.kept.push(`${exports.STARTUP_TIMEOUT_KEY} = ${info.startupTimeout ?? rawValue(lines[found.keys.get(exports.STARTUP_TIMEOUT_KEY)])}`);
411
+ else {
412
+ additions.push(`${exports.STARTUP_TIMEOUT_KEY} = ${startup}`);
413
+ result.actions.push(`added ${exports.STARTUP_TIMEOUT_KEY} = ${startup}`);
414
+ }
415
+ if (found.keys.has(exports.TOOL_TIMEOUT_KEY))
416
+ result.kept.push(`${exports.TOOL_TIMEOUT_KEY} = ${info.toolTimeout ?? rawValue(lines[found.keys.get(exports.TOOL_TIMEOUT_KEY)])}`);
417
+ else {
418
+ additions.push(`${exports.TOOL_TIMEOUT_KEY} = ${tool}`);
419
+ result.actions.push(`added ${exports.TOOL_TIMEOUT_KEY} = ${tool}`);
420
+ }
421
+ if (additions.length)
422
+ edits.push({ at: insertAt, remove: 0, insert: additions });
423
+ if (!edits.length)
424
+ return result;
425
+ // Applied bottom-up so earlier indexes stay valid.
426
+ edits.sort((a, b) => b.at - a.at || a.remove - b.remove);
427
+ for (const e of edits)
428
+ lines.splice(e.at, e.remove, ...e.insert);
429
+ result.text = (bom ? '\ufeff' : '') + lines.join(eol);
430
+ result.changed = true;
431
+ return result;
432
+ }
433
+ /**
434
+ * Takes [mcp_servers.crbro] and its subtables out, only when it runs CRBRO.
435
+ * The blank line that separated it from the table before goes with it.
436
+ */
437
+ function removeCodexServer(text) {
438
+ const bom = text.charCodeAt(0) === 0xfeff;
439
+ const body = bom ? text.slice(1) : text;
440
+ const eol = eolOf(body);
441
+ const found = findCrbroTable(body);
442
+ const result = { text, changed: false, notOurs: null, refused: null };
443
+ if (found.uncertain) {
444
+ result.refused = `${found.uncertain}: remove it by hand`;
445
+ return result;
446
+ }
447
+ if (!found.table && found.subtables.length === 0)
448
+ return result;
449
+ const info = readCodexServer(body);
450
+ if (found.table && !pointsAtCrbro(info.command, info.args)) {
451
+ result.notOurs = [info.command ?? '(no command)', ...info.args].join(' ');
452
+ return result;
453
+ }
454
+ const lines = body.split(/\r?\n/);
455
+ const ranges = [...(found.table ? [found.table] : []), ...found.subtables].sort((a, b) => b.start - a.start);
456
+ for (const r of ranges) {
457
+ let start = r.start;
458
+ let end = r.end;
459
+ // Blank lines after the table's last key, up to the next header, go too.
460
+ while (end < lines.length && lines[end].trim() === '')
461
+ end++;
462
+ if (end >= lines.length) {
463
+ // Last table in the file: drop the blank line(s) before it instead.
464
+ while (start > 0 && lines[start - 1].trim() === '')
465
+ start--;
466
+ }
467
+ lines.splice(start, end - start);
468
+ }
469
+ let out = lines.join(eol);
470
+ if (/\r?\n$/.test(body) && !out.endsWith(eol))
471
+ out += eol;
472
+ result.text = (bom ? '\ufeff' : '') + out;
473
+ result.changed = true;
474
+ return result;
475
+ }
476
+ //# sourceMappingURL=codexconfig.js.map