@minhspark/codex-mcp-bridge 1.21.1 → 1.21.2
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/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +15 -0
- package/README.md +6 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/scripts/install-native-relay.mjs +1 -1
- package/scripts/publish-repo-analytics.mjs +19 -2
- package/src/claude-bridge.mjs +1 -1
- package/src/index.mjs +1 -1
- package/src/native-relay-companion.mjs +1 -1
- package/src/telemetry.mjs +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [SemVer](ht
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.21.2] - 2026-10-10
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Show an npm download trend in the repository README, with links to the interactive npmcharts view and GitHub dependents.
|
|
12
|
+
- Publish a source-dated trend SVG from the existing hourly analytics history. Distinguish unavailable data from a reported zero and label stale sources at generation time.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Preserve a telemetry lock replaced by another writer on Windows even when rapid file creation gives both locks the same reported file identity and birth time.
|
|
17
|
+
|
|
18
|
+
### Upgrade notes
|
|
19
|
+
|
|
20
|
+
- MCP behavior is unchanged. The repository analytics workflow publishes the new SVG after this release reaches `main`.
|
|
21
|
+
|
|
7
22
|
## [1.21.1] - 2026-10-07
|
|
8
23
|
|
|
9
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -16,6 +16,12 @@ Send prompts and replies between **Claude and Codex**, keeping each conversation
|
|
|
16
16
|
|
|
17
17
|
https://github.com/user-attachments/assets/98b23989-826f-4d7d-9dcb-ad7dd2739095
|
|
18
18
|
|
|
19
|
+
## Package activity
|
|
20
|
+
|
|
21
|
+
[](https://www.npmcharts.com/compare/%40minhspark%2Fcodex-mcp-bridge?minimal=true)
|
|
22
|
+
|
|
23
|
+
[Explore the interactive npmcharts trend](https://www.npmcharts.com/compare/%40minhspark%2Fcodex-mcp-bridge?minimal=true) · [View public GitHub dependents](https://github.com/buidangminh23/codex-mcp-bridge/network/dependents). The chart uses the repository's hourly npm analytics snapshots; npm downloads include updates and automation.
|
|
24
|
+
|
|
19
25
|
## Release notifications
|
|
20
26
|
|
|
21
27
|
To receive new release notifications, open [this repository](https://github.com/buidangminh23/codex-mcp-bridge), select **Watch → Custom → Releases**, then click **Apply**. Choose GitHub or email delivery in your [notification settings](https://github.com/settings/notifications).
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "codex-mcp-bridge",
|
|
4
|
-
"version": "1.21.
|
|
4
|
+
"version": "1.21.2",
|
|
5
5
|
"description": "Connect ChatGPT to your own Codex Desktop projects and conversations.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Bui Dang Minh",
|
|
@@ -23,7 +23,7 @@ import { enableCodexFullAccess, fullAccessEnabled } from "../src/codex-full-acce
|
|
|
23
23
|
|
|
24
24
|
exitForVersionRequest(import.meta.url);
|
|
25
25
|
|
|
26
|
-
const VERSION = "1.21.
|
|
26
|
+
const VERSION = "1.21.2";
|
|
27
27
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
28
28
|
const entry = path.join(root, "src", "mcp-supervisor.mjs");
|
|
29
29
|
const entryArgs = ["native-relay-companion.mjs"];
|
|
@@ -128,6 +128,23 @@ export function renderGithubViewsBadge(data, { now = Date.now() } = {}) {
|
|
|
128
128
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="24" viewBox="0 0 ${width} 24" role="img" aria-label="${escapeXml(accessible)}"><title>${escapeXml(`${accessible}. ${detail}`)}</title><rect width="${width}" height="24" rx="4" fill="#555"/><path d="M${labelWidth} 0H${width - 4}Q${width} 0 ${width} 4V20Q${width} 24 ${width - 4} 24H${labelWidth}Z" fill="${color}"/><g fill="#fff" font-family="DejaVu Sans Mono,monospace" font-size="12" text-anchor="middle"><text x="${labelWidth / 2}" y="16">${escapeXml(label)}</text><text x="${labelWidth + messageWidth / 2}" y="16">${escapeXml(message)}</text></g></svg>\n`;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
export function renderNpmDownloadsChart(data, { now = Date.now() } = {}) {
|
|
132
|
+
const snapshot = [...data.snapshots].sort((a, b) => String(b.collectedAt).localeCompare(String(a.collectedAt))).find(row => row.npm);
|
|
133
|
+
const collectedAt = snapshot?.sourceCollectedAt?.npm;
|
|
134
|
+
const rows = Object.entries(data.daily.npm || {}).filter(([day, row]) => date(day) && number(row?.downloads) != null).sort(([left], [right]) => left.localeCompare(right)).slice(-30);
|
|
135
|
+
const values = rows.map(([, row]) => row.downloads);
|
|
136
|
+
const maximum = Math.max(1, ...values);
|
|
137
|
+
const total = values.reduce((sum, value) => sum + value, 0);
|
|
138
|
+
const status = !date(collectedAt) ? 'Source unavailable' : now - Date.parse(collectedAt) > staleAfter ? 'Source stale at generation' : 'Source collected';
|
|
139
|
+
const summary = rows.length ? `${format(total)} downloads across ${rows.length} reported ${rows.length === 1 ? 'day' : 'days'} · ${rows[0][0]} to ${rows.at(-1)[0]} UTC` : 'No download history available';
|
|
140
|
+
const points = rows.map(([day, row], index) => ({ day, value: row.downloads, x: 76 + index * 680 / Math.max(1, rows.length - 1), y: 226 - row.downloads * 132 / maximum }));
|
|
141
|
+
const path = points.map((point, index) => `${index ? 'L' : 'M'}${point.x.toFixed(1)} ${point.y.toFixed(1)}`).join(' ');
|
|
142
|
+
const grid = [94, 160, 226].map(y => `<path d="M76 ${y}H756" stroke="#d8dee4" stroke-width="1"/>`).join('');
|
|
143
|
+
const axis = points.length ? `${grid}<text x="66" y="98" text-anchor="end" font-size="12" fill="#57606a">${escapeXml(format(maximum))}</text><text x="66" y="230" text-anchor="end" font-size="12" fill="#57606a">0</text><text x="76" y="250" font-size="12" fill="#57606a">${escapeXml(rows[0][0])}</text><text x="756" y="250" text-anchor="end" font-size="12" fill="#57606a">${escapeXml(rows.at(-1)[0])}</text>` : '';
|
|
144
|
+
const line = points.length ? `<path d="${path}" fill="none" stroke="#2f81f7" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>${points.map(point => `<circle cx="${point.x.toFixed(1)}" cy="${point.y.toFixed(1)}" r="3" fill="#2f81f7"><title>${escapeXml(`${point.day}: ${format(point.value)} downloads`)}</title></circle>`).join('')}` : '<text x="416" y="167" text-anchor="middle" font-size="17" fill="#57606a">Download data unavailable</text>';
|
|
145
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="820" height="300" viewBox="0 0 820 300" role="img" aria-label="npm download trend"><title>npm download trend</title><desc>${escapeXml(`${summary}. ${status}: ${displayTime(collectedAt)}.`)}</desc><rect width="820" height="300" rx="12" fill="#ffffff"/><rect x="0.5" y="0.5" width="819" height="299" rx="11.5" fill="none" stroke="#d8dee4"/><g font-family="Arial, sans-serif"><text x="32" y="38" font-size="21" font-weight="bold" fill="#24292f">npm download trend</text><text x="32" y="65" font-size="14" fill="#57606a">${escapeXml(summary)}</text>${axis}${line}<text x="32" y="280" font-size="12" fill="${status === 'Source collected' ? '#57606a' : '#9a6700'}">${escapeXml(`npm API · ${status} · ${displayTime(collectedAt)}`)}</text></g></svg>\n`;
|
|
146
|
+
}
|
|
147
|
+
|
|
131
148
|
export function renderLiveDashboard(data) {
|
|
132
149
|
const latest = source => [...data.snapshots].sort((a, b) => String(b.collectedAt).localeCompare(String(a.collectedAt))).find(row => row[source]);
|
|
133
150
|
const npm = latest('npm');
|
|
@@ -216,9 +233,9 @@ export function publicFiles(history, usage) {
|
|
|
216
233
|
const usageDays = Object.fromEntries((data.usage?.daily || []).map(row => [row.day, row.installations]));
|
|
217
234
|
const days = [...new Set([...Object.keys(data.daily.views), ...Object.keys(data.daily.clones), ...Object.keys(data.daily.npm), ...Object.keys(usageDays)])].sort().reverse();
|
|
218
235
|
const table = ['| Date (UTC) | Views | Unique visitors | Clones | Unique cloners | npm downloads | Active installations |', '|---|---:|---:|---:|---:|---:|---:|', ...days.map(day => `| ${day} | ${[data.daily.views[day]?.count, data.daily.views[day]?.uniques, data.daily.clones[day]?.count, data.daily.clones[day]?.uniques, data.daily.npm[day]?.downloads, usageDays[day]].map(format).join(' | ')} |`)].join('\n');
|
|
219
|
-
const readme = `# Public repository analytics\n\n\n\n[Aggregate JSON history](data.json) · [Repository](https://github.com/${data.repo})\n\nAll dates and source windows use UTC. Source timestamps on the dashboard show when each metric was last available. Downloads and clones include automation and do not measure people. Unique visitors/cloners apply only to the source window; never sum daily unique counts across dates. Active installations cover consenting installations only, with distinct counts for each whole period. Operating system and version breakdowns cover 30 days; an installation may appear in multiple version groups after upgrading.\n\nThis branch contains only aggregate statistics. No installation identifiers, prompts, account details, credentials, private error messages, or local paths are published. Missing metrics mean unavailable, not zero.\n\n## Daily history\n\nEvery archived source day is listed below, newest first. Active installations count consenting installations that reported that UTC day.\n\n${table}\n`;
|
|
236
|
+
const readme = `# Public repository analytics\n\n\n\n\n\n[Aggregate JSON history](data.json) · [Repository](https://github.com/${data.repo})\n\nAll dates and source windows use UTC. Source timestamps on the dashboard show when each metric was last available. Downloads and clones include automation and do not measure people. Unique visitors/cloners apply only to the source window; never sum daily unique counts across dates. Active installations cover consenting installations only, with distinct counts for each whole period. Operating system and version breakdowns cover 30 days; an installation may appear in multiple version groups after upgrading.\n\nThis branch contains only aggregate statistics. No installation identifiers, prompts, account details, credentials, private error messages, or local paths are published. Missing metrics mean unavailable, not zero.\n\n## Daily history\n\nEvery archived source day is listed below, newest first. Active installations count consenting installations that reported that UTC day.\n\n${table}\n`;
|
|
220
237
|
const pagesUrl = `https://${data.repo.split('/')[0]}.github.io/${data.repo.split('/')[1]}/`;
|
|
221
|
-
return { 'stats-github-views.svg': renderGithubViewsBadge(data), 'dashboard.svg': renderPublicDashboard(data), 'README.md': readme.replace('![Repository dashboard]', `[Live dashboard · refreshes every 30 seconds](${pagesUrl})\n\n![Repository dashboard]`), 'data.json': `${JSON.stringify(data, null, 2)}\n`, 'index.html': renderLiveDashboard(data) };
|
|
238
|
+
return { 'stats-github-views.svg': renderGithubViewsBadge(data), 'stats-npm-downloads.svg': renderNpmDownloadsChart(data), 'dashboard.svg': renderPublicDashboard(data), 'README.md': readme.replace('![Repository dashboard]', `[Live dashboard · refreshes every 30 seconds](${pagesUrl})\n\n![Repository dashboard]`), 'data.json': `${JSON.stringify(data, null, 2)}\n`, 'index.html': renderLiveDashboard(data) };
|
|
222
239
|
}
|
|
223
240
|
|
|
224
241
|
export function githubEnvironment(token, env = process.env) {
|
package/src/claude-bridge.mjs
CHANGED
|
@@ -29,7 +29,7 @@ import { registerProjectOnboardingTools } from "./project-onboarding-tools.mjs";
|
|
|
29
29
|
|
|
30
30
|
exitForVersionRequest(import.meta.url);
|
|
31
31
|
|
|
32
|
-
const VERSION = "1.21.
|
|
32
|
+
const VERSION = "1.21.2";
|
|
33
33
|
void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
|
|
34
34
|
const FORWARD_MIN_INTERVAL_MS = 5000;
|
|
35
35
|
const FORWARD_MAX_PER_SESSION = 50;
|
package/src/index.mjs
CHANGED
|
@@ -39,7 +39,7 @@ import { PROJECT_SETUP_TOOLS, registerProjectOnboardingTools } from "./project-o
|
|
|
39
39
|
|
|
40
40
|
exitForVersionRequest(import.meta.url);
|
|
41
41
|
|
|
42
|
-
const VERSION = "1.21.
|
|
42
|
+
const VERSION = "1.21.2";
|
|
43
43
|
void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
|
|
44
44
|
const log = (msg) => process.stderr.write(`[codex-mcp-bridge] ${msg}\n`);
|
|
45
45
|
|
|
@@ -36,7 +36,7 @@ import { NativeCreationReceipts, creationIdValid } from "./native-creation-recei
|
|
|
36
36
|
|
|
37
37
|
exitForVersionRequest(import.meta.url);
|
|
38
38
|
|
|
39
|
-
const VERSION = "1.21.
|
|
39
|
+
const VERSION = "1.21.2";
|
|
40
40
|
const log = (msg) => process.stderr.write(`[native-relay] ${msg}\n`);
|
|
41
41
|
|
|
42
42
|
function errorResponse(code, message, sent) {
|
package/src/telemetry.mjs
CHANGED
|
@@ -51,11 +51,12 @@ async function locked(directory, action) {
|
|
|
51
51
|
throw new Error('Telemetry settings are busy. Locks never expire automatically; inspect active telemetry processes before repairing a leftover lock.');
|
|
52
52
|
}
|
|
53
53
|
let owned;
|
|
54
|
-
|
|
54
|
+
const token = randomUUID();
|
|
55
|
+
try { await lock.writeFile(token); owned = await lock.stat({ bigint: true }); return await action(); }
|
|
55
56
|
finally {
|
|
56
57
|
try {
|
|
57
|
-
const current = await lstat(filename).catch(error => { if (error.code !== 'ENOENT') throw error; return null; });
|
|
58
|
-
if (owned && current?.isFile() && !current.isSymbolicLink() && current.nlink ===
|
|
58
|
+
const current = await lstat(filename, { bigint: true }).catch(error => { if (error.code !== 'ENOENT') throw error; return null; });
|
|
59
|
+
if (owned && current?.isFile() && !current.isSymbolicLink() && current.nlink === 1n && owned.dev === current.dev && owned.ino === current.ino && owned.birthtimeNs === current.birthtimeNs && owned.ctimeNs === current.ctimeNs && owned.size === current.size) await unlink(filename);
|
|
59
60
|
} finally { await lock.close(); }
|
|
60
61
|
}
|
|
61
62
|
}
|