From ce664d72da6b55fb5687fa6dfc15f02f4ffbf3de Mon Sep 17 00:00:00 2001 From: Staninna <26458805+Staninna@users.noreply.github.com> Date: Thu, 2 Jul 2026 19:32:22 +0200 Subject: [PATCH 1/3] feat(install): add --minimal binary-only install mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install.sh installs the plannotator binary and then writes a large amount of extra state: the sem sidecar, the agent-terminal runtime, and per-agent skills, hooks, slash commands, and config for Claude, Codex, OpenCode, Gemini, and Kiro. There is no way to get just the binary — even the --no-extras path still writes ~/.claude skills and OpenCode commands. Add a --minimal flag (aliased --binary-only) plus a PLANNOTATOR_MINIMAL env var for `curl | bash` runs. Minimal mode installs only the binary to ~/.local/bin, prints PATH advice, and exits before any sidecar download, agent integration, skill checkout, config write, cache clear, or cleanup migration. Precedence: flag > env var > default (off). The PATH-advice block is extracted into print_path_advice() so both the minimal early exit and the normal flow reuse it. Closes #977 --- README.md | 6 ++++ scripts/install.sh | 76 +++++++++++++++++++++++++++++++++-------- scripts/install.test.ts | 52 ++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 6359b919f..54125b512 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,12 @@ curl -fsSL https://plannotator.ai/install.sh | bash irm https://plannotator.ai/install.ps1 | iex ``` +Want just the binary and nothing else? Pass `--minimal` (or export `PLANNOTATOR_MINIMAL=1`) to install only the `plannotator` binary to `~/.local/bin`, skipping every skill, hook, slash command, and per-agent config: + +```bash +curl -fsSL https://plannotator.ai/install.sh | bash -s -- --minimal +``` + Then finish the step for your agent: | Agent | After the installer | Details | diff --git a/scripts/install.sh b/scripts/install.sh index f3aaa97a9..fd01bd050 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -40,12 +40,18 @@ EXTRAS_FLAG="" MODEL_INVOCABLE_FLAG="" NON_INTERACTIVE=0 RECONFIGURE=0 +# Binary-only mode. Installs just the plannotator binary (to $INSTALL_DIR) and +# nothing else — no sem sidecar, no agent-terminal runtime, no skills, hooks, +# slash commands, or per-agent config (Claude, Codex, OpenCode, Gemini, Kiro). +# -1 = flag not set (fall through to env var); 0 = off; 1 = on. Resolved after +# arg parsing so PLANNOTATOR_MINIMAL can enable it for `curl | bash` runs. +MINIMAL_FLAG=-1 usage() { cat <<'USAGE' Usage: install.sh [--version ] [--verify-attestation | --skip-attestation] [--extras | --no-extras] [--model-invocable |none] - [--non-interactive] [--reconfigure] [--help] + [--minimal] [--non-interactive] [--reconfigure] [--help] install.sh Options: @@ -63,6 +69,13 @@ Options: --model-invocable Comma-separated skill names to make model-invocable (e.g. plannotator-review,plannotator-compound), or "none". Skills are user-invoked-only by default. + --minimal Install only the plannotator binary. Skips the sem + semantic-diff sidecar, the agent-terminal runtime, and + every per-agent integration (skills, hooks, slash + commands, and config for Claude, Codex, OpenCode, + Gemini, and Kiro). Nothing is written outside + $HOME/.local/bin. Also enabled by exporting + PLANNOTATOR_MINIMAL=1. --non-interactive Never prompt, even in a terminal. Uses flags, then saved answers from a previous run, then the defaults (no extras, nothing model-invocable). @@ -189,6 +202,10 @@ while [ $# -gt 0 ]; do RECONFIGURE=1 shift ;; + --minimal|--binary-only) + MINIMAL_FLAG=1 + shift + ;; -h|--help) usage exit 0 @@ -214,6 +231,17 @@ while [ $# -gt 0 ]; do esac done +# Resolve binary-only mode. Precedence: --minimal flag > PLANNOTATOR_MINIMAL +# env var > default (off). The env var lets `curl ... | bash` runs opt in +# without a flag, matching how PLANNOTATOR_SKIP_SEM_INSTALL et al. work. +minimal=0 +case "${PLANNOTATOR_MINIMAL:-}" in + 1|true|yes|TRUE|YES|True|Yes) minimal=1 ;; +esac +if [ "$MINIMAL_FLAG" -ne -1 ]; then + minimal="$MINIMAL_FLAG" +fi + case "$(uname -s)" in Darwin) os="darwin" ;; Linux) os="linux" ;; @@ -379,6 +407,37 @@ chmod +x "$INSTALL_DIR/plannotator" echo "" echo "plannotator ${latest_tag} installed to ${INSTALL_DIR}/plannotator" +# Print the PATH-setup hint if $INSTALL_DIR isn't already on PATH. Extracted so +# both the normal flow and the --minimal early exit below can reuse it. +print_path_advice() { + if ! echo "$PATH" | tr ':' '\n' | grep -qx "$INSTALL_DIR"; then + echo "" + echo "${INSTALL_DIR} is not in your PATH. Add it with:" + echo "" + + case "$SHELL" in + */zsh) shell_config="~/.zshrc" ;; + */bash) shell_config="~/.bashrc" ;; + *) shell_config="your shell config" ;; + esac + + echo " echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ${shell_config}" + echo " source ${shell_config}" + fi +} + +# Binary-only mode stops here: the binary is installed, so print PATH advice and +# exit before any sidecar download, agent integration, skill checkout, config +# write, cache clear, or cleanup migration runs. Nothing outside $INSTALL_DIR is +# touched. See the MINIMAL_FLAG / PLANNOTATOR_MINIMAL resolution near the top. +if [ "$minimal" -eq 1 ]; then + print_path_advice + echo "" + echo "Minimal install complete — only the plannotator binary was installed." + echo "No skills, hooks, agent integrations, or config files were written." + exit 0 +fi + sem_asset_for_platform() { case "$platform" in darwin-arm64) echo "sem-darwin-arm64.tar.gz" ;; @@ -496,20 +555,7 @@ install_agent_terminal_runtime() { install_sem_sidecar install_agent_terminal_runtime -if ! echo "$PATH" | tr ':' '\n' | grep -qx "$INSTALL_DIR"; then - echo "" - echo "${INSTALL_DIR} is not in your PATH. Add it with:" - echo "" - - case "$SHELL" in - */zsh) shell_config="~/.zshrc" ;; - */bash) shell_config="~/.bashrc" ;; - *) shell_config="your shell config" ;; - esac - - echo " echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ${shell_config}" - echo " source ${shell_config}" -fi +print_path_advice # --- Codex CLI / Desktop app support (only if Codex is installed or configured) --- # Codex stores config and state under $CODEX_HOME when set, falling back to diff --git a/scripts/install.test.ts b/scripts/install.test.ts index 2d95376d4..0c9a22fcd 100644 --- a/scripts/install.test.ts +++ b/scripts/install.test.ts @@ -284,6 +284,58 @@ describe("install.sh", () => { expect(script).toContain('GEMINI_POLICY_EOF'); expect(script).toContain('GEMINI_SETTINGS_EOF'); }); + + test("--minimal flag and PLANNOTATOR_MINIMAL env var are documented", () => { + // Usage text advertises the flag and the env-var opt-in for curl | bash. + expect(script).toContain("--minimal"); + expect(script).toContain("PLANNOTATOR_MINIMAL"); + // Accepts both --minimal and the --binary-only alias. + expect(script).toContain("--minimal|--binary-only)"); + }); + + test("minimal mode is resolved from flag with env-var fallback", () => { + // Flag wins over env var, which wins over the default (off). + expect(script).toContain("MINIMAL_FLAG=-1"); + expect(script).toContain('case "${PLANNOTATOR_MINIMAL:-}" in'); + expect(script).toContain('if [ "$MINIMAL_FLAG" -ne -1 ]; then'); + }); + + test("minimal mode exits after the binary install, before any extras", () => { + // The early exit must come AFTER the binary is moved into place but BEFORE + // the sidecar downloads, agent integrations, skill checkout, and config + // writes — that ordering is the whole point of #977. + const binaryInstalled = script.indexOf( + 'mv "$tmp_file" "$INSTALL_DIR/plannotator"', + ); + const minimalExit = script.indexOf('if [ "$minimal" -eq 1 ]; then'); + const semInstall = script.indexOf("install_sem_sidecar\n"); + const agentTerminal = script.indexOf("install_agent_terminal_runtime\n"); + const codexBlock = script.indexOf( + "# --- Codex CLI / Desktop app support", + ); + const skillsCheckout = script.indexOf( + "git clone --depth 1 --filter=blob:none --sparse", + ); + + expect(binaryInstalled).toBeGreaterThan(0); + expect(minimalExit).toBeGreaterThan(binaryInstalled); + // Everything the reporter called "trash" runs strictly after the exit gate. + expect(semInstall).toBeGreaterThan(minimalExit); + expect(agentTerminal).toBeGreaterThan(minimalExit); + expect(codexBlock).toBeGreaterThan(minimalExit); + expect(skillsCheckout).toBeGreaterThan(minimalExit); + // The gate really exits rather than falling through. + const gateBody = script.slice(minimalExit, minimalExit + 400); + expect(gateBody).toContain("exit 0"); + }); + + test("PATH advice is a reusable function shared by both paths", () => { + // Extracted so the minimal early exit and the normal flow both print it. + expect(script).toContain("print_path_advice() {"); + // Called exactly once inside the minimal gate and once in the normal flow. + const calls = script.match(/^\s*print_path_advice$/gm) ?? []; + expect(calls.length).toBe(2); + }); }); describe("install.ps1", () => { From c112e39d19a9df082076d917571f80f79e18670b Mon Sep 17 00:00:00 2001 From: Staninna <26458805+Staninna@users.noreply.github.com> Date: Thu, 2 Jul 2026 19:55:21 +0200 Subject: [PATCH 2/3] feat(install): mirror minimal mode to ps1/cmd, add --no-minimal, docs Extend the binary-only install mode to full cross-installer parity and address the Copilot review on #989. install.sh: - Add --no-minimal (MINIMAL_FLAG=0) so a CLI flag can override PLANNOTATOR_MINIMAL=1 in both directions; the -1/0/1 comment is now accurate. --minimal/--no-minimal are mutually exclusive. - Document the --binary-only alias and soften the absolute "nothing written outside ..." wording to "no persistent state" (minimal mode still uses a temp download file and may read the config dir). install.ps1 / install.cmd: - Add -Minimal (alias BinaryOnly)/-NoMinimal and --minimal/--binary-only/ --no-minimal with PLANNOTATOR_MINIMAL resolution, mirroring install.sh. - Extract PATH advice into Show-PathAdvice / :PrintPathAdvice and reuse it in both the minimal early exit and the normal flow. - Early-exit after the binary is placed, before the sidecar/agent/skill/ config work. Tests: add minimal-mode assertions to the install.ps1 and install.cmd describe blocks plus a shared-behavior test asserting all three installers support it; extend the sh tests for --no-minimal. Docs: document --minimal / PLANNOTATOR_MINIMAL in the installation guide (noting minimal mode needs no git) and add PLANNOTATOR_MINIMAL (and backfill PLANNOTATOR_SKIP_SEM_INSTALL) to the env-var registries. --- AGENTS.md | 2 + .../docs/getting-started/installation.md | 21 +++++ .../docs/reference/environment-variables.md | 2 + scripts/install.cmd | 90 +++++++++++++++---- scripts/install.ps1 | 55 ++++++++++-- scripts/install.sh | 56 ++++++++---- scripts/install.test.ts | 87 +++++++++++++++++- 7 files changed, 270 insertions(+), 43 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5c5323ca0..bb1424185 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -142,6 +142,8 @@ claude --plugin-dir ./apps/hook | `PLANNOTATOR_GLIMPSE_HEIGHT` | Height in pixels for the Glimpse native window. Default: `900`. | | `PLANNOTATOR_VERIFY_ATTESTATION` | **Read by the install scripts only**, not by the runtime binary. Set to `1` / `true` to have `scripts/install.sh` / `install.ps1` / `install.cmd` run `gh attestation verify` on every install. Off by default. Can also be set persistently via `~/.plannotator/config.json` (`{ "verifyAttestation": true }`) or per-invocation via `--verify-attestation`. Requires `gh` installed and authenticated. | | `PLANNOTATOR_SKIP_AGENT_TERMINAL_INSTALL` | Set to `1` / `true` to skip installing the managed Node/WebTUI runtime used by compiled Bun builds for the annotate-mode agent terminal. Read by `plannotator install-runtime agent-terminal`, which the installers call automatically. | +| `PLANNOTATOR_MINIMAL` | **Read by the install scripts only**, not by the runtime binary. Set to `1` / `true` / `yes` to have `scripts/install.sh` / `install.ps1` / `install.cmd` install **only** the `plannotator` binary — skipping the sem sidecar, the agent-terminal runtime, and all per-agent skills, hooks, slash commands, and config. Equivalent to the `--minimal` (aliased `--binary-only`) flag; `--no-minimal` overrides it. Off by default. | +| `PLANNOTATOR_SKIP_SEM_INSTALL` | **Read by the install scripts only.** Set to `1` / `true` to skip installing the optional `sem` semantic-diff sidecar (used by code review). Off by default. | **Config-only settings (`~/.plannotator/config.json`)**: Some settings have no env-var equivalent and are toggled by editing the config file directly: diff --git a/apps/marketing/src/content/docs/getting-started/installation.md b/apps/marketing/src/content/docs/getting-started/installation.md index beae65652..d2dcb9099 100644 --- a/apps/marketing/src/content/docs/getting-started/installation.md +++ b/apps/marketing/src/content/docs/getting-started/installation.md @@ -62,6 +62,27 @@ Version pinning is fully supported from **v0.17.2 onwards**. v0.17.2 is the firs +
+Binary-only install (nothing but the CLI) + +Pass `--minimal` (aliased `--binary-only`) to install **only** the `plannotator` binary — no sem semantic-diff sidecar, no agent-terminal runtime, and none of the per-agent skills, hooks, slash commands, or config for Claude, Codex, OpenCode, Gemini, or Kiro. No persistent state is written outside the install directory, and because it skips the sparse checkout, **minimal mode does not require `git`**. + +```bash +curl -fsSL https://plannotator.ai/install.sh | bash -s -- --minimal +``` + +```powershell +& ([scriptblock]::Create((irm https://plannotator.ai/install.ps1))) -Minimal +``` + +```cmd +curl -fsSL https://plannotator.ai/install.cmd -o install.cmd && install.cmd --minimal && del install.cmd +``` + +For `curl … | bash` pipelines you can set `PLANNOTATOR_MINIMAL=1` in the environment instead of passing the flag; pass `--no-minimal` to force a full install even when that variable is set. + +
+ Every release includes SHA256 checksums (verified automatically) and optional [SLSA build provenance](/docs/reference/verifying-your-install/) attestations. ## Claude Code diff --git a/apps/marketing/src/content/docs/reference/environment-variables.md b/apps/marketing/src/content/docs/reference/environment-variables.md index 37d7bdfa3..871b92003 100644 --- a/apps/marketing/src/content/docs/reference/environment-variables.md +++ b/apps/marketing/src/content/docs/reference/environment-variables.md @@ -64,6 +64,8 @@ When running your own paste service binary, these variables configure it: | Variable | Default | Description | |----------|---------|-------------| | `PLANNOTATOR_VERIFY_ATTESTATION` | off | Set to `1` or `true` to have the install script run `gh attestation verify` on the downloaded binary. Requires `gh` CLI installed and authenticated. Can also be set via `~/.plannotator/config.json` (`{ "verifyAttestation": true }`) or per-invocation via `--verify-attestation`. | +| `PLANNOTATOR_MINIMAL` | off | Set to `1` / `true` / `yes` to install **only** the `plannotator` binary — no sem sidecar, agent-terminal runtime, skills, hooks, slash commands, or per-agent config. Equivalent to passing `--minimal` (aliased `--binary-only`); pass `--no-minimal` to override. Read by the install scripts only, not the runtime binary. | +| `PLANNOTATOR_SKIP_SEM_INSTALL` | off | Set to `1` / `true` to skip installing the optional `sem` semantic-diff sidecar used by code review. Read by the install scripts only. | | `CLAUDE_CONFIG_DIR` | `~/.claude` | Custom Claude Code config directory. The install script places hooks here instead of the default location. | ## Remote mode behavior diff --git a/scripts/install.cmd b/scripts/install.cmd index 855d67ca3..a53041fca 100644 --- a/scripts/install.cmd +++ b/scripts/install.cmd @@ -18,6 +18,10 @@ set "EXTRAS_FLAG=" set "MODEL_INVOCABLE_FLAG=" set "NON_INTERACTIVE=0" set "RECONFIGURE=0" +REM Binary-only mode. Installs just plannotator.exe and no persistent state +REM elsewhere. Set by --minimal (1) / --no-minimal (0); -1 = neither flag given +REM (fall through to the PLANNOTATOR_MINIMAL env var, resolved after :args_done). +set "MINIMAL_FLAG=-1" :parse_args if "%~1"=="" goto args_done @@ -92,6 +96,33 @@ if /i "%~1"=="--reconfigure" ( shift goto parse_args ) +if /i "%~1"=="--minimal" ( + if "!MINIMAL_FLAG!"=="0" ( + echo --minimal and --no-minimal are mutually exclusive >&2 + exit /b 1 + ) + set "MINIMAL_FLAG=1" + shift + goto parse_args +) +if /i "%~1"=="--binary-only" ( + if "!MINIMAL_FLAG!"=="0" ( + echo --binary-only and --no-minimal are mutually exclusive >&2 + exit /b 1 + ) + set "MINIMAL_FLAG=1" + shift + goto parse_args +) +if /i "%~1"=="--no-minimal" ( + if "!MINIMAL_FLAG!"=="1" ( + echo --no-minimal and --minimal are mutually exclusive >&2 + exit /b 1 + ) + set "MINIMAL_FLAG=0" + shift + goto parse_args +) REM Reject any other dash-prefixed token as an unknown option, so a typoed REM flag like --verify-attesttion fails fast instead of being interpreted as REM a version tag (which would 404 on releases/download/v--verify-attesttion/...). @@ -106,7 +137,7 @@ REM unquoted arg containing `&` would re-trigger metacharacter interpretation. set "CURRENT_ARG=%~1" if "!CURRENT_ARG:~0,1!"=="-" ( echo Unknown option: "%~1" >&2 - echo Usage: install.cmd [--version ^] [--verify-attestation ^| --skip-attestation] [--extras ^| --no-extras] [--model-invocable ^] [--non-interactive] [--reconfigure] >&2 + echo Usage: install.cmd [--version ^] [--verify-attestation ^| --skip-attestation] [--extras ^| --no-extras] [--model-invocable ^] [--minimal ^| --no-minimal] [--non-interactive] [--reconfigure] >&2 exit /b 1 ) REM Positional form: install.cmd vX.Y.Z (legacy interface). @@ -122,6 +153,15 @@ shift goto parse_args :args_done +REM Resolve binary-only mode. Precedence: --minimal / --no-minimal flag > +REM PLANNOTATOR_MINIMAL env var > default (off). Mirrors install.sh / install.ps1. +set "MINIMAL=0" +if /i "%PLANNOTATOR_MINIMAL%"=="1" set "MINIMAL=1" +if /i "%PLANNOTATOR_MINIMAL%"=="true" set "MINIMAL=1" +if /i "%PLANNOTATOR_MINIMAL%"=="yes" set "MINIMAL=1" +if "!MINIMAL_FLAG!"=="1" set "MINIMAL=1" +if "!MINIMAL_FLAG!"=="0" set "MINIMAL=0" + set "REPO=backnotprop/plannotator" set "SEM_REPO=Ataraxy-Labs/sem" set "SEM_VERSION=v0.8.0" @@ -388,23 +428,22 @@ move /y "!TEMP_FILE!" "!INSTALL_PATH!" >nul echo. echo plannotator !TAG! installed to !INSTALL_PATH! +REM Binary-only mode stops here (see the MINIMAL resolution after :args_done): +REM the binary is installed, so print PATH advice and exit before any sidecar +REM download, agent integration, skill checkout, or config write runs. No +REM persistent state is written outside !INSTALL_DIR!. +if "!MINIMAL!"=="1" ( + call :PrintPathAdvice + echo. + echo Minimal install complete - only the plannotator binary was installed. + echo No skills, hooks, agent integrations, or config files were written. + exit /b 0 +) + call :InstallSemSidecar call :InstallAgentTerminalRuntime -REM Check if install directory is in PATH -echo !PATH! | findstr /i /c:"!INSTALL_DIR!" >nul -if !ERRORLEVEL! neq 0 ( - echo. - echo !INSTALL_DIR! is not in your PATH. - echo. - echo Add it permanently with: - echo. - echo setx PATH "%%PATH%%;!INSTALL_DIR!" - echo. - echo Or add it for this session only: - echo. - echo set PATH=%%PATH%%;!INSTALL_DIR! -) +call :PrintPathAdvice REM Validate plugin hooks.json if plugin is already installed if defined CLAUDE_CONFIG_DIR ( @@ -959,6 +998,27 @@ if exist "!PLUGIN_HOOKS!" if exist "!CLAUDE_SETTINGS!" ( echo. exit /b 0 +REM ====================================================================== +REM Print the PATH-setup hint if INSTALL_DIR isn't already on PATH. Called by +REM both the --minimal early exit and the normal flow (mirrors install.sh's +REM print_path_advice). +REM ====================================================================== +:PrintPathAdvice +echo !PATH! | findstr /i /c:"!INSTALL_DIR!" >nul +if !ERRORLEVEL! neq 0 ( + echo. + echo !INSTALL_DIR! is not in your PATH. + echo. + echo Add it permanently with: + echo. + echo setx PATH "%%PATH%%;!INSTALL_DIR!" + echo. + echo Or add it for this session only: + echo. + echo set PATH=%%PATH%%;!INSTALL_DIR! +) +goto :eof + REM ====================================================================== REM Optional annotate agent terminal runtime install. Non-fatal: Plannotator REM remains installed if Node/npm or npm install is unavailable. diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 7a32df189..64fac1c89 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -7,7 +7,10 @@ param( [switch]$NoExtras, [string]$ModelInvocable = "", [switch]$NonInteractive, - [switch]$Reconfigure + [switch]$Reconfigure, + [Alias("BinaryOnly")] + [switch]$Minimal, + [switch]$NoMinimal ) $ErrorActionPreference = "Stop" @@ -23,6 +26,22 @@ if ($Extras -and $NoExtras) { [Console]::Error.WriteLine("-Extras and -NoExtras are mutually exclusive. Pass one or the other.") exit 1 } +if ($Minimal -and $NoMinimal) { + [Console]::Error.WriteLine("-Minimal and -NoMinimal are mutually exclusive. Pass one or the other.") + exit 1 +} + +# Binary-only mode. Installs just the plannotator binary and no persistent state +# elsewhere — no sem sidecar, agent-terminal runtime, skills, hooks, or per-agent +# config. Precedence: -Minimal / -NoMinimal switch > PLANNOTATOR_MINIMAL env var +# > default (off). Mirrors install.sh's --minimal / --no-minimal. +$minimal = $false +if ($env:PLANNOTATOR_MINIMAL -match '^(1|true|yes)$') { + $minimal = $true +} +if ($Minimal) { $minimal = $true } +if ($NoMinimal) { $minimal = $false } + $repo = "backnotprop/plannotator" $semRepo = "Ataraxy-Labs/sem" $semVersion = "v0.8.0" @@ -330,18 +349,36 @@ Move-Item -Force $tmpFile "$installDir\plannotator.exe" Write-Host "" Write-Host "plannotator $latestTag installed to $installDir\plannotator.exe" -Install-SemSidecar -Install-AgentTerminalRuntime +# Add $installDir to the user PATH if not already there. Extracted so both the +# -Minimal early exit and the normal flow reuse it (mirrors install.sh's +# print_path_advice). +function Show-PathAdvice { + $userPath = [Environment]::GetEnvironmentVariable("Path", "User") + if ($userPath -notlike "*$installDir*") { + Write-Host "" + Write-Host "$installDir is not in your PATH. Adding it..." + [Environment]::SetEnvironmentVariable("Path", "$userPath;$installDir", "User") + Write-Host "Added to PATH. Restart your terminal for changes to take effect." + } +} -# Add to PATH if not already there -$userPath = [Environment]::GetEnvironmentVariable("Path", "User") -if ($userPath -notlike "*$installDir*") { +# Binary-only mode stops here (see the $minimal resolution near the top): the +# binary is installed, so add it to PATH and exit before any sidecar download, +# agent integration, skill checkout, config write, or cleanup runs. No +# persistent state is written outside $installDir. +if ($minimal) { + Show-PathAdvice Write-Host "" - Write-Host "$installDir is not in your PATH. Adding it..." - [Environment]::SetEnvironmentVariable("Path", "$userPath;$installDir", "User") - Write-Host "Added to PATH. Restart your terminal for changes to take effect." + Write-Host "Minimal install complete - only the plannotator binary was installed." + Write-Host "No skills, hooks, agent integrations, or config files were written." + exit 0 } +Install-SemSidecar +Install-AgentTerminalRuntime + +Show-PathAdvice + # Validate plugin hooks.json if plugin is already installed $pluginHooks = if ($env:CLAUDE_CONFIG_DIR) { "$env:CLAUDE_CONFIG_DIR\plugins\marketplaces\plannotator\apps\hook\hooks\hooks.json" } else { "$env:USERPROFILE\.claude\plugins\marketplaces\plannotator\apps\hook\hooks\hooks.json" } if (Test-Path $pluginHooks) { diff --git a/scripts/install.sh b/scripts/install.sh index fd01bd050..05ec1d483 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -41,17 +41,19 @@ MODEL_INVOCABLE_FLAG="" NON_INTERACTIVE=0 RECONFIGURE=0 # Binary-only mode. Installs just the plannotator binary (to $INSTALL_DIR) and -# nothing else — no sem sidecar, no agent-terminal runtime, no skills, hooks, -# slash commands, or per-agent config (Claude, Codex, OpenCode, Gemini, Kiro). -# -1 = flag not set (fall through to env var); 0 = off; 1 = on. Resolved after -# arg parsing so PLANNOTATOR_MINIMAL can enable it for `curl | bash` runs. +# no persistent state elsewhere — no sem sidecar, no agent-terminal runtime, no +# skills, hooks, slash commands, or per-agent config (Claude, Codex, OpenCode, +# Gemini, Kiro). Set by --minimal (1) / --no-minimal (0); -1 = neither flag +# given (fall through to the PLANNOTATOR_MINIMAL env var). Resolved after arg +# parsing so a flag overrides the env var in either direction. MINIMAL_FLAG=-1 usage() { cat <<'USAGE' Usage: install.sh [--version ] [--verify-attestation | --skip-attestation] [--extras | --no-extras] [--model-invocable |none] - [--minimal] [--non-interactive] [--reconfigure] [--help] + [--minimal | --no-minimal] [--non-interactive] + [--reconfigure] [--help] install.sh Options: @@ -69,13 +71,16 @@ Options: --model-invocable Comma-separated skill names to make model-invocable (e.g. plannotator-review,plannotator-compound), or "none". Skills are user-invoked-only by default. - --minimal Install only the plannotator binary. Skips the sem - semantic-diff sidecar, the agent-terminal runtime, and - every per-agent integration (skills, hooks, slash - commands, and config for Claude, Codex, OpenCode, - Gemini, and Kiro). Nothing is written outside - $HOME/.local/bin. Also enabled by exporting - PLANNOTATOR_MINIMAL=1. + --minimal Install only the plannotator binary (aliased + --binary-only). Skips the sem semantic-diff sidecar, + the agent-terminal runtime, and every per-agent + integration (skills, hooks, slash commands, and config + for Claude, Codex, OpenCode, Gemini, and Kiro). No + persistent state is written outside $HOME/.local/bin + (a temp download file is still used and removed). Also + enabled by exporting PLANNOTATOR_MINIMAL=1. + --no-minimal Force a full install even when PLANNOTATOR_MINIMAL is + set in the environment. --non-interactive Never prompt, even in a terminal. Uses flags, then saved answers from a previous run, then the defaults (no extras, nothing model-invocable). @@ -203,9 +208,23 @@ while [ $# -gt 0 ]; do shift ;; --minimal|--binary-only) + if [ "$MINIMAL_FLAG" = "0" ]; then + echo "--minimal and --no-minimal are mutually exclusive" >&2 + usage >&2 + exit 1 + fi MINIMAL_FLAG=1 shift ;; + --no-minimal) + if [ "$MINIMAL_FLAG" = "1" ]; then + echo "--no-minimal and --minimal are mutually exclusive" >&2 + usage >&2 + exit 1 + fi + MINIMAL_FLAG=0 + shift + ;; -h|--help) usage exit 0 @@ -231,9 +250,10 @@ while [ $# -gt 0 ]; do esac done -# Resolve binary-only mode. Precedence: --minimal flag > PLANNOTATOR_MINIMAL -# env var > default (off). The env var lets `curl ... | bash` runs opt in -# without a flag, matching how PLANNOTATOR_SKIP_SEM_INSTALL et al. work. +# Resolve binary-only mode. Precedence: --minimal / --no-minimal flag > +# PLANNOTATOR_MINIMAL env var > default (off). The env var lets `curl ... | bash` +# runs opt in without a flag, matching how PLANNOTATOR_SKIP_SEM_INSTALL et al. +# work; --no-minimal lets a flag override an env var that enables it. minimal=0 case "${PLANNOTATOR_MINIMAL:-}" in 1|true|yes|TRUE|YES|True|Yes) minimal=1 ;; @@ -428,8 +448,10 @@ print_path_advice() { # Binary-only mode stops here: the binary is installed, so print PATH advice and # exit before any sidecar download, agent integration, skill checkout, config -# write, cache clear, or cleanup migration runs. Nothing outside $INSTALL_DIR is -# touched. See the MINIMAL_FLAG / PLANNOTATOR_MINIMAL resolution near the top. +# write, cache clear, or cleanup migration runs. No persistent state is written +# outside $INSTALL_DIR (the temp download file was already cleaned up above; the +# config dir may have been read, never written). See the MINIMAL_FLAG / +# PLANNOTATOR_MINIMAL resolution near the top. if [ "$minimal" -eq 1 ]; then print_path_advice echo "" diff --git a/scripts/install.test.ts b/scripts/install.test.ts index 0c9a22fcd..98eee06ae 100644 --- a/scripts/install.test.ts +++ b/scripts/install.test.ts @@ -289,15 +289,19 @@ describe("install.sh", () => { // Usage text advertises the flag and the env-var opt-in for curl | bash. expect(script).toContain("--minimal"); expect(script).toContain("PLANNOTATOR_MINIMAL"); - // Accepts both --minimal and the --binary-only alias. + // Accepts both --minimal and the --binary-only alias, plus the opt-out. expect(script).toContain("--minimal|--binary-only)"); + expect(script).toContain("--no-minimal)"); }); test("minimal mode is resolved from flag with env-var fallback", () => { - // Flag wins over env var, which wins over the default (off). + // A flag (--minimal or --no-minimal) wins over the env var, which wins over + // the default (off). MINIMAL_FLAG stays -1 until a flag sets 0 or 1. expect(script).toContain("MINIMAL_FLAG=-1"); expect(script).toContain('case "${PLANNOTATOR_MINIMAL:-}" in'); expect(script).toContain('if [ "$MINIMAL_FLAG" -ne -1 ]; then'); + // --minimal and --no-minimal are mutually exclusive. + expect(script).toContain("--minimal and --no-minimal are mutually exclusive"); }); test("minimal mode exits after the binary install, before any extras", () => { @@ -476,6 +480,35 @@ describe("install.ps1", () => { expect(skillsInstallIndex).toBeGreaterThan(0); expect(piUpdateCallIndex).toBeGreaterThan(skillsInstallIndex); }); + + test("supports -Minimal / -BinaryOnly binary-only mode with env-var fallback", () => { + // Switch + alias in the param block, plus the PLANNOTATOR_MINIMAL env fallback. + expect(script).toContain('[Alias("BinaryOnly")]'); + expect(script).toContain("[switch]$Minimal"); + expect(script).toContain("[switch]$NoMinimal"); + expect(script).toContain("$env:PLANNOTATOR_MINIMAL"); + // -Minimal / -NoMinimal are mutually exclusive (parity with sh/cmd). + expect(script).toContain("-Minimal and -NoMinimal are mutually exclusive"); + }); + + test("minimal mode exits after the binary install, before any extras", () => { + // Same ordering guarantee as install.sh: binary placed, then the early exit, + // then (only in the full install) the sidecar + integration work. + const binaryInstalled = script.indexOf( + 'Move-Item -Force $tmpFile "$installDir\\plannotator.exe"', + ); + const minimalExit = script.indexOf("if ($minimal) {"); + const semInstall = script.indexOf("Install-SemSidecar\n"); + const pathAdvice = script.indexOf("function Show-PathAdvice"); + + expect(binaryInstalled).toBeGreaterThan(0); + expect(pathAdvice).toBeGreaterThan(binaryInstalled); + expect(minimalExit).toBeGreaterThan(binaryInstalled); + expect(semInstall).toBeGreaterThan(minimalExit); + // The gate exits rather than falling through. + const gateBody = script.slice(minimalExit, minimalExit + 400); + expect(gateBody).toContain("exit 0"); + }); }); describe("install.cmd", () => { @@ -632,6 +665,36 @@ describe("install.cmd", () => { // Enforcement: hard-fail when opted in but gh missing expect(script).toContain("gh CLI was not found"); }); + + test("supports --minimal / --binary-only binary-only mode with env-var fallback", () => { + expect(script).toContain('if /i "%~1"=="--minimal"'); + expect(script).toContain('if /i "%~1"=="--binary-only"'); + expect(script).toContain('if /i "%~1"=="--no-minimal"'); + expect(script).toContain("PLANNOTATOR_MINIMAL"); + // Usage string advertises the flag. + expect(script).toContain("[--minimal ^| --no-minimal]"); + // --minimal / --no-minimal are mutually exclusive (parity with sh/ps1). + expect(script).toContain("--minimal and --no-minimal are mutually exclusive"); + }); + + test("minimal mode exits after the binary install, before any extras", () => { + const binaryInstalled = script.indexOf( + 'move /y "!TEMP_FILE!" "!INSTALL_PATH!"', + ); + const minimalExit = script.indexOf('if "!MINIMAL!"=="1" ('); + const semInstall = script.indexOf("call :InstallSemSidecar"); + const printPathAdvice = script.indexOf(":PrintPathAdvice"); + + expect(binaryInstalled).toBeGreaterThan(0); + expect(minimalExit).toBeGreaterThan(binaryInstalled); + expect(semInstall).toBeGreaterThan(minimalExit); + // The gate exits rather than falling through, and reuses :PrintPathAdvice. + const gateBody = script.slice(minimalExit, minimalExit + 400); + expect(gateBody).toContain("call :PrintPathAdvice"); + expect(gateBody).toContain("exit /b 0"); + // :PrintPathAdvice is defined as a subroutine. + expect(printPathAdvice).toBeGreaterThan(0); + }); }); describe("Core Plannotator skills", () => { @@ -691,6 +754,26 @@ describe("install shared behavior", () => { expect(cmdScript).not.toContain("/dev/null"); }); + test("binary-only (minimal) mode exists in all three installers", () => { + const cmdScript = readFileSync(join(scriptsDir, "install.cmd"), "utf-8"); + // Every installer exposes the flag, its --binary-only / -BinaryOnly alias, + // the explicit opt-out, and the PLANNOTATOR_MINIMAL env-var fallback — so a + // user gets the same binary-only path whatever host they install from. + expect(sh).toContain("--minimal|--binary-only)"); + expect(sh).toContain("--no-minimal)"); + expect(sh).toContain("PLANNOTATOR_MINIMAL"); + + expect(ps).toContain('[Alias("BinaryOnly")]'); + expect(ps).toContain("[switch]$Minimal"); + expect(ps).toContain("[switch]$NoMinimal"); + expect(ps).toContain("$env:PLANNOTATOR_MINIMAL"); + + expect(cmdScript).toContain('if /i "%~1"=="--minimal"'); + expect(cmdScript).toContain('if /i "%~1"=="--binary-only"'); + expect(cmdScript).toContain('if /i "%~1"=="--no-minimal"'); + expect(cmdScript).toContain("PLANNOTATOR_MINIMAL"); + }); + test("guided install exists in all three installers with safe automation behavior", () => { const cmdScript = readFileSync(join(scriptsDir, "install.cmd"), "utf-8"); // Shared prefs file (same format across platforms) in the data dir. From 7634fc59f0d8f309f1d491e62f15c58d4a38b707 Mon Sep 17 00:00:00 2001 From: Stan <26458805+Staninna@users.noreply.github.com> Date: Thu, 2 Jul 2026 20:18:13 +0200 Subject: [PATCH 3/3] docs(install): correct minimal-mode PATH wording (ps1 persists PATH) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PowerShell installer's minimal path calls Show-PathAdvice, which persistently writes the user PATH — so "no persistent state is written outside $installDir" overpromised. Reword the install.ps1 comment and the installation docs to describe what minimal mode skips (sidecar, agent runtime, per-agent integrations) and note the binary + PATH entry are still added on Windows. Addresses the second Copilot review on #989. --- .../src/content/docs/getting-started/installation.md | 2 +- scripts/install.ps1 | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/marketing/src/content/docs/getting-started/installation.md b/apps/marketing/src/content/docs/getting-started/installation.md index d2dcb9099..1e88d3169 100644 --- a/apps/marketing/src/content/docs/getting-started/installation.md +++ b/apps/marketing/src/content/docs/getting-started/installation.md @@ -65,7 +65,7 @@ Version pinning is fully supported from **v0.17.2 onwards**. v0.17.2 is the firs
Binary-only install (nothing but the CLI) -Pass `--minimal` (aliased `--binary-only`) to install **only** the `plannotator` binary — no sem semantic-diff sidecar, no agent-terminal runtime, and none of the per-agent skills, hooks, slash commands, or config for Claude, Codex, OpenCode, Gemini, or Kiro. No persistent state is written outside the install directory, and because it skips the sparse checkout, **minimal mode does not require `git`**. +Pass `--minimal` (aliased `--binary-only`) to install **only** the `plannotator` binary — no sem semantic-diff sidecar, no agent-terminal runtime, and none of the per-agent skills, hooks, slash commands, or config for Claude, Codex, OpenCode, Gemini, or Kiro. The only thing installed is the binary (the Windows PowerShell installer also adds the install directory to your user `PATH`), and because it skips the sparse checkout, **minimal mode does not require `git`**. ```bash curl -fsSL https://plannotator.ai/install.sh | bash -s -- --minimal diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 64fac1c89..f1e37b528 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -364,8 +364,9 @@ function Show-PathAdvice { # Binary-only mode stops here (see the $minimal resolution near the top): the # binary is installed, so add it to PATH and exit before any sidecar download, -# agent integration, skill checkout, config write, or cleanup runs. No -# persistent state is written outside $installDir. +# agent integration, skill checkout, config write, or cleanup runs. Only the +# binary and its PATH entry are added — none of the sem sidecar, agent-terminal +# runtime, or per-agent skills, hooks, or config. if ($minimal) { Show-PathAdvice Write-Host ""