#!/usr/bin/env bash # Installs the locally built plugin into the host's plugin directory, so the # host picks up `npm run build` output without a reinstall from its URL. # # It copies rather than symlinks, and that is not a matter of taste: # `scanPlugins()` walks the plugins directory with # `readdirSync(dir, { withFileTypes: true })` and skips every entry whose # `entry.isDirectory()` is false — which is exactly what a symlink reports. A # symlinked plugin is silently invisible to the host. # Symlinking the files *inside* a real directory fails too: the asset route # compares the `realpath` of each asset against the `realpath` of the plugin # directory and 404s anything pointing outside it. # # Usage: ./scripts/link-plugin.sh [--remove] set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" DIR_NAME="cloudcli-plugin-taskwork" # last URL segment — how the host names it TARGET="${HOME}/.claude-code-ui/plugins/${DIR_NAME}" SOURCE="$ROOT/plugin-taskwork" # Everything the host needs at runtime: manifest, entry points, icon, and the # package.json it reads to decide whether to run a build. CONTENTS=(manifest.json package.json icon.svg build) if [ "${1:-}" = "--remove" ]; then [ -e "$TARGET" ] && rm -rf "$TARGET" && echo "removed $TARGET" || echo "nothing to remove" exit 0 fi [ -f "$SOURCE/manifest.json" ] || { echo "plugin-taskwork/manifest.json not found — run ./scripts/bootstrap.sh" >&2; exit 1; } [ -d "$SOURCE/build" ] || { echo "plugin-taskwork/build missing — run npm run build in plugin-taskwork/" >&2; exit 1; } if [ -L "$TARGET" ]; then echo "removing a stale symlink at $TARGET (the host cannot see symlinked plugins)" rm "$TARGET" fi mkdir -p "$TARGET" rm -rf "${TARGET:?}/build" for item in "${CONTENTS[@]}"; do cp -r "$SOURCE/$item" "$TARGET/" done # Intermediate tsc output is a build artefact, not part of the plugin. rm -rf "$TARGET/build/.tsc-client" echo "installed $TARGET (copy of $SOURCE)" cat <<'EOF' Re-run this script after every `npm run build`; a copy does not track the source. Client changes need the plugin surface reopened, server changes need the host restarted — the plugin's backend process is spawned once at host startup. EOF