clearotron 0.3.2-beta.12 → 0.3.2-beta.14
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/CONTRIBUTING.md +6 -5
- package/INSTALL.md +3 -4
- package/README.md +2 -1
- package/bin/clearotron.mjs +7 -1
- package/bin/example.mjs +61 -23
- package/bin/onboard.mjs +16 -2
- package/bin/start.mjs +6 -3
- package/build-info.json +2 -2
- package/docs/CLIENT-MCP.md +6 -6
- package/docs/DELIVERY.md +3 -3
- package/docs/ONBOARDING.md +1 -1
- package/docs/PORTAL.md +3 -3
- package/docs/RELEASES.md +1 -1
- package/docs/SECURITY.md +1 -1
- package/docs/architecture/04-configuration-reference.md +4 -4
- package/docs/architecture/05-config-governance.md +10 -10
- package/docs/architecture/06-operations-runbook.md +3 -3
- package/docs/architecture/07-quality-and-audit.md +1 -1
- package/docs/architecture/08-development-guide.md +2 -2
- package/docs/architecture/09-security-and-data.md +1 -1
- package/docs/decisions/0002-no-dark-functionality.md +1 -1
- package/docs/decisions/0006-what-the-public-repository-carries.md +3 -3
- package/driver/CHANGELOG.md +21 -0
- package/driver/ask-ledger.mjs +1 -1
- package/driver/band-shape.mjs +1 -1
- package/driver/bundled-demos.mjs +2 -2
- package/driver/card-budget.mjs +2 -2
- package/driver/case-law-ledger.mjs +2 -2
- package/driver/connotation-search.mjs +5 -5
- package/driver/contract-e3-backlog.mjs +13 -13
- package/driver/coverage-form.mjs +2 -2
- package/driver/demo-container.mjs +26 -2
- package/driver/disposition-tool.mjs +2 -2
- package/driver/engine/CONTRACT.md +3 -3
- package/driver/engine/mcp/codex-config.mjs +3 -11
- package/driver/engine/mcp/gather-config.mjs +1 -1
- package/driver/engine/openai-agent.mjs +2 -2
- package/driver/findings-model.mjs +21 -6
- package/driver/gateway.mjs +1 -1
- package/driver/package.json +1 -1
- package/driver/pipeline-knockout.mjs +1 -1
- package/driver/pipeline.mjs +4 -4
- package/driver/placement-union.mjs +1 -1
- package/driver/portal-mcp-client.mjs +1 -1
- package/driver/portal-report.mjs +4 -1
- package/driver/portal-service.mjs +17 -5
- package/driver/predelivery-lint.mjs +9 -4
- package/driver/progress.mjs +37 -1
- package/driver/publish/render-knockout.mjs +31 -6
- package/driver/publish/render.mjs +6 -6
- package/driver/record-discard.mjs +1 -1
- package/driver/register-count.mjs +1 -1
- package/driver/register-digest-record.mjs +1 -1
- package/driver/report-card-record.mjs +2 -2
- package/driver/roster-verdict.mjs +2 -2
- package/driver/search-policy.mjs +1 -1
- package/driver/skeptic-record.mjs +1 -1
- package/driver/stages.mjs +1 -1
- package/driver/suite-census.json +40 -10
- package/driver/systemd/README.md +1 -1
- package/driver/unit-inventory.mjs +2 -2
- package/driver/verify.mjs +1 -1
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/CONNECT.md +8 -8
- package/mcp-server/lib/runs.mjs +1 -1
- package/mcp-server/package.json +1 -1
- package/mcp-server/serve.mjs +27 -0
- package/package.json +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/free-tier/src/capabilities.js +2 -2
- package/providers/jx/src/core.js +3 -3
- package/providers/jx/src/turn-envelope.mjs +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/perplexity/README.md +1 -1
- package/providers/signa/src/capabilities.js +2 -2
- package/providers/signa/src/core.js +2 -2
- package/providers/uspto-local/src/core.js +1 -1
- package/providers/uspto-local/src/sync.js +1 -1
- package/scripts/README.md +2 -6
- package/scripts/citation-anchor-report.mjs +1 -1
- package/scripts/citation-line-check.mjs +3 -3
- package/scripts/e2e.mjs +1 -1
- package/scripts/env-audit.mjs +1 -1
- package/scripts/env-classify.mjs +1 -1
- package/scripts/pack-publishable.mjs +1 -1
- package/scripts/release-artifact-seal.mjs +2 -2
- package/scripts/settings-render-check.mjs +5 -1
- package/scripts/strip-tracker-citations.mjs +4 -4
- package/scripts/test-run.mjs +46 -3
- package/shared/browser-temp-root.mjs +10 -3
- package/shared/client-door.mjs +18 -6
- package/shared/connect-clients.mjs +2 -0
- package/shared/demo-start-args.mjs +10 -0
- package/shared/identifier-scan.mjs +2 -2
- package/shared/invocation.mjs +1 -1
- package/shared/reference-guard-classes.mjs +5 -3
- package/shared/stdio-connect.mjs +80 -28
- package/shared/toml-string.mjs +25 -0
- package/shared/writing-standard-classes.mjs +2 -3
package/shared/stdio-connect.mjs
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
import { join } from "node:path";
|
|
33
33
|
import { fileURLToPath } from "node:url";
|
|
34
34
|
import { stableInstallRoot } from "./permanent-install.mjs";
|
|
35
|
+
import { tomlString } from "./toml-string.mjs";
|
|
35
36
|
|
|
36
37
|
/** The install root — the directory holding `mcp-server/`, resolved from this module rather than cwd. */
|
|
37
38
|
export const INSTALL_ROOT = join(fileURLToPath(new URL(".", import.meta.url)), "..");
|
|
@@ -50,8 +51,8 @@ export const STDIO_SERVER_NAME = "trademark-artifacts";
|
|
|
50
51
|
* @param {{ installRoot?: string, workDir?: string|null }} [opts]
|
|
51
52
|
* @returns {string} the exact command to run
|
|
52
53
|
*/
|
|
53
|
-
export function stdioConnectCommand({ installRoot = stableInstallRoot({ installRoot: INSTALL_ROOT }), workDir = null, reportsDir = null, platform = process.platform } = {}) {
|
|
54
|
-
return STDIO_SHAPES["claude-cli"].render({ server: join(installRoot, "mcp-server",
|
|
54
|
+
export function stdioConnectCommand({ installRoot = stableInstallRoot({ installRoot: INSTALL_ROOT }), workDir = null, reportsDir = null, platform = process.platform, node = process.execPath } = {}) {
|
|
55
|
+
return STDIO_SHAPES["claude-cli"].render({ server: join(installRoot, "mcp-server", STDIO_ENTRY), workDir, reportsDir, platform, node });
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/**
|
|
@@ -103,15 +104,38 @@ export function stdioConnectOffer(opts = {}) {
|
|
|
103
104
|
function envOf({ workDir = null, reportsDir = null } = {}) {
|
|
104
105
|
return { ...(workDir ? { CLEAROTRON_WORK_DIR: workDir } : {}), ...(reportsDir ? { CLEAROTRON_REPORTS_DIR: reportsDir } : {}) };
|
|
105
106
|
}
|
|
106
|
-
const envFlags = (o) => Object.entries(envOf(o)).map(([k, v]) => ` -e ${k}=${v}`).join("");
|
|
107
107
|
const envBlock = (o) => (Object.keys(envOf(o)).length ? { env: envOf(o) } : {});
|
|
108
108
|
|
|
109
|
+
// ── ONE WORD PER ARGUMENT, IN THE SHELL THAT READS THE LINE ───────────────────────────────────────────
|
|
110
|
+
//
|
|
111
|
+
// The command was the arguments joined with spaces, so a path with a space in it became two arguments:
|
|
112
|
+
// `/tmp/Example User/node/bin/node` reached the assistant as `/tmp/Example` and `User/node/bin/node`
|
|
113
|
+
// (measured on the published beta, 2026-09-19). Each argument is now written as one word for the shell it
|
|
114
|
+
// is pasted into. A word made only of characters no shell treats specially is left bare, so the line for
|
|
115
|
+
// an ordinary install reads exactly as it did.
|
|
116
|
+
//
|
|
117
|
+
// POSIX shells: single quotes, the one quoting in which no character is special. A single quote inside is
|
|
118
|
+
// closed, escaped and reopened.
|
|
119
|
+
const POSIX_BARE = /^[A-Za-z0-9_@%+=:,./-]+$/;
|
|
120
|
+
export const posixWord = (s) => (POSIX_BARE.test(String(s)) ? String(s) : `'${String(s).replace(/'/g, "'\\''")}'`);
|
|
121
|
+
// Windows: cmd and PowerShell both honour double quotes, and the program then splits its command line by
|
|
122
|
+
// the C runtime's rules, under which backslashes are literal except where they run up to a quote; there
|
|
123
|
+
// they are doubled, and a quote inside the word is escaped. A comma or a parenthesis is quoted too, because
|
|
124
|
+
// PowerShell reads a bare one as an array or an expression. `$`, a backtick and `%` cannot be quoted the
|
|
125
|
+
// same way for both shells, and a path holding one is left to the reader.
|
|
126
|
+
const WINDOWS_BARE = /^[A-Za-z0-9_+=:./\\-]+$/;
|
|
127
|
+
export const windowsWord = (s) => {
|
|
128
|
+
const w = String(s);
|
|
129
|
+
if (WINDOWS_BARE.test(w)) return w;
|
|
130
|
+
return `"${w.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/, "$1$1")}"`;
|
|
131
|
+
};
|
|
132
|
+
|
|
109
133
|
/**
|
|
110
134
|
* The argument that ends Claude Code's own options. Windows PowerShell 5.1 drops a bare `--` before it
|
|
111
135
|
* reaches a native program, so the line failed with "missing required argument"; quoted, every shell passes
|
|
112
136
|
* it through (bash, zsh, PowerShell and cmd alike), so a Windows install is handed the quoted form.
|
|
113
137
|
*/
|
|
114
|
-
const separator = (
|
|
138
|
+
const separator = (windowsShell) => (windowsShell ? '"--"' : "--");
|
|
115
139
|
|
|
116
140
|
/**
|
|
117
141
|
* WHAT THE HOST ACTUALLY RUNS, and on WSL that is not `node`.
|
|
@@ -125,6 +149,12 @@ const separator = (platform = process.platform) => (platform === "win32" ? '"--"
|
|
|
125
149
|
* -e …`. The distribution is named from `WSL_DISTRO_NAME` when we have it, because a machine with more
|
|
126
150
|
* than one would otherwise get whichever is default — which may be a distribution with no install.
|
|
127
151
|
*
|
|
152
|
+
* AND IT IS NEVER LEFT BLANK (owner, 2026-09-19). With no name to hand the row used to drop `-d` and take
|
|
153
|
+
* the default distribution, which fails on a machine whose default is not this one, with nothing on screen
|
|
154
|
+
* to connect the failure to the choice. The row now carries WSL_DISTRO_PLACEHOLDER in the name's place,
|
|
155
|
+
* and the Windows side's hint says plainly to fill it in. `WSL_DISTRO_NAME` is the one place the name can
|
|
156
|
+
* be read from inside the distribution: `/etc/wsl.conf` holds settings for it, not its name.
|
|
157
|
+
*
|
|
128
158
|
* THE ENVIRONMENT CROSSES THROUGH `env`, NOT THROUGH THE HOST'S OWN env BLOCK. A host on Windows sets
|
|
129
159
|
* variables for the process it starts, which is `wsl.exe`; they do not cross the boundary into the
|
|
130
160
|
* distribution, so a work directory set that way is silently absent on the other side and the server
|
|
@@ -134,17 +164,32 @@ const separator = (platform = process.platform) => (platform === "win32" ? '"--"
|
|
|
134
164
|
* Off WSL the launcher is exactly what it always was, so every row on every other platform is
|
|
135
165
|
* byte-identical to before. PURE.
|
|
136
166
|
*/
|
|
137
|
-
|
|
167
|
+
/**
|
|
168
|
+
* WHICH NODE, AND WHICH FILE (owner, 2026-09-19). A line that said bare `node` ran whatever `node` the
|
|
169
|
+
* assistant's own PATH found. Through `wsl.exe -e`, a non-login shell, that was the distribution's system
|
|
170
|
+
* Node 18 while the demo ran on Node 22, and the server died on a syntax error before saying anything
|
|
171
|
+
* (measured on a Windows laptop). A desktop assistant on any platform starts from a bare PATH the same
|
|
172
|
+
* way. So every line names the absolute Node this install is running on, `process.execPath`, seen from
|
|
173
|
+
* where the line is composed, inside WSL when that is where the install is. It names `serve.mjs`, the
|
|
174
|
+
* entry that refuses an old Node in one plain line before the server loads.
|
|
175
|
+
*/
|
|
176
|
+
export const STDIO_ENTRY = "serve.mjs";
|
|
177
|
+
|
|
178
|
+
/** What stands in the distribution's name when it cannot be read, and the line that says to fill it in. */
|
|
179
|
+
export const WSL_DISTRO_PLACEHOLDER = "YOUR-WSL-DISTRIBUTION";
|
|
180
|
+
export const WSL_DISTRO_FILL_IN = `Replace ${WSL_DISTRO_PLACEHOLDER} with this Linux's name before you paste it: \`wsl -l\` in Windows lists the names.`;
|
|
181
|
+
|
|
182
|
+
export function stdioLauncher({ server, workDir = null, reportsDir = null, wsl = null, node = process.execPath } = {}) {
|
|
138
183
|
const vars = envOf({ workDir, reportsDir });
|
|
139
|
-
if (!wsl) return { command:
|
|
184
|
+
if (!wsl) return { command: node, args: [server], env: vars, crossesIntoWsl: false };
|
|
140
185
|
const distro = String(wsl.distro ?? "").trim();
|
|
141
186
|
return {
|
|
142
187
|
command: "wsl.exe",
|
|
143
188
|
// `-e` runs the command directly rather than through a login shell, so nothing of the reader's
|
|
144
189
|
// profile can rewrite the arguments between Windows and the server.
|
|
145
|
-
args: [
|
|
190
|
+
args: ["-d", distro || WSL_DISTRO_PLACEHOLDER, "-e",
|
|
146
191
|
...(Object.keys(vars).length ? ["env", ...Object.entries(vars).map(([k, v]) => `${k}=${v}`)] : []),
|
|
147
|
-
|
|
192
|
+
node, server],
|
|
148
193
|
// Already carried inside the argument list above; a host-side env block would set them on the
|
|
149
194
|
// Windows process and never reach the server.
|
|
150
195
|
env: {},
|
|
@@ -157,21 +202,27 @@ export const STDIO_SHAPES = Object.freeze({
|
|
|
157
202
|
"claude-cli": {
|
|
158
203
|
kind: "command",
|
|
159
204
|
where: null,
|
|
160
|
-
render: ({ server, workDir, reportsDir, platform, wsl }) => {
|
|
161
|
-
const l = stdioLauncher({ server, workDir, reportsDir, wsl });
|
|
205
|
+
render: ({ server, workDir, reportsDir, platform, wsl, node }) => {
|
|
206
|
+
const l = stdioLauncher({ server, workDir, reportsDir, wsl, node });
|
|
207
|
+
// WHICH SHELL READS IT. A native Windows install's line is pasted on Windows, and so is the row
|
|
208
|
+
// that crosses into WSL: it exists for an assistant on the Windows side. Everything else is pasted
|
|
209
|
+
// into a POSIX shell.
|
|
210
|
+
const windowsShell = platform === "win32" || l.crossesIntoWsl;
|
|
211
|
+
const word = windowsShell ? windowsWord : posixWord;
|
|
162
212
|
// The host's own `-e` flags set variables for the process IT starts. Off WSL that is the server;
|
|
163
213
|
// through the wrapper it is `wsl.exe`, and they stop at the boundary — so on WSL they ride inside
|
|
164
214
|
// the command instead and this line carries none.
|
|
165
|
-
const flags = l.crossesIntoWsl ? ""
|
|
166
|
-
|
|
215
|
+
const flags = l.crossesIntoWsl ? ""
|
|
216
|
+
: Object.entries(envOf({ workDir, reportsDir })).map(([k, v]) => ` -e ${windowsShell ? word(`${k}=${v}`) : `${k}=${word(v)}`}`).join("");
|
|
217
|
+
return `claude mcp add ${STDIO_SERVER_NAME} --scope user${flags} ${separator(windowsShell)} ${[l.command, ...l.args].map(word).join(" ")}`;
|
|
167
218
|
},
|
|
168
219
|
after: null,
|
|
169
220
|
},
|
|
170
221
|
"desktop-json": {
|
|
171
222
|
kind: "config",
|
|
172
223
|
where: "Settings → Developer → Edit Config",
|
|
173
|
-
render: ({ server, workDir, reportsDir, wsl }) => {
|
|
174
|
-
const l = stdioLauncher({ server, workDir, reportsDir, wsl });
|
|
224
|
+
render: ({ server, workDir, reportsDir, wsl, node }) => {
|
|
225
|
+
const l = stdioLauncher({ server, workDir, reportsDir, wsl, node });
|
|
175
226
|
return JSON.stringify({
|
|
176
227
|
mcpServers: {
|
|
177
228
|
[STDIO_SERVER_NAME]: {
|
|
@@ -189,8 +240,8 @@ export const STDIO_SHAPES = Object.freeze({
|
|
|
189
240
|
"generic-json": {
|
|
190
241
|
kind: "config",
|
|
191
242
|
where: "your agent's MCP server configuration",
|
|
192
|
-
render: ({ server, workDir, reportsDir, wsl }) => {
|
|
193
|
-
const l = stdioLauncher({ server, workDir, reportsDir, wsl });
|
|
243
|
+
render: ({ server, workDir, reportsDir, wsl, node }) => {
|
|
244
|
+
const l = stdioLauncher({ server, workDir, reportsDir, wsl, node });
|
|
194
245
|
return JSON.stringify({
|
|
195
246
|
command: l.command,
|
|
196
247
|
args: l.args,
|
|
@@ -203,20 +254,21 @@ export const STDIO_SHAPES = Object.freeze({
|
|
|
203
254
|
kind: "config",
|
|
204
255
|
where: "~/.codex/config.toml",
|
|
205
256
|
// Written out rather than produced by a TOML library: this is four lines with no user-supplied
|
|
206
|
-
// strings in key positions, and adding a dependency to emit them would be the larger risk.
|
|
257
|
+
// strings in key positions, and adding a dependency to emit them would be the larger risk. Every
|
|
258
|
+
// value goes through `tomlString`, so a Windows path's backslashes are escaped as TOML requires.
|
|
207
259
|
//
|
|
208
260
|
// `env` and not `env_vars`, deliberately, and CONNECT.md explains why the distinction matters:
|
|
209
261
|
// Codex does not forward the shell environment, and a credential would have to be forwarded BY NAME
|
|
210
262
|
// rather than written into a file. This server takes no credential — the work directory is a path,
|
|
211
263
|
// not a secret — so `env` is correct here and would not be for a server that wanted a key.
|
|
212
|
-
render: ({ server, workDir, reportsDir, wsl }) => {
|
|
213
|
-
const l = stdioLauncher({ server, workDir, reportsDir, wsl });
|
|
264
|
+
render: ({ server, workDir, reportsDir, wsl, node }) => {
|
|
265
|
+
const l = stdioLauncher({ server, workDir, reportsDir, wsl, node });
|
|
214
266
|
return [
|
|
215
267
|
`[mcp_servers.${STDIO_SERVER_NAME}]`,
|
|
216
|
-
`command =
|
|
217
|
-
`args = [${l.args.map(
|
|
268
|
+
`command = ${tomlString(l.command)}`,
|
|
269
|
+
`args = [${l.args.map(tomlString).join(", ")}]`,
|
|
218
270
|
...(Object.keys(l.env).length
|
|
219
|
-
? [`env = { ${Object.entries(l.env).map(([k, v]) => `${k} =
|
|
271
|
+
? [`env = { ${Object.entries(l.env).map(([k, v]) => `${k} = ${tomlString(v)}`).join(", ")} }`] : []),
|
|
220
272
|
].join("\n");
|
|
221
273
|
},
|
|
222
274
|
after: null,
|
|
@@ -276,7 +328,7 @@ export const REMOTE_SHAPES = Object.freeze({
|
|
|
276
328
|
label: "Copy block",
|
|
277
329
|
render: ({ address }) => [
|
|
278
330
|
`[mcp_servers.${STDIO_SERVER_NAME}]`,
|
|
279
|
-
`url =
|
|
331
|
+
`url = ${tomlString(address)}`,
|
|
280
332
|
].join("\n"),
|
|
281
333
|
},
|
|
282
334
|
"claude-cli-http": {
|
|
@@ -288,7 +340,7 @@ export const REMOTE_SHAPES = Object.freeze({
|
|
|
288
340
|
label: null,
|
|
289
341
|
render: ({ address }) => [
|
|
290
342
|
`[mcp_servers.${STDIO_SERVER_NAME}]`,
|
|
291
|
-
`url =
|
|
343
|
+
`url = ${tomlString(address)}`,
|
|
292
344
|
`bearer_token_env_var = "${KEY_ENV_VAR}"`,
|
|
293
345
|
].join("\n"),
|
|
294
346
|
},
|
|
@@ -335,11 +387,11 @@ export function remoteConnectFor(shape, { address = null } = {}) {
|
|
|
335
387
|
*/
|
|
336
388
|
export const WSL_ROW_HEADINGS = Object.freeze({ fromWindows: "From Windows", insideWsl: "Inside WSL" });
|
|
337
389
|
|
|
338
|
-
export function stdioConnectFor(shape, { installRoot = stableInstallRoot({ installRoot: INSTALL_ROOT }), workDir = null, reportsDir = null, platform = process.platform, wsl = null } = {}) {
|
|
390
|
+
export function stdioConnectFor(shape, { installRoot = stableInstallRoot({ installRoot: INSTALL_ROOT }), workDir = null, reportsDir = null, platform = process.platform, wsl = null, node = process.execPath } = {}) {
|
|
339
391
|
const spec = Object.hasOwn(STDIO_SHAPES, String(shape ?? "")) ? STDIO_SHAPES[shape] : null;
|
|
340
392
|
if (!spec) return null;
|
|
341
|
-
const server = join(installRoot, "mcp-server",
|
|
342
|
-
const render = (target) => spec.render({ server, workDir, reportsDir, platform, wsl: target });
|
|
393
|
+
const server = join(installRoot, "mcp-server", STDIO_ENTRY);
|
|
394
|
+
const render = (target) => spec.render({ server, workDir, reportsDir, platform, wsl: target, node });
|
|
343
395
|
const base = { shape, kind: spec.kind, where: spec.where, after: spec.after, name: STDIO_SERVER_NAME };
|
|
344
396
|
// OFF WSL NOTHING CHANGES: one launcher, no variants, and `variants: null` rather than an empty array
|
|
345
397
|
// so a consumer cannot read "this install has no sides" as "this install has two sides, both missing".
|
|
@@ -355,7 +407,7 @@ export function stdioConnectFor(shape, { installRoot = stableInstallRoot({ insta
|
|
|
355
407
|
...base,
|
|
356
408
|
text: render(wsl),
|
|
357
409
|
variants: [
|
|
358
|
-
{ heading: WSL_ROW_HEADINGS.fromWindows, text: render(wsl) },
|
|
410
|
+
{ heading: WSL_ROW_HEADINGS.fromWindows, text: render(wsl), hint: String(wsl.distro ?? "").trim() ? null : WSL_DISTRO_FILL_IN },
|
|
359
411
|
{ heading: WSL_ROW_HEADINGS.insideWsl, text: render(null) },
|
|
360
412
|
],
|
|
361
413
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
3
|
+
//
|
|
4
|
+
// ONE TOML STRING ENCODER, for every block this product writes.
|
|
5
|
+
//
|
|
6
|
+
// Two composers write TOML: the engine's per-run Codex configuration and the connection block a reader
|
|
7
|
+
// pastes into `~/.codex/config.toml`. The second wrapped raw values in double quotes, so a Windows path
|
|
8
|
+
// such as `C:\Program Files\nodejs\node.exe` produced a file TOML parsers refuse ("Unescaped '\' in a
|
|
9
|
+
// string"). A value is a TOML string only once it has been through here.
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A TOML basic string holding exactly `s`. PURE.
|
|
13
|
+
*
|
|
14
|
+
* TOML requires the quotation mark, the backslash and every control character other than tab to be
|
|
15
|
+
* escaped (U+0000 to U+0008, U+000A to U+001F, and U+007F). The named escapes are used where TOML has one,
|
|
16
|
+
* and `\uXXXX` for the rest.
|
|
17
|
+
*/
|
|
18
|
+
export function tomlString(s) {
|
|
19
|
+
const esc = String(s ?? "")
|
|
20
|
+
.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
|
21
|
+
.replace(/\x08/g, "\\b").replace(/\t/g, "\\t").replace(/\n/g, "\\n")
|
|
22
|
+
.replace(/\f/g, "\\f").replace(/\r/g, "\\r")
|
|
23
|
+
.replace(/[\x00-\x1f\x7f]/g, (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0"));
|
|
24
|
+
return `"${esc}"`;
|
|
25
|
+
}
|
|
@@ -443,9 +443,8 @@ const H1_BLOCK = /<h1\b[^>]*>([\s\S]*?)<\/h1>/g
|
|
|
443
443
|
* heading was entirely its subject.
|
|
444
444
|
*/
|
|
445
445
|
const isSubjectHeading = (inner) => {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
.replace(/<[^>]*>/g, '') // nested tags
|
|
446
|
+
let bare = String(inner).replace(/\{(?:[^{}]|\{[^{}]*\})*\}/g, '') // JSX expressions, one level of nesting
|
|
447
|
+
for (let prev = null; prev !== bare;) { prev = bare; bare = bare.replace(/<[^>]*>/g, '') } // nested tags, until none is left
|
|
449
448
|
return !/[A-Za-z]{2}/.test(bare)
|
|
450
449
|
}
|
|
451
450
|
|