Conversation
Handle the current OpenCode 1.14.20 layout and install path so the patch works with binaries installed under ~/.opencode/bin as well as older ~/.local/bin setups. Tighten the live TPS estimate to track visible streamed output more closely instead of overcounting small chunks and reasoning updates.
There was a problem hiding this comment.
Pull request overview
Updates the OpenCode TPS Meter installer/patcher to support newer OpenCode releases (notably v1.14.20) by handling upstream source layout and event API changes, and by installing the launcher wrapper alongside the detected opencode binary.
Changes:
- Extend the auto-patcher to handle
installation/version.ts(in addition toinstallation/meta.ts) and support different TUI event sources. - Change install/uninstall behavior to place the wrapper next to the detected
opencodebinary and persist launcher locations inlauncher.env. - Refine live TPS estimation to better align with visible streamed output (avoid counting reasoning and tiny chunk boundaries).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
scripts/apply-opencode-tps-patch.mjs |
Adds version-file detection and adjusts event wiring + live TPS sampling logic for newer OpenCode TUI changes. |
install.sh |
Installs wrapper beside detected opencode, writes launcher.env, and updates fallback path behavior. |
uninstall.sh |
Restores launcher based on persisted launcher.env (or a legacy ~/.opencode/bin fallback). |
manifest.sh |
Updates the tested versions list to include 1.14.20. |
README.md |
Documents new wrapper placement behavior and updated tested versions. |
.github/workflows/validate.yml |
Adds 1.14.20 to the CI patch/typecheck matrix. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if [ -f "$LAUNCHER_STATE" ]; then | ||
| # shellcheck disable=SC1090 | ||
| . "$LAUNCHER_STATE" | ||
| elif [ -e "$HOME/.opencode/bin/opencode-stock" ]; then | ||
| BIN_DIR="$HOME/.opencode/bin" | ||
| WRAPPER="$BIN_DIR/opencode" | ||
| STOCK="$BIN_DIR/opencode-stock" |
There was a problem hiding this comment.
uninstall.sh sources launcher.env directly. If that file is corrupted/edited, the script will execute its contents and may end up moving/removing unintended paths via the subsequent mv/rm. Consider parsing the state file safely (not via .), and validating that WRAPPER/STOCK are within an expected bin directory before using them.
| DETECTED_VERSION="$(detect_installed_version)" | ||
| BIN_DIR="$(resolve_bin_dir)" | ||
| WRAPPER="$BIN_DIR/opencode" | ||
| STOCK="$BIN_DIR/opencode-stock" |
There was a problem hiding this comment.
detect_installed_version is called before BIN_DIR/WRAPPER/STOCK are initialized (they’re still empty strings). This means the EXISTING_OPENCODE != "$WRAPPER" guard will always pass, so a previously-installed TPS wrapper can be treated as “non-wrapper” and used for version detection, potentially failing (--version may error if the source dir is missing) or selecting the wrong REQUESTED_VERSION. Initialize BIN_DIR/WRAPPER/STOCK first (after EXISTING_OPENCODE is known), then call detect_installed_version so wrapper-vs-stock detection works as intended.
| DETECTED_VERSION="$(detect_installed_version)" | |
| BIN_DIR="$(resolve_bin_dir)" | |
| WRAPPER="$BIN_DIR/opencode" | |
| STOCK="$BIN_DIR/opencode-stock" | |
| BIN_DIR="$(resolve_bin_dir)" | |
| WRAPPER="$BIN_DIR/opencode" | |
| STOCK="$BIN_DIR/opencode-stock" | |
| DETECTED_VERSION="$(detect_installed_version)" |
| cat > "$LAUNCHER_STATE" <<EOF | ||
| BIN_DIR='$BIN_DIR' | ||
| WRAPPER='$WRAPPER' | ||
| STOCK='$STOCK' | ||
| EOF |
There was a problem hiding this comment.
launcher.env is being written and later sourced (. "$LAUNCHER_STATE"). Because it’s executed as shell code, a corrupted or manually edited state file could cause arbitrary code execution and/or make uninstall.sh move/remove unexpected paths. Consider storing state in a non-executable format (e.g., key=value lines parsed safely) and validating that WRAPPER/STOCK resolve under an expected bin dir before using them.
| @@ -135,7 +165,7 @@ cat > "$WRAPPER" <<'WRAPEOF' | |||
| #!/bin/zsh | |||
| set -euo pipefail | |||
| SOURCE_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/opencode-tps-meter/current/packages/opencode" | |||
| FALLBACK="$HOME/.local/bin/opencode-stock" | |||
| FALLBACK="__STOCK_PATH__" | |||
| if [ ! -d "$SOURCE_DIR" ]; then | |||
| if [ -x "$FALLBACK" ]; then | |||
| exec "$FALLBACK" "$@" | |||
| @@ -148,13 +178,15 @@ export OPENCODE_LAUNCH_CWD="$ORIG_PWD" | |||
| exec "__BUN_BIN__" --cwd "$SOURCE_DIR" --conditions=browser ./src/index.ts "$@" | |||
| WRAPEOF | |||
| perl -0pi -e 's|__BUN_BIN__|'"$BUN_BIN"'|g' "$WRAPPER" | |||
| perl -0pi -e 's|__STOCK_PATH__|'"$STOCK"'|g' "$WRAPPER" | |||
| chmod +x "$WRAPPER" | |||
There was a problem hiding this comment.
The perl ... s|__EXISTING_OPENCODE__|...| / __BUN_BIN__ / __STOCK_PATH__ substitutions interpolate unescaped paths into a Perl replacement string. If any of these paths contain Perl metacharacters (e.g., |, \, $, @) the generated wrapper/stock script can be corrupted. Use a safer replacement approach (e.g., choose a delimiter you escape, or pass the value via an env var and read it inside Perl, or generate the wrapper with proper shell escaping rather than string substitution).
| if (!source.includes("function estimateStreamTokens(delta: string)")) { | ||
| const helpers = ` | ||
|
|
There was a problem hiding this comment.
This includes("function estimateStreamTokens(delta: string)") guard no longer matches the helper you now inject (estimateStreamTokens(chars: number)). If the patcher is run twice on the same source tree, it will re-insert the helper block and duplicate the injected code. Update the idempotency check to look for the new helper signature (or a stable marker comment) so re-running the patcher is safe.
| if (!source.includes("function estimateStreamTokens(delta: string)")) { | |
| const helpers = ` | |
| if (!source.includes("// opencode-tps-patch helpers")) { | |
| const helpers = ` | |
| // opencode-tps-patch helpers |
|
|
||
| function patchPromptIndexTsx(file) { | ||
| let source = read(file) | ||
| const eventSource = source.includes("const event = useEvent()") ? "event" : "sdk.event" |
There was a problem hiding this comment.
eventSource selection relies on an exact string match ("const event = useEvent()"). Minor upstream formatting changes (spacing, let vs const, etc.) will flip this to sdk.event and break the injected listener wiring. Prefer a more resilient detection (e.g., regex for useEvent\(\) with an event binding, or checking for useEvent import/usage patterns) to reduce patch fragility across upstream versions.
| const eventSource = source.includes("const event = useEvent()") ? "event" : "sdk.event" | |
| const hasUseEventBinding = /\b(?:const|let|var)\s+event\s*=\s*useEvent\s*\(\s*\)\s*;?/.test(source) | |
| const eventSource = hasUseEventBinding ? "event" : "sdk.event" |
Summary
installation/version.tslayout and the newer typed TUI event source used byv1.14.20opencodebinary so current installs under~/.opencode/binare actually patched, and persist launcher metadata so uninstall can restore the right locationValidation
bun ./scripts/apply-opencode-tps-patch.mjs <v1.14.20 clone> 1.14.20(cd <v1.14.20 clone>/packages/opencode && bun install --frozen-lockfile && bun run typecheck)bun ./scripts/apply-opencode-tps-patch.mjs <v1.4.1 clone> 1.4.1install.shrun against a simulated~/.opencode/bin/opencodesetup~/.opencode/bin/opencode, verified withopencode --version