From ac67d5a1f6c62b434e44ec7aa9a0a42011bb77a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 22:51:58 +0000 Subject: [PATCH 1/2] Initial plan From bdbd2e53e1ab2993eeb8046200ecf1578c77f021 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 22:56:34 +0000 Subject: [PATCH 2/2] Add basecamp-fizzy-cli feature Co-authored-by: sebst <592313+sebst@users.noreply.github.com> Agent-Logs-Url: https://github.com/devcontainer-community/devcontainer-features/sessions/fbf52b35-5660-43f5-b39f-7c5dbe718276 --- src/basecamp-fizzy-cli/NOTES.md | 17 +++ .../devcontainer-feature.json | 17 +++ src/basecamp-fizzy-cli/install.sh | 139 ++++++++++++++++++ test/basecamp-fizzy-cli/test.sh | 17 +++ 4 files changed, 190 insertions(+) create mode 100644 src/basecamp-fizzy-cli/NOTES.md create mode 100644 src/basecamp-fizzy-cli/devcontainer-feature.json create mode 100755 src/basecamp-fizzy-cli/install.sh create mode 100755 test/basecamp-fizzy-cli/test.sh diff --git a/src/basecamp-fizzy-cli/NOTES.md b/src/basecamp-fizzy-cli/NOTES.md new file mode 100644 index 0000000..40a86ea --- /dev/null +++ b/src/basecamp-fizzy-cli/NOTES.md @@ -0,0 +1,17 @@ +# basecamp-fizzy-cli + +## Project + +- [fizzy-cli](https://github.com/basecamp/fizzy-cli) + +## Description + +`fizzy` is a command-line interface for [Fizzy](https://fizzy.do), a project management tool by Basecamp. Manage boards, cards, comments, and more from your terminal or through AI agents. It works standalone or with any AI agent (Claude, Codex, Copilot, Gemini) and returns structured JSON output with breadcrumbs for easy navigation. + +## Installation Method + +Downloaded as a pre-compiled binary from the [GitHub releases page](https://github.com/basecamp/fizzy-cli/releases) and placed in `/usr/local/bin`. + +## Other Notes + +_No additional notes._ diff --git a/src/basecamp-fizzy-cli/devcontainer-feature.json b/src/basecamp-fizzy-cli/devcontainer-feature.json new file mode 100644 index 0000000..77d83f2 --- /dev/null +++ b/src/basecamp-fizzy-cli/devcontainer-feature.json @@ -0,0 +1,17 @@ +{ + "name": "basecamp-fizzy-cli", + "id": "basecamp-fizzy-cli", + "version": "1.0.0", + "description": "Install \"fizzy\" CLI binary", + "documentationURL": "https://github.com/devcontainer-community/devcontainer-features/tree/main/src/basecamp-fizzy-cli", + "options": { + "version": { + "type": "string", + "default": "latest", + "proposals": [ + "latest" + ], + "description": "Version of \"fizzy\" to install." + } + } +} diff --git a/src/basecamp-fizzy-cli/install.sh b/src/basecamp-fizzy-cli/install.sh new file mode 100755 index 0000000..d37d1ce --- /dev/null +++ b/src/basecamp-fizzy-cli/install.sh @@ -0,0 +1,139 @@ +#!/bin/bash +set -o errexit +set -o pipefail +set -o noclobber +set -o nounset +set -o allexport +readonly githubRepository='basecamp/fizzy-cli' +readonly binaryName='fizzy' +readonly versionArgument='--version' +readonly binaryTargetFolder='/usr/local/bin' +readonly name="${githubRepository##*/}" +apt_get_update() { + if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then + echo "Running apt-get update..." + apt-get update -y + fi +} +apt_get_checkinstall() { + if ! dpkg -s "$@" >/dev/null 2>&1; then + apt_get_update + DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends --no-install-suggests --option 'Debug::pkgProblemResolver=true' --option 'Debug::pkgAcquire::Worker=1' "$@" + fi +} +apt_get_cleanup() { + apt-get clean + rm -rf /var/lib/apt/lists/* +} +check_curl_envsubst_installed() { + declare -a requiredAptPackagesMissing=() + if ! [ -r '/etc/ssl/certs/ca-certificates.crt' ]; then + requiredAptPackagesMissing+=('ca-certificates') + fi + if ! command -v curl >/dev/null 2>&1; then + requiredAptPackagesMissing+=('curl') + fi + if ! command -v envsubst >/dev/null 2>&1; then + requiredAptPackagesMissing+=('gettext-base') + fi + declare -i requiredAptPackagesMissingCount=${#requiredAptPackagesMissing[@]} + if [ $requiredAptPackagesMissingCount -gt 0 ]; then + apt_get_update + apt_get_checkinstall "${requiredAptPackagesMissing[@]}" + apt_get_cleanup + fi +} +curl_check_url() { + local url=$1 + local status_code + status_code=$(curl -s -o /dev/null -w '%{http_code}' "$url") + if [ "$status_code" -ne 200 ] && [ "$status_code" -ne 302 ]; then + echo "Failed to download '$url'. Status code: $status_code." + return 1 + fi +} +curl_download_file() { + local url=$1 + local target=$2 + curl \ + --silent \ + --location \ + --output "$target" \ + --connect-timeout 5 \ + "$url" +} +debian_get_arch() { + echo "$(dpkg --print-architecture)" +} +debian_get_target_arch() { + case $(debian_get_arch) in + amd64) echo 'amd64' ;; + arm64) echo 'arm64' ;; + *) echo 'unknown' ;; + esac +} +echo_banner() { + local text="$1" + echo -e "\e[1m\e[97m\e[41m$text\e[0m" +} +github_list_releases() { + if [ -z "$1" ]; then + echo "Usage: list_github_releases " + return 1 + fi + local repo="$1" + local url="https://api.github.com/repos/$repo/releases" + curl -s "$url" | grep -Po '"tag_name": "\K.*?(?=")' | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' | sed 's/^v//' +} +github_get_latest_release() { + if [ -z "$1" ]; then + echo "Usage: get_latest_github_release " + return 1 + fi + github_list_releases "$1" | head -n 1 +} +github_get_tag_for_version() { + if [ -z "$1" ] || [ -z "$2" ]; then + echo "Usage: github_get_tag_for_version " + return 1 + fi + local repo="$1" + local _version="$2" + local url="https://api.github.com/repos/$repo/releases" + local escaped_version + escaped_version="$(printf '%s' "$_version" | sed 's/\./\\./g')" + curl -s "$url" | grep -Po '"tag_name": "\K.*?(?=")' | grep -E "^v?${escaped_version}$" | head -n 1 +} +utils_check_version() { + local version=$1 + if ! [[ "${version:-}" =~ ^(latest|[0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + printf >&2 '=== [ERROR] Option "version" (value: "%s") is not "latest" or valid semantic version format "X.Y.Z" !\n' \ + "$version" + exit 1 + fi +} +install() { + utils_check_version "$VERSION" + check_curl_envsubst_installed + readonly architecture="$(debian_get_target_arch)" + readonly binaryTargetPath="${binaryTargetFolder}/${binaryName}" + if [ "$VERSION" == 'latest' ] || [ -z "$VERSION" ]; then + VERSION=$(github_get_latest_release "$githubRepository") + fi + readonly version="${VERSION:?}" + readonly releaseTag="$(github_get_tag_for_version "$githubRepository" "$version")" + if [ -z "$releaseTag" ]; then + printf >&2 '=== [ERROR] Could not find release tag for version "%s" in "%s"!\n' "$version" "$githubRepository" + exit 1 + fi + readonly downloadUrlTemplate='https://github.com/${githubRepository}/releases/download/${releaseTag}/fizzy-linux-${architecture}' + readonly downloadUrl="$(echo -n "$downloadUrlTemplate" | envsubst)" + curl_check_url "$downloadUrl" + curl_download_file "$downloadUrl" "$binaryTargetPath" + chmod 755 "$binaryTargetPath" + apt_get_cleanup +} +echo_banner "devcontainer.community" +echo "Installing $name..." +install "$@" +echo "(*) Done!" diff --git a/test/basecamp-fizzy-cli/test.sh b/test/basecamp-fizzy-cli/test.sh new file mode 100755 index 0000000..05aa318 --- /dev/null +++ b/test/basecamp-fizzy-cli/test.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e + +# Optional: Import test library bundled with the devcontainer CLI +# See https://github.com/devcontainers/cli/blob/HEAD/docs/features/test.md#dev-container-features-test-lib +# Provides the 'check' and 'reportResults' commands. +source dev-container-features-test-lib + +# Feature-specific tests +# The 'check' command comes from the dev-container-features-test-lib. Syntax is... +# check