Skip to content

feat(build): bundle wrapper to dist/, build-check CI, desktop-app README - #4

Merged
Dani Akash (DaniAkash) merged 2 commits into
mainfrom
feat/bundle-with-bunup
Jun 19, 2026
Merged

Dani Akash (DaniAkash) merged 2 commits into
mainfrom
feat/bundle-with-bunup

Conversation

@DaniAkash

Copy link
Copy Markdown
Contributor

Summary

Three coupled changes that put the plugin in shape for real Codex desktop-app install testing:

  1. Bundle the wrapper into plugins/browseros/dist/wrapper.js so a fresh codex plugin marketplace add actually works. The Codex plugin marketplace docs do not describe an install hook — the plugin cache it produces has no node_modules. Without bundling, the wrapper's import '@modelcontextprotocol/sdk/...' crashes at startup.
  2. Add a build-check GitHub workflow that fails any PR whose dist/ is out of date with server/. The committed bundle is the runtime artifact; CI guards drift.
  3. Rewrite README to lead with the Codex desktop app user. The prior copy framed install around CLI commands; the actual UX is a one-time terminal command followed by management inside the desktop app's Plugins panel.

Why bun build and not bunup

You picked bunup for speed and I started there, but bunup's bundle path mangles Zod's lazy-method-installation pattern and the resulting bundle crashes at startup with ReferenceError: util3 is not defined. Bun's native bun build (which bunup wraps) produces a working bundle in 16 ms. The build script invokes bun build directly:

bun build server/wrapper.js --outfile dist/wrapper.js --target node --format esm --external 'node:*'

If bunup grows past this Zod bug we can swap back; for now the simpler tool works.

What's in

File Change
plugins/browseros/package.json SDK to devDeps (bundled), build script using bun build, drop runtime deps
plugins/browseros/dist/wrapper.js Committed bundle, 574 KB, what Codex actually spawns
plugins/browseros/.mcp.json args: ["./dist/wrapper.js"]
plugins/browseros/bun.lock Pinned lockfile for deterministic CI
plugins/browseros/package-lock.json Deleted; replaced by bun.lock
.github/workflows/build-check.yml SHA-pinned: checkout v4.3.1, setup-bun v2.2.0. Runs bun install --frozen-lockfile, bun run build, git diff --exit-code dist/
.gitignore Generic dist/ ignored; force-track plugins/browseros/dist/
README.md Desktop-app-first framing; install is one terminal command + manage in app; override via ~/.codex/config.toml; local dev with bun

Smoke tested locally

  • bun run build produces dist/wrapper.js
  • Removing node_modules/ and spawning node dist/wrapper.js: initialize handshake returns serverInfo: {"name":"browseros-codex","version":"0.1.0"} cleanly
  • Stderr shows the wrapper's discovery flow as expected

Test plan

  • Build runs in <20 ms locally
  • Bundle runs without node_modules and completes MCP initialize handshake
  • git diff --exit-code dist/ returns 0 after a fresh build (CI invariant)
  • After merge, real Codex desktop app install against this repo

…top-app README

The Codex plugin marketplace does not document an install hook, so a
fresh 'codex plugin marketplace add' produces a plugin cache with no
node_modules. The wrapper would import @modelcontextprotocol/sdk and
crash at startup. Bundling the wrapper + SDK into a single committed
artifact at plugins/browseros/dist/wrapper.js makes a marketplace add
work end-to-end without extra setup on the user's side.

What changed:

- plugins/browseros/package.json: SDK moved to devDependencies (it is
  inlined at build time, not needed at runtime). New 'build' script:
  'bun build server/wrapper.js --outfile dist/wrapper.js --target node
  --format esm --external node:*'. Initially tried bunup, but bunup's
  bundle path mangles Zod's lazy method names and crashes at startup
  with 'util3 is not defined'. bun build directly produces a working
  bundle.

- plugins/browseros/dist/wrapper.js: committed bundle. 574 KB, 16 ms
  to rebuild. node:* imports stay external (Node provides them); SDK
  and all transitive deps inline.

- plugins/browseros/.mcp.json: command args updated from
  './server/wrapper.js' to './dist/wrapper.js'. Codex spawns the
  bundle.

- plugins/browseros/bun.lock: pinned lockfile for deterministic CI
  builds. Replaces the npm package-lock.json from the earlier import
  step; the wrapper no longer has runtime deps so npm vs bun is now a
  pure preference, and bun is consistent with what the build script
  needs.

- .github/workflows/build-check.yml: runs on PRs that touch the
  wrapper source or build inputs. Installs deps with
  'bun install --frozen-lockfile', rebuilds, and fails the check if
  'git diff --exit-code dist/' finds drift. Actions pinned to SHAs.

- .gitignore: keeps generic dist/ ignored, force-tracks
  plugins/browseros/dist/ since that's the committed runtime artifact.

- README.md: rewritten to lead with the Codex DESKTOP APP user. The
  prior copy framed the install around the CLI. Install is one
  terminal command ('codex plugin marketplace add ...') then the rest
  is managed inside the desktop app's Plugins panel. Config override
  moves to the documented ~/.codex/config.toml path. Local-dev
  section explains the bun build loop.
@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown

Greptile Summary

This PR bundles the MCP wrapper into plugins/browseros/dist/wrapper.js so Codex can spawn it without a node_modules directory, adds a CI workflow that fails any PR where dist/ drifts from source, and rewrites the README around the Codex desktop app install flow.

  • Bundle & entry point: @modelcontextprotocol/sdk moves to devDependencies, a bun build script produces dist/wrapper.js, and both .mcp.json and package.json#main are updated to point at the bundle.
  • CI guard: .github/workflows/build-check.yml uses SHA-pinned actions, runs bun install --frozen-lockfile + bun run build, then asserts git diff --exit-code dist/ — but uses bun-version: latest, which can cause false failures when Bun's codegen changes between the local build and CI.
  • README: Rewritten to lead with the desktop-app install UX, config.toml override path, and local dev instructions for contributors.

Confidence Score: 4/5

Safe to merge; the runtime bundle and entry-point wiring are correct, and the CI guard is sound in structure — the only fragile spot is the unpinned Bun version.

The bundling approach, .gitignore carve-out, and .mcp.json update are all correct. The CI workflow's use of bun-version: latest means the Bun codegen used in CI can silently diverge from what the developer ran locally, turning the git diff --exit-code dist/ check into a source of false failures whenever Bun ships a codegen change — without any code actually being wrong.

.github/workflows/build-check.yml — the bun-version: latest setting deserves a pinned version number to keep the dist-drift check deterministic.

Important Files Changed

Filename Overview
.github/workflows/build-check.yml New CI workflow that verifies dist/ matches source; third-party actions are SHA-pinned correctly but the Bun version is unpinned, which can cause false-positive failures when Bun codegen changes.
.gitignore Correctly restructures dist/ ignore: generic rule stays, negation carve-out force-tracks plugins/browseros/dist/ for the committed bundle artifact.
plugins/browseros/package.json Moves @modelcontextprotocol/sdk to devDependencies, adds build script using bun build, updates main to dist/wrapper.js — all consistent with the bundling strategy.
plugins/browseros/.mcp.json Switches the node entry point from ./server/wrapper.js to ./dist/wrapper.js, correctly matching the new bundled artifact path.
plugins/browseros/dist/wrapper.js Committed 574 KB bundle produced by bun build; bundles MCP SDK and dependencies, correctly externalises node:* built-ins, and includes the shebang.
README.md Desktop-app-first rewrite; install flow, config.toml override path, local dev instructions, and repo layout all updated to match the new bundling approach.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Developer edits server/*.js] --> B[bun run build]
    B --> C[dist/wrapper.js committed to repo]
    C --> D[PR opened]
    D --> E{paths filter matches?}
    E -- No --> F[build-check skipped]
    E -- Yes --> G[CI: bun install --frozen-lockfile]
    G --> H[CI: bun run build with bun-version latest]
    H --> I{git diff --exit-code dist/}
    I -- clean --> J[CI passes]
    I -- dirty --> K[CI fails: dist out of date]
    C --> L[Codex marketplace add]
    L --> M[node dist/wrapper.js spawned]
    M --> N[stdio MCP server]
    N --> O[discoverBaseUrl]
    O --> P[BrowserOS HTTP MCP endpoint]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Developer edits server/*.js] --> B[bun run build]
    B --> C[dist/wrapper.js committed to repo]
    C --> D[PR opened]
    D --> E{paths filter matches?}
    E -- No --> F[build-check skipped]
    E -- Yes --> G[CI: bun install --frozen-lockfile]
    G --> H[CI: bun run build with bun-version latest]
    H --> I{git diff --exit-code dist/}
    I -- clean --> J[CI passes]
    I -- dirty --> K[CI fails: dist out of date]
    C --> L[Codex marketplace add]
    L --> M[node dist/wrapper.js spawned]
    M --> N[stdio MCP server]
    N --> O[discoverBaseUrl]
    O --> P[BrowserOS HTTP MCP endpoint]
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
.github/workflows/build-check.yml:33-36
Unpinned Bun version can cause the dist-drift check to produce false failures. The build output from `bun build` is not guaranteed to be byte-for-byte identical across Bun minor/patch releases (codegen optimisations, comment formatting, etc.). When CI installs a newer `latest` than the developer used locally, `git diff --exit-code dist/` will report differences even though no source changed, making the CI invariant unreliable and requiring developers to re-run and re-commit `dist/wrapper.js` just to appease a Bun version bump.

```suggestion
      - name: Set up Bun
        uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
        with:
          bun-version: "1.2.15"
```

### Issue 2 of 2
.github/workflows/build-check.yml:9-15
The path filter triggers the check when `server/**`, `dist/**`, `package.json`, `bun.lock`, or the workflow file itself changes. A PR that edits only `server/` files will correctly trigger CI; a PR that edits both `server/` and `README.md` will also trigger (any path match is sufficient). The existing filter is correct; worth confirming this intent is documented for future contributors.

Reviews (1): Last reviewed commit: "feat(build): bundle wrapper into dist/wr..." | Re-trigger Greptile

Comment thread .github/workflows/build-check.yml Resolved
Greptile flagged 'bun-version: latest' as a source of false-positive
dist-drift failures: bun build output is not guaranteed byte-stable
across Bun releases, so CI on a newer Bun than the local toolchain
can fail the git diff --exit-code check with no actual source change.
Pin to the version we are building against locally.
@DaniAkash
Dani Akash (DaniAkash) merged commit 5deb6a5 into main Jun 19, 2026
1 check passed
@DaniAkash
Dani Akash (DaniAkash) deleted the feat/bundle-with-bunup branch June 19, 2026 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant