diff --git a/.gitignore b/.gitignore index cb9a35f..e7ce2ea 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,13 @@ build/bin/ dist/ +# ── VSCode extensions: platform-specific build artifacts (never commit) ──── +# The Go bridge is compiled per-host by vscode/build.sh and staged into each +# extension's bin/ — committing it ships a binary for the wrong OS/arch. +vscode/bin/ +vscode/dist/ +vscode/*/bin/ + # ── Build platform resources (tracked — icons, plists, manifests) ───────── # build/darwin/, build/windows/, build/linux/ are tracked for icons & metadata diff --git a/vscode/bin/nexus-vscode-bridge b/vscode/bin/nexus-vscode-bridge deleted file mode 100755 index e52950b..0000000 Binary files a/vscode/bin/nexus-vscode-bridge and /dev/null differ diff --git a/vscode/leviathan/bin/nexus-vscode-bridge b/vscode/leviathan/bin/nexus-vscode-bridge deleted file mode 100755 index e52950b..0000000 Binary files a/vscode/leviathan/bin/nexus-vscode-bridge and /dev/null differ diff --git a/vscode/leviathan/extension.js b/vscode/leviathan/extension.js index e6c5d88..84e5622 100644 --- a/vscode/leviathan/extension.js +++ b/vscode/leviathan/extension.js @@ -69,6 +69,10 @@ class Bridge { if (!fs.existsSync(bridgePath)) { throw new Error(`Bridge executable not found: ${bridgePath}. Run vscode/build.sh first.`); } + // VSIX extraction does not always preserve the executable bit, so ensure it. + if (process.platform !== 'win32') { + try { fs.chmodSync(bridgePath, 0o755); } catch (_) { /* best effort */ } + } this.proc = cp.spawn(bridgePath, ['-listen', '127.0.0.1:0'], { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true @@ -776,12 +780,19 @@ class ToolPanel { h1 { margin: 0; font-size: 15px; } .title-wrap { min-width: 0; } .file-state { color: var(--vscode-descriptionForeground); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + header button, .actions button { border: 1px solid var(--vscode-button-border, transparent); background: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground); } + header button:hover, .actions button:hover { background: var(--vscode-button-secondaryHoverBackground, var(--vscode-button-secondaryBackground)); } main { display: grid; grid-template-columns: minmax(260px, 360px) 1fr; height: calc(100vh - 58px); } - textarea { width: calc(100% - 24px); height: calc(100% - 66px); margin: 12px; resize: none; color: var(--vscode-input-foreground); background: var(--vscode-input-background); border: 1px solid var(--vscode-input-border); font-family: var(--vscode-editor-font-family); } - .left { border-right: 1px solid var(--vscode-panel-border); } - .actions { display: flex; gap: 6px; padding: 0 12px 12px; } - button { padding: 5px 10px; } + .left { border-right: 1px solid var(--vscode-panel-border); display: flex; flex-direction: column; min-height: 0; min-width: 0; } + .params-head { padding: 12px 12px 6px; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--vscode-descriptionForeground); } + textarea { flex: 1; margin: 0 12px; resize: none; color: var(--vscode-input-foreground); background: var(--vscode-input-background); border: 1px solid var(--vscode-input-border); padding: 8px; box-sizing: border-box; font-family: var(--vscode-editor-font-family, monospace); font-size: 12px; } + textarea:focus { outline: none; border-color: var(--vscode-focusBorder); } + .actions { display: flex; gap: 6px; padding: 10px 12px; } + button { padding: 5px 10px; cursor: pointer; } + button.primary { background: var(--vscode-button-background); color: var(--vscode-button-foreground); border-color: var(--vscode-button-border, transparent); } + button.primary:hover { background: var(--vscode-button-hoverBackground, var(--vscode-button-background)); } #out { margin: 0; padding: 12px; overflow: auto; height: calc(100% - 24px); box-sizing: border-box; } + .out-empty { color: var(--vscode-descriptionForeground); } table { border-collapse: collapse; width: 100%; margin: 0 0 12px; } th, td { border: 1px solid var(--vscode-panel-border); padding: 4px 6px; text-align: left; vertical-align: top; font-family: var(--vscode-editor-font-family); font-size: 12px; } th { background: var(--vscode-editorWidget-background); position: sticky; top: 0; } @@ -800,15 +811,20 @@ class ToolPanel {
+
Parameters (JSON)
-
+
-
Run the tool to see results.
+
Adjust the parameters and select Run to see results.
@@ -699,13 +798,18 @@ class RawPanel { .send { color: var(--vscode-terminal-ansiGreen); } .recv { color: var(--vscode-terminal-ansiCyan); } .hex { color: var(--vscode-descriptionForeground); } + button { border: 1px solid var(--vscode-button-border, transparent); background: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground); cursor: pointer; } + button:hover { background: var(--vscode-button-secondaryHoverBackground, var(--vscode-button-secondaryBackground)); } + button.primary { background: var(--vscode-button-background); color: var(--vscode-button-foreground); } + button.primary:hover { background: var(--vscode-button-hoverBackground, var(--vscode-button-background)); } + input:focus, select:focus, textarea:focus { outline: none; border-color: var(--vscode-focusBorder); }
- + connecting @@ -858,6 +962,12 @@ class GitHubPanel { .repo { border: 1px solid var(--vscode-panel-border); padding: 8px; } .repo h3 { margin: 0 0 4px; font-size: 14px; } .meta { color: var(--vscode-descriptionForeground); font-size: 12px; } + button { border: 1px solid var(--vscode-button-border, transparent); background: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground); cursor: pointer; } + button:hover { background: var(--vscode-button-secondaryHoverBackground, var(--vscode-button-secondaryBackground)); } + button.primary { background: var(--vscode-button-background); color: var(--vscode-button-foreground); } + button.primary:hover { background: var(--vscode-button-hoverBackground, var(--vscode-button-background)); } + input:focus, select:focus { outline: none; border-color: var(--vscode-focusBorder); } + .repo p { display: flex; gap: 6px; margin: 8px 0 0; } @@ -951,13 +1061,18 @@ class IRCPanel { #target { width: 140px; } #message { flex: 1; } #log { height: calc(100vh - 92px); overflow: auto; padding: 8px; font-family: var(--vscode-editor-font-family), monospace; white-space: pre-wrap; } - .status { color: var(--vscode-descriptionForeground); } + .status { color: var(--vscode-descriptionForeground); align-self: center; } + button { border: 1px solid var(--vscode-button-border, transparent); background: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground); padding: 5px 10px; cursor: pointer; } + button:hover { background: var(--vscode-button-secondaryHoverBackground, var(--vscode-button-secondaryBackground)); } + button.primary { background: var(--vscode-button-background); color: var(--vscode-button-foreground); } + button.primary:hover { background: var(--vscode-button-hoverBackground, var(--vscode-button-background)); } + input:focus { outline: none; border-color: var(--vscode-focusBorder); }
connecting
- + diff --git a/vscode/nexus/media/nexus.svg b/vscode/nexus/media/nexus.svg index ad12f7e..a92f1b6 100644 --- a/vscode/nexus/media/nexus.svg +++ b/vscode/nexus/media/nexus.svg @@ -1,9 +1,5 @@ - - - - - - - - + + + + diff --git a/vscode/package-all.sh b/vscode/package-all.sh new file mode 100755 index 0000000..6699b47 --- /dev/null +++ b/vscode/package-all.sh @@ -0,0 +1,190 @@ +#!/usr/bin/env bash +# +# Build platform-specific VSIX packages for the Nexus and Leviathan VSCode +# extensions, covering Windows, macOS and Linux on both x64 and arm64. +# +# Each extension embeds a Go "bridge" binary that is OS/arch specific, so we +# produce one VSIX per target using VSCode's platform-specific extension +# mechanism (`vsce package --target `). VSCode then installs the VSIX +# matching the user's platform automatically (e.g. from the Marketplace, or via +# `code --install-extension .vsix`). +# +# The bridge is pure Go (CGO_ENABLED=0), so every target cross-compiles from any +# host with just the Go toolchain — no C compilers or per-OS toolchains needed. +# +# Usage: +# vscode/package-all.sh # build every target +# vscode/package-all.sh linux-x64 ... # build only the named vsce target(s) +# +# Optional macOS code signing + notarization (darwin targets only): +# Runs only on a macOS host with Xcode command line tools, and only when the +# relevant env vars are set — otherwise it is skipped with a warning and the +# darwin VSIXs are produced unsigned (fine for local use, blocked by Gatekeeper +# when distributed to other machines). +# +# MACOS_SIGN_IDENTITY "Developer ID Application: Name (TEAMID)" — enables signing. +# +# Then, to also notarize, provide EITHER a stored notarytool profile: +# MACOS_NOTARY_PROFILE profile name from `xcrun notarytool store-credentials` +# OR an Apple ID + app-specific password: +# MACOS_NOTARY_APPLE_ID Apple ID email +# MACOS_NOTARY_TEAM_ID Apple Developer team id +# MACOS_NOTARY_PASSWORD app-specific password +# +# Example: +# MACOS_SIGN_IDENTITY="Developer ID Application: Andy Dixon (AB12CD34EF)" \ +# MACOS_NOTARY_PROFILE="nexus-notary" \ +# vscode/package-all.sh darwin-x64 darwin-arm64 +# +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +VSCODE_DIR="$ROOT_DIR/vscode" +OUT_DIR="$VSCODE_DIR/dist" +EXTENSIONS=(nexus leviathan) + +# vsce target -> GOOS/GOARCH +ALL_TARGETS=( + "win32-x64:windows/amd64" + "win32-arm64:windows/arm64" + "darwin-x64:darwin/amd64" + "darwin-arm64:darwin/arm64" + "linux-x64:linux/amd64" + "linux-arm64:linux/arm64" +) + +# Optional positional filter: only build the vsce targets passed as arguments. +SELECTED=("$@") +target_selected() { + [ ${#SELECTED[@]} -eq 0 ] && return 0 + local want="$1" + for t in "${SELECTED[@]}"; do [ "$t" = "$want" ] && return 0; done + return 1 +} + +run_vsce() { + if command -v vsce >/dev/null 2>&1; then + vsce "$@" + else + npx --yes @vscode/vsce "$@" + fi +} + +# Codesign (hardened runtime) and optionally notarize a single darwin bridge +# binary. Opt-in via env vars and skipped with a warning when it can't run, so +# the cross-platform build still succeeds on non-macOS hosts. Signing the staged +# binary before it is copied into each extension means both VSIXs inherit the +# signature (and notarization, which is keyed to the binary's hash). +macos_sign() { + local bin="$1" + + if [ -z "${MACOS_SIGN_IDENTITY:-}" ]; then + echo "[vscode] (skip signing: MACOS_SIGN_IDENTITY not set — shipping unsigned)" + return 0 + fi + if [ "$(uname -s)" != "Darwin" ] || ! command -v codesign >/dev/null 2>&1; then + echo "[vscode] WARNING: cannot sign darwin bridge here (needs macOS + Xcode tools); shipping unsigned" + return 0 + fi + + echo "[vscode] codesign ($MACOS_SIGN_IDENTITY)" + codesign --force --options runtime --timestamp --sign "$MACOS_SIGN_IDENTITY" "$bin" + codesign --verify --strict --verbose=2 "$bin" + + # Notarize only when credentials are provided. + local notary_args=() + if [ -n "${MACOS_NOTARY_PROFILE:-}" ]; then + notary_args=(--keychain-profile "$MACOS_NOTARY_PROFILE") + elif [ -n "${MACOS_NOTARY_APPLE_ID:-}" ] && [ -n "${MACOS_NOTARY_TEAM_ID:-}" ] && [ -n "${MACOS_NOTARY_PASSWORD:-}" ]; then + notary_args=(--apple-id "$MACOS_NOTARY_APPLE_ID" --team-id "$MACOS_NOTARY_TEAM_ID" --password "$MACOS_NOTARY_PASSWORD") + else + echo "[vscode] (skip notarization: no notary credentials set)" + return 0 + fi + if ! command -v xcrun >/dev/null 2>&1; then + echo "[vscode] WARNING: xcrun not found; cannot notarize" + return 0 + fi + + echo "[vscode] notarize (submitting to Apple, waiting for result…)" + local zip="${bin}.notarize.zip" + # notarytool requires an archive; a bare Mach-O binary cannot be submitted directly. + ditto -c -k --keepParent "$bin" "$zip" + xcrun notarytool submit "$zip" "${notary_args[@]}" --wait + rm -f "$zip" + # A standalone executable cannot be stapled (stapler only supports app/dmg/pkg); + # Gatekeeper verifies the notarization online via the now-registered hash, which + # matches the exact bytes embedded in every VSIX. + echo "[vscode] notarized (online check; standalone binaries are not stapleable)" +} + +echo "[vscode] Building frontend assets (platform independent)" +if [ ! -d "$ROOT_DIR/frontend/node_modules" ]; then + npm --prefix "$ROOT_DIR/frontend" install +fi +npm --prefix "$ROOT_DIR/frontend" run build + +echo "[vscode] Staging shared media assets" +cp "$ROOT_DIR/frontend/dist/src/js/lib/xterm.min.js" "$VSCODE_DIR/nexus/media/xterm.min.js" +cp "$ROOT_DIR/frontend/dist/src/js/lib/xterm-addon-fit.min.js" "$VSCODE_DIR/nexus/media/xterm-addon-fit.min.js" +cp "$ROOT_DIR/frontend/dist/src/css/lib/xterm.css" "$VSCODE_DIR/nexus/media/xterm.css" +cp "$ROOT_DIR/frontend/dist/src/js/lib/novnc.min.js" "$VSCODE_DIR/nexus/media/novnc.min.js" + +rm -rf "$OUT_DIR" +mkdir -p "$OUT_DIR" + +cd "$ROOT_DIR" +VERSION="$(node -p "require('$VSCODE_DIR/nexus/package.json').version")" + +for entry in "${ALL_TARGETS[@]}"; do + VSCE_TARGET="${entry%%:*}" + GOPLATFORM="${entry##*:}" + GOOS="${GOPLATFORM%%/*}" + GOARCH="${GOPLATFORM##*/}" + + if ! target_selected "$VSCE_TARGET"; then + continue + fi + + BIN_NAME="nexus-vscode-bridge" + [ "$GOOS" = "windows" ] && BIN_NAME="nexus-vscode-bridge.exe" + + echo "[vscode] Building bridge for $VSCE_TARGET ($GOOS/$GOARCH)" + STAGE_BIN="$VSCODE_DIR/bin/$VSCE_TARGET/$BIN_NAME" + mkdir -p "$(dirname "$STAGE_BIN")" + GOOS="$GOOS" GOARCH="$GOARCH" CGO_ENABLED=0 \ + go build -trimpath -o "$STAGE_BIN" ./cmd/vscode-bridge + + # Sign + notarize macOS bridges before they are copied into the extensions. + if [ "$GOOS" = "darwin" ]; then + macos_sign "$STAGE_BIN" + fi + + for EXT in "${EXTENSIONS[@]}"; do + EXT_BIN_DIR="$VSCODE_DIR/$EXT/bin" + # Ship exactly one binary per VSIX so the package can't carry a wrong-arch + # (or wrong-OS) bridge alongside the right one. + rm -rf "$EXT_BIN_DIR" + mkdir -p "$EXT_BIN_DIR" + cp "$STAGE_BIN" "$EXT_BIN_DIR/$BIN_NAME" + chmod 0755 "$EXT_BIN_DIR/$BIN_NAME" + + OUT_VSIX="$OUT_DIR/$EXT-$VERSION-$VSCE_TARGET.vsix" + ( cd "$VSCODE_DIR/$EXT" && run_vsce package --target "$VSCE_TARGET" \ + --no-dependencies --allow-missing-repository -o "$OUT_VSIX" >/dev/null ) + echo "[vscode] wrote $(basename "$OUT_VSIX")" + done +done + +# Leave the working tree with a host-native bridge staged so a plain +# `code --install-extension` of an unpacked extension still works locally. +HOST_OS="$(go env GOOS)"; HOST_ARCH="$(go env GOARCH)" +HOST_BIN="nexus-vscode-bridge"; [ "$HOST_OS" = "windows" ] && HOST_BIN="nexus-vscode-bridge.exe" +for EXT in "${EXTENSIONS[@]}"; do + rm -rf "$VSCODE_DIR/$EXT/bin"; mkdir -p "$VSCODE_DIR/$EXT/bin" + GOOS="$HOST_OS" GOARCH="$HOST_ARCH" CGO_ENABLED=0 \ + go build -trimpath -o "$VSCODE_DIR/$EXT/bin/$HOST_BIN" ./cmd/vscode-bridge +done + +echo "[vscode] Done. Packages in $OUT_DIR:" +ls -1 "$OUT_DIR"