Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ This repo **is** a CLI tool manager, so the word "upgrade" is overloaded:
## Index of scoped AGENTS.md

- [cli_audit/AGENTS.md](./cli_audit/AGENTS.md) — Python package (21 modules)
- [scripts/AGENTS.md](./scripts/AGENTS.md) — Installation scripts (Bash, 33 scripts)
- [scripts/AGENTS.md](./scripts/AGENTS.md) — Installation scripts (Bash, 34 scripts)
- [tests/AGENTS.md](./tests/AGENTS.md) — Test suite (pytest, 14 test files)

## Commands (verified 2026-04-16)
Expand Down Expand Up @@ -123,8 +123,8 @@ This repo **is** a CLI tool manager, so the word "upgrade" is overloaded:
**AI CLI Preparation v2.0** — Tool version auditing and installation management for AI coding agents.

- **Modules:** 21 Python modules under `cli_audit/` (see [`cli_audit/AGENTS.md`](./cli_audit/AGENTS.md))
- **Catalog:** 97 JSON tool definitions under `catalog/`
- **Installers:** 33 Bash scripts under `scripts/` (see [`scripts/AGENTS.md`](./scripts/AGENTS.md))
- **Catalog:** 98 JSON tool definitions under `catalog/`
- **Installers:** 34 Bash scripts under `scripts/` (see [`scripts/AGENTS.md`](./scripts/AGENTS.md))
- **Phase 1:** Detection & auditing (complete)
- **Phase 2:** Installation & upgrade management (complete)
- **Entry point:** `audit.py` → `cli_audit` package
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.
## [Unreleased]

### Added
- `wslu`/`wslview` catalog entry (`catalog/wslu.json` + `scripts/install_wslu.sh`), gated by a new `requires_wsl` catalog flag so it is only surfaced and installed under WSL. Its installer also points the xdg default browser at `wslview` (so links open in the Windows browser); opt out with `WSLU_SET_DEFAULT_BROWSER=0`.
- Governance files: PR template, `CHANGELOG.md`. Security reporting is covered by the [org-level SECURITY.md](https://github.com/netresearch/.github/blob/main/SECURITY.md).
- `.github/workflows/dependency-review.yml` and a dedicated `security.yml` (pip-audit + bandit + CycloneDX SBOM). CodeQL continues to run via GitHub's default code-scanning setup.
- `.pre-commit-config.yaml` for local hook enforcement (flake8, black, isort, shellcheck).
Expand Down
4 changes: 2 additions & 2 deletions catalog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Install tools via system package managers (apt, brew, dnf, pacman).
2. The tool will automatically be available via `make install-<tool>`, `make upgrade-<tool>`, `make uninstall-<tool>`, and `make reconcile-<tool>`
3. No need to create a custom install script!

Currently **97 tools** are cataloged.
Currently **98 tools** are cataloged.

## Environment Variables

Expand All @@ -96,6 +96,6 @@ make upgrade

## Architecture

All 97 tools have catalog entries. The generic installer (`scripts/install_tool.sh`) reads a tool's catalog JSON and delegates to the appropriate method-specific installer under `scripts/installers/`. Tools with complex installation needs (python, node, docker, rust, etc.) use `install_method: "dedicated_script"` to route to their existing bespoke scripts.
All 98 tools have catalog entries. The generic installer (`scripts/install_tool.sh`) reads a tool's catalog JSON and delegates to the appropriate method-specific installer under `scripts/installers/`. Tools with complex installation needs (python, node, docker, rust, etc.) use `install_method: "dedicated_script"` to route to their existing bespoke scripts.

See [ADR-007](../docs/adr/ADR-007-generic-tool-installation-architecture.md) for the full architectural decision record.
17 changes: 17 additions & 0 deletions catalog/wslu.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "wslu",
"category": "platform",
"install_method": "dedicated_script",
"description": "WSL utilities; provides wslview to open URLs/files in the Windows default browser",
"homepage": "https://wslutiliti.es/wslu/",
"binary_name": "wslview",
"script": "install_wslu.sh",
"requires_wsl": true,
"skip_upstream": true,
"tags": ["platform", "wsl"],
"guide": {
"display_name": "wslu (wslview)",
"install_action": "install",
"order": 210
}
}
13 changes: 12 additions & 1 deletion cli_audit/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,21 @@ def all_tools(self) -> list[str]:
def all_tool_definitions(self) -> list[Tool]: # noqa: F821
"""Get all tools as Tool instances.

Tools flagged ``requires_wsl`` in their catalog JSON are only included
when running under WSL, so non-WSL machines are not prompted to install
(or shown as missing) a tool that has no meaning there.

Returns:
List of Tool instances generated from catalog
"""
return [entry.to_tool() for entry in self._entries.values()]
from cli_audit.collectors import is_wsl # lazy import: avoids import cycle

on_wsl = is_wsl()
return [
entry.to_tool()
for entry in self._entries.values()
if on_wsl or not self.get_raw_data(entry.name).get("requires_wsl")
]

def get_package_manager_tools(self) -> list[ToolCatalogEntry]:
"""Get tools that use package_manager install method.
Expand Down
5 changes: 3 additions & 2 deletions scripts/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

## Overview

33 Bash scripts for installing developer tools with multiple actions:
34 Bash scripts for installing developer tools with multiple actions:
- **install**: Fresh installation (default action)
- **update**: Upgrade to latest version
- **uninstall**: Remove installation
Comment thread
CybotTM marked this conversation as resolved.
Expand All @@ -28,6 +28,7 @@
- `install_ansible.sh`, `install_brew.sh`, `install_composer.sh`
- `install_docker.sh`, `install_gem.sh`, `install_parallel.sh`, `install_yarn.sh`
- `install_tmux.sh`, `install_tree.sh` (single-binary utilities)
- `install_wslu.sh`: WSL utilities (`wslview`) — installs only under WSL; optionally sets `wslview` as the default browser

**Upgrade & management:**
- `guide.sh`: Interactive upgrade guide (supports 'a' for auto-update)
Expand Down Expand Up @@ -94,7 +95,7 @@ make scripts-perms # Ensure all scripts are executable
./scripts/install_node.sh reconcile
```

**Via Make (works for all 97 cataloged tools):**
**Via Make (works for all 98 cataloged tools):**
```bash
make install-python # Install Python toolchain (dedicated script)
make upgrade-python # Upgrade Python toolchain
Expand Down
85 changes: 85 additions & 0 deletions scripts/install_wslu.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
set -euo pipefail

# wslu provides `wslview`, which opens URLs/files in the Windows default browser.
# It is only meaningful under WSL, so every action is a graceful no-op elsewhere.

DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$DIR/lib/common.sh"

ACTION="${1:-install}"

wslview_version() { have wslview && wslview --version 2>/dev/null | head -1 || true; }

set_default_browser() {
# Point the xdg default browser at wslview so links (xdg-open, Python's
# webbrowser, MCP servers, etc.) open in the Windows browser instead of a
# WSLg Linux browser. Opt out with WSLU_SET_DEFAULT_BROWSER=0.
[ "${WSLU_SET_DEFAULT_BROWSER:-1}" = "1" ] || {

Check failure on line 18 in scripts/install_wslu.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=netresearch_coding_agent_cli_toolset&issues=AZ8cbeBOYi8n4DQPNdSU&open=AZ8cbeBOYi8n4DQPNdSU&pullRequest=99
echo "[wslu] WSLU_SET_DEFAULT_BROWSER=0 - leaving default browser unchanged"
return 0
}
have xdg-settings || {
echo "[wslu] xdg-settings not found - skipping default-browser setup"
return 0
}
local current
current="$(xdg-settings get default-web-browser 2>/dev/null || true)"
if [ "$current" = "wslview.desktop" ]; then

Check failure on line 28 in scripts/install_wslu.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=netresearch_coding_agent_cli_toolset&issues=AZ8cbeBOYi8n4DQPNdSV&open=AZ8cbeBOYi8n4DQPNdSV&pullRequest=99
echo "[wslu] default web browser already wslview.desktop"
return 0
fi
if xdg-settings set default-web-browser wslview.desktop 2>/dev/null; then
echo "[wslu] default web browser set to wslview.desktop (was: ${current:-unset})"
else
echo "[wslu] could not set default browser (non-fatal); run: xdg-settings set default-web-browser wslview.desktop"
fi
}

install_wslu() {
if ! is_wsl; then
echo "[wslu] Not running under WSL - nothing to do (wslu/wslview only applies on WSL)."
return 0
fi
echo "[wslu] current: $(wslview_version)"
if have apt-get; then
apt_install_if_missing wslu || { echo "[wslu] apt install failed" >&2; return 1; }
elif have dnf; then
sudo dnf install -y wslu
elif have pacman; then
sudo pacman -S --noconfirm wslu
elif have zypper; then
sudo zypper install -y wslu
else
echo "[wslu] No supported package manager found." >&2
echo "[wslu] Install wslu manually: https://wslutiliti.es/wslu/install.html" >&2
return 1
fi
echo "[wslu] installed: $(wslview_version)"
set_default_browser
}

uninstall_wslu() {
if ! is_wsl; then
echo "[wslu] Not running under WSL - nothing to do."
echo "[wslu] If wslu was installed manually elsewhere, remove it with your package manager (e.g. sudo apt-get remove wslu)."
return 0
fi
if have apt-get; then
apt_remove_if_present wslu
elif have dnf; then
sudo dnf remove -y wslu || true
elif have pacman; then
sudo pacman -R --noconfirm wslu || true
elif have zypper; then
sudo zypper remove -y wslu || true
else
echo "[wslu] Please remove wslu via your system package manager." >&2
fi
}

case "$ACTION" in
install|update) install_wslu ;;
uninstall) uninstall_wslu ;;
*) echo "Usage: $0 {install|update|uninstall}" >&2 ; exit 2 ;;
esac
38 changes: 38 additions & 0 deletions tests/test_wsl_gating.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Tests for WSL-conditional catalog tools (the ``requires_wsl`` gate).

A tool flagged ``requires_wsl`` (currently ``wslu``/``wslview``) must only be
surfaced when running under WSL, so non-WSL machines are neither prompted to
install it nor shown it as missing.
"""

from unittest.mock import patch

from cli_audit.catalog import ToolCatalog


@patch("cli_audit.collectors.is_wsl", return_value=True)
def test_wsl_only_tool_present_on_wsl(_mock_is_wsl):
names = {t.name for t in ToolCatalog().all_tool_definitions()}
assert "wslu" in names


@patch("cli_audit.collectors.is_wsl", return_value=False)
def test_wsl_only_tool_hidden_off_wsl(_mock_is_wsl):
names = {t.name for t in ToolCatalog().all_tool_definitions()}
assert "wslu" not in names


@patch("cli_audit.collectors.is_wsl", return_value=False)
def test_non_wsl_tools_unaffected_off_wsl(_mock_is_wsl):
"""Gating a WSL-only tool must not drop ordinary tools."""
names = {t.name for t in ToolCatalog().all_tool_definitions()}
assert "ripgrep" in names


def test_wslu_catalog_entry_is_wsl_gated():
"""The wslu entry must stay WSL-gated, apt/skip-upstream, wslview-detected."""
raw = ToolCatalog().get_raw_data("wslu")
assert raw.get("requires_wsl") is True
assert raw.get("skip_upstream") is True
assert raw.get("binary_name") == "wslview"
assert raw.get("install_method") == "dedicated_script"
Loading