Skip to content

feat: ship an installable crosscode npm package - #30

Merged
amsultan2010 merged 1 commit into
mainfrom
alexandria
Aug 4, 2026
Merged

feat: ship an installable crosscode npm package#30
amsultan2010 merged 1 commit into
mainfrom
alexandria

Conversation

@amsultan2010

Copy link
Copy Markdown
Owner

What this changes

Bundles the CLI, MCP server, and daemon into dist/ with esbuild (scripts/build.mjs) and makes the root manifest a publishable crosscode package exposing the crosscode and crosscode-mcp bins, replacing a state where tsc --noEmit emitted nothing, every package was private, and both bin entries pointed at TypeScript sources Node cannot execute. It also fixes two failures that only appear after install: the MCP bootstrap hardcoded the monorepo tsx + apps/daemon/src/main.ts layout and swallowed the failure via stdio: "ignore", so an installed package reported DAEMON_UNAVAILABLE forever with no diagnostic, and the CLI's main-module guard compared import.meta.url against argv[1] as given, so the symlinked installed binary parsed nothing and exited silently. resolveDaemonLaunch now prefers the bundled daemon run by process.execPath, keeps an existence-checked monorepo fallback so pnpm mcp still works, and surfaces a message naming what it tried. Touches the CLI, MCP server, build tooling, and docs.

Why

Nothing else in this repo turns it into something a stranger can install and run, and bundling sidesteps the cross-package relative imports (../../daemon/src/client.js) that escape any per-package tarball root. The build fails if anything from node_modules gets inlined, so a bundled-but-undeclared dependency can no longer pass in the monorepo and break only once the tarball is installed elsewhere.

Testing

Verified from a packed tarball installed outside this repo on Node 24 alone: --help, init --json, status --json, and commands --json all work; an MCP initialize + tools/list handshake returns 22 tools; the bootstrap starts a real daemon (node .../dist/daemon.js) whose eventSequence advances 0 to 2 after an edit; and both spawn-failure modes produce a message naming the paths tried. Four new bootstrap.test.ts cases cover the launch resolution, pnpm audit --audit-level high is clean, and pnpm install --frozen-lockfile is in sync. One intermediate full-suite run hit the pre-existing wall-clock flake in ws-client.test.ts > reconnects with increasing backoff; it passed standalone, on a stashed clean tree, and in the final run.

  • pnpm build passes
  • pnpm test passes - 282 passed, 19 skipped (278 baseline + 4 new)
  • pnpm test:postgres passes - 16 passed

Security / trust-boundary impact

None. No change to auth, RLS, checkpoint/materialization safety, or excluded paths. The daemon is now spawned from a path derived from the installed module and checked for existence, rather than a ../../.. guess, which narrows rather than widens what gets executed.

Related issues

Implements plan 01 (distribution / npm package). npm publish is deliberately not run - that is the owner's call, and docs/install-prompt.md, docs/mcp-clients.md, and the site's install snippet still describe the clone-and-tsx path, which stays correct until the package is live and should be switched over together.

🤖 Generated with Claude Code

Nothing in this repo compiled: the root `build` script was `tsc --noEmit`, every
package was private, and both declared `bin` entries pointed at TypeScript sources
that plain Node cannot execute. Bundle three entrypoints with esbuild instead --
`crosscode`, `crosscode-mcp`, and the daemon the MCP server spawns -- and publish
one package from the root manifest. Bundling also sidesteps the cross-package
relative imports (`../../daemon/src/client.js`) that escape any per-package tarball
root, so no import rewriting is needed to ship.

scripts/build.mjs fails the build if anything from node_modules is inlined: a
bundled-but-undeclared dependency works in the monorepo and breaks only once the
tarball is installed somewhere else.

Also fixes two things that only fail after install:

- The MCP bootstrap hardcoded the monorepo layout (`node_modules/.bin/tsx` plus
  `apps/daemon/src/main.ts`) and spawned it with `stdio: "ignore"`, so in an
  installed package it failed silently and the client saw DAEMON_UNAVAILABLE
  forever. resolveDaemonLaunch now prefers the bundled daemon run by
  process.execPath, keeps an existence-checked monorepo fallback for `pnpm mcp`,
  and surfaces a message naming what was tried.
- The CLI's main-module guard compared import.meta.url against argv[1] as given.
  npm installs a bin as a symlink, so the installed `crosscode` binary parsed
  nothing and exited silently. Compare against the realpath.

Verified from a tarball installed outside this repo on Node 24 alone: --help,
init, status, and commands all work; an MCP initialize + tools/list handshake
returns 22 tools; and the bootstrap starts a real daemon whose eventSequence
advances after an edit.

Publishing is deliberately left to the owner -- the docs still describe the
clone-and-tsx install path, which stays correct until the package is live.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
crosscode Ready Ready Preview Aug 4, 2026 6:14am

@amsultan2010
amsultan2010 merged commit 991132a into main Aug 4, 2026
4 checks passed
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