Skip to content

refactor(crosscode): modularize CLI architecture and fix critical runtime bugs - #103

Merged
snhsish merged 10 commits into
snhsish:mainfrom
r2hu1:refactor/split-cli-and-fix-review
Sep 8, 2026
Merged

refactor(crosscode): modularize CLI architecture and fix critical runtime bugs#103
snhsish merged 10 commits into
snhsish:mainfrom
r2hu1:refactor/split-cli-and-fix-review

Conversation

@r2hu1

@r2hu1 r2hu1 commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR modularizes the crosscode CLI package, breaking down the monolithic 1,120-line cli.ts into isolated, single-responsibility modules under src/. It also improves process lifecycle management, tunnel fallback reliability, connection stability, and timing safety across the package.

🛠️ Key Changes & Fixes

1. Architecture & Modularization

Reduced cli.ts from 1,120 lines to ~270 lines by extracting concerns into dedicated modules:

  • src/util.ts — Common helpers for debug logging, credential censoring, dependency checking, and free port allocation.
  • src/log.ts — Log streams, rotation, and async buffer flushing on exit.
  • src/config.ts — Persistent configuration, project identity, and session tokens.
  • src/auth.ts — API key validation, login flows, and interactive user prompts.
  • src/proxy.ts — HTTP proxy server, SSE upstream bridging, and URL sanitization.
  • src/opencode.ts — Unified opencode serve process spawning and port detection, removing ~300 lines of duplicated code.
  • src/providers/ — Dedicated adapters for tunnel.ts, cloudflared.ts, and ngrok.ts.

2. Runtime & Stability Improvements

  • Tunnel fallback handling: Fixed an issue where falling back from the CrossCode tunnel to Cloudflare could fail because the fallback flag was set asynchronously after the branch condition had already been evaluated. Fallback is now handled explicitly through a callback.
  • Port detection crash handling: waitForOpencodePort now listens for process exit events and immediately rejects if opencode crashes during startup instead of hanging for 15 seconds.
  • Keypress listener cleanup: Stored the data listener reference so cleanupKeypress() can reliably detach event listeners on exit. Also added support for the h keypress for help.
  • WebSocket reconnection & heartbeat:
    • Prevented duplicate reconnection timers from being scheduled by both close and error events.
    • Added a 60-second client-side heartbeat timeout to detect and reconnect dead or silent connections.
    • Reset exponential backoff only after authentication succeeds through auth.ok.
  • Timing-safe authentication & Git route parsing:
    • Replaced standard string equality with timingSafeEqualStr for username authentication in git-handler.ts.
    • Made the commit hash route regex case-insensitive for hexadecimal characters.
  • Graceful shutdown: shutdown() now waits for log stream buffers to drain before calling process.exit(0).
  • Packaging & workspace bundling:
    • Added packages/crosscode/tsup.config.ts with noExternal: [/@crosscode\/shared/] so workspace dependencies are bundled directly for CLI runtime usage.
    • Added "type": "module" to packages/shared/package.json.
    • Moved @crosscode/shared to dependencies in packages/crosscode/package.json.

🧪 Verification

  • pnpm -r build completed successfully across all workspace packages and apps.
  • pnpm -r exec -- tsc --noEmit passed with 0 errors.
  • Verified direct CLI execution with node packages/crosscode/dist/cli.js --help.

r2hu1 added 10 commits September 7, 2026 22:57
The data listener was never removed on cleanup, leaking handlers
if onKeypress was called more than once. Now stores the reference
and removes it in cleanupKeypress. Also adds 'h' key support and
drops the unused emitKeypressEvents import.
If opencode crashed before a port was detected, the promise would
sit for 15s then silently resolve with a dead port. Now listens
for the exit event, clears the polling interval, and rejects
immediately so the caller can surface the error.
Both close and error events would fire on failure, scheduling two
reconnect timers and opening duplicate connections. Added a guard
so only close triggers reconnect.

Also adds a 60s heartbeat timeout to detect silently-dead
connections, and moves backoff reset to after auth.ok so a
rejected handshake doesn't hammer the server at 1s intervals.
…h regex

The username was compared with === while the password used
timingSafeEqual, leaking timing info. Now both use timingSafeEqual.

Also removed the encodeURI re-encoding on the pathname and made
the commit hash regex accept uppercase hex digits.
@crosscode/shared is imported at runtime in cli.ts, so it belongs
in dependencies not devDependencies. Removed the main field
pointing to dist/index.js which was never generated.
The 1120-line cli.ts was doing everything: logging, config, auth,
proxying, and three tunnel providers with near-identical spawn
logic copy-pasted across each.

Now split into:
  util.ts       - debug, censor, dep check, port alloc
  log.ts        - rotation, streams, async flush on shutdown
  config.ts     - types, read/save, project/session identity
  auth.ts       - login flow, API key validation, prompt input
  proxy.ts      - HTTP proxy server, URL sanitization
  opencode.ts   - shared opencode spawn+port-detect helper
  providers/    - one file per tunnel backend

cli.ts is now ~270 lines of arg parsing and orchestration.

This also fixes the tunnel-to-cloudflared fallback that never
actually started cloudflared (the flag was set async after the
if/else chain had already evaluated), and makes shutdown await
log buffer flush before calling process.exit.
@snhsish
snhsish merged commit 3f098b8 into snhsish:main Sep 8, 2026
@r2hu1
r2hu1 deleted the refactor/split-cli-and-fix-review branch September 8, 2026 15:29
@r2hu1
r2hu1 restored the refactor/split-cli-and-fix-review branch September 8, 2026 15:29
@r2hu1
r2hu1 deleted the refactor/split-cli-and-fix-review branch September 8, 2026 15:29
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.

2 participants