diff --git a/README.md b/README.md index 0504157..991f072 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ The command-line bridge between **humans** and **agents**. [Dailybot](https://www.dailybot.com) connects your team — whether they work in Slack, Google Chat, Discord, Microsoft Teams, or the web — with AI agents and automated workflows. The CLI brings that power to your terminal: progress reports, observability, health checks, messaging, and workflow automation for modern teams. -## Installation. +## Installation ```bash pip install dailybot-cli @@ -10,29 +10,67 @@ pip install dailybot-cli Requires Python 3.10+. +**Install a specific version** — append `==` to pin an exact release: + +```bash +pip install dailybot-cli==1.15.0 +``` + ### Alternative installation methods +Every method installs the **latest** release by default. Where a specific +version can be pinned, the pinned form is shown right below the default one. + **macOS (Homebrew)** ```bash brew install dailybothq/tap/dailybot ``` +Homebrew always installs the latest published formula. To pin a specific +version, install with pip instead: + +```bash +pip install dailybot-cli==1.15.0 +``` + **Linux, WSL2, or Git Bash on Windows (binary or pip fallback)** ```bash curl -sSL https://cli.dailybot.com/install.sh | bash ``` +Pin a version with the `DAILYBOT_VERSION` environment variable **or** the +`--version` flag — both do the same thing, so use whichever reads better: + +```bash +# environment variable +curl -sSL https://cli.dailybot.com/install.sh | DAILYBOT_VERSION=1.15.0 bash + +# or the equivalent flag (note the `-s --` that forwards args through bash) +curl -sSL https://cli.dailybot.com/install.sh | bash -s -- --version 1.15.0 +``` + +A pinned version installs the matching Linux binary when one exists, and +otherwise falls back to `pip install dailybot-cli==`. + **Native Windows PowerShell** (only if you don't have WSL2 or Git Bash) ```powershell irm https://cli.dailybot.com/install.ps1 | iex ``` +Pin a version by setting `DAILYBOT_VERSION` before running the script (piping +to `iex` can't take arguments, so the environment variable is the only way): + +```powershell +$env:DAILYBOT_VERSION = '1.15.0'; irm https://cli.dailybot.com/install.ps1 | iex +``` + Requires Python 3.10+ on PATH. Wraps `pipx` / `uv tool` / `pip --user`. -Or download directly from [GitHub Releases](https://github.com/DailyBotHQ/cli/releases). +Or download a specific release directly from +[GitHub Releases](https://github.com/DailyBotHQ/cli/releases). ## Checking your installed version diff --git a/docs/RELEASE_AND_DISTRIBUTION.md b/docs/RELEASE_AND_DISTRIBUTION.md index 9c57ad6..2ebe5ed 100644 --- a/docs/RELEASE_AND_DISTRIBUTION.md +++ b/docs/RELEASE_AND_DISTRIBUTION.md @@ -634,6 +634,19 @@ The public URL `https://cli.dailybot.com/install.sh` is a **Cloudflare 301 redir **Be deliberate** when editing `install.sh` — there is no staging step between merge and rollout. Test the script locally (`bash install.sh` from a checkout, or `curl -sSL https://raw.githubusercontent.com/DailyBotHQ/cli//install.sh | bash` on a feature branch) before merging. +### Pinning a specific version + +Both installers default to the **latest** release but accept an explicit version, so users (and CI) can reproduce a known-good install: + +| Method | How to pin | Applied as | +|--------|-----------|-----------| +| `pip` | `pip install dailybot-cli==` | pip requirement specifier | +| Homebrew | not supported — fall back to `pip install dailybot-cli==` | (brew installs latest formula only) | +| `install.sh` | `DAILYBOT_VERSION=` env var **or** `bash -s -- --version ` | pinned Linux binary tag `v`, falling back to `pip install dailybot-cli==` | +| `install.ps1` | `$env:DAILYBOT_VERSION = ''` (piping to `iex` cannot forward args) | `pipx` / `uv tool` / `pip --user` install of `dailybot-cli==` | + +Both scripts validate the value against `^[0-9A-Za-z.+-]+$` before it reaches any `pip`/URL command, so a malformed or hostile `DAILYBOT_VERSION` aborts the install instead of being interpolated. An empty/unset value means "latest". The README documents the user-facing commands for each method. + ### `install.sh.sha256` (supply-chain verification) Consumers like [`DailyBotHQ/agent-skill`](https://github.com/DailyBotHQ/agent-skill) verify `install.sh` against a SHA-256 sidecar before executing it: diff --git a/install.ps1 b/install.ps1 index 76acbcb..0a00059 100644 --- a/install.ps1 +++ b/install.ps1 @@ -19,6 +19,12 @@ $Package = 'dailybot-cli' $Command = 'dailybot' $MinPython = [version]'3.10' +# Install a specific version instead of the latest by setting the +# DAILYBOT_VERSION environment variable before running the script: +# $env:DAILYBOT_VERSION = '1.15.0'; irm https://cli.dailybot.com/install.ps1 | iex +# An empty value means "install the latest published version". +$Version = $env:DAILYBOT_VERSION + function Write-Info { param([string]$msg) Write-Host "==> $msg" -ForegroundColor Cyan } function Write-Ok { param([string]$msg) Write-Host "==> $msg" -ForegroundColor Green } function Write-Warn { param([string]$msg) Write-Host "==> $msg" -ForegroundColor Yellow } @@ -29,6 +35,12 @@ function Has-Cmd { return $null -ne (Get-Command $name -ErrorAction SilentlyContinue) } +function Get-PackageSpec { + # "dailybot-cli" or "dailybot-cli==" when a version is pinned. + if ($Version) { return "$Package==$Version" } + return $Package +} + function Find-Python { # Pick the first interpreter on PATH that satisfies $MinPython. # Tries `python`, `python3`, and the Windows `py` launcher (in that order). @@ -64,16 +76,17 @@ function Invoke-Python { function Try-Pipx { param([hashtable]$Python) + $spec = Get-PackageSpec if (Has-Cmd 'pipx') { Write-Info "Installing with pipx..." - & pipx install $Package --force + & pipx install $spec --force return $? } # pipx not on PATH — try invoking it via Python module if installed Invoke-Python $Python @('-m', 'pipx', '--version') *>$null if ($LASTEXITCODE -eq 0) { Write-Info "Installing with python -m pipx..." - Invoke-Python $Python @('-m', 'pipx', 'install', $Package, '--force') + Invoke-Python $Python @('-m', 'pipx', 'install', $spec, '--force') return $? } return $false @@ -82,7 +95,7 @@ function Try-Pipx { function Try-UvTool { if (Has-Cmd 'uv') { Write-Info "Installing with uv tool..." - & uv tool install $Package --force + & uv tool install (Get-PackageSpec) --force return $? } return $false @@ -91,7 +104,7 @@ function Try-UvTool { function Try-PipUser { param([hashtable]$Python) Write-Info "Installing with pip --user..." - Invoke-Python $Python @('-m', 'pip', 'install', '--user', '--upgrade', $Package) + Invoke-Python $Python @('-m', 'pip', 'install', '--user', '--upgrade', (Get-PackageSpec)) return $? } @@ -119,6 +132,16 @@ function Add-LocalBinToPath { # === Main ================================================================= +if ($Version) { + # Reject anything that is not a plain version token so it cannot be + # smuggled into the pip spec. + if ($Version -notmatch '^[0-9A-Za-z.+-]+$') { + Write-Err "Invalid version '$Version'. Expected a version like 1.15.0." + exit 1 + } + Write-Info "Requested Dailybot CLI version: $Version" +} + Write-Info "Detecting Python..." $py = Find-Python if ($null -eq $py) { @@ -150,9 +173,9 @@ if (-not $installed) { Write-Err "All installation methods failed." Write-Host "" Write-Host " Try manually:" - Write-Host " pipx install $Package" + Write-Host " pipx install $(Get-PackageSpec)" Write-Host " # or" - Write-Host " pip install --user $Package" + Write-Host " pip install --user $(Get-PackageSpec)" exit 1 } diff --git a/install.ps1.sha256 b/install.ps1.sha256 index 2854648..462b020 100644 --- a/install.ps1.sha256 +++ b/install.ps1.sha256 @@ -1 +1 @@ -6136b953d9bc933206d729241531e02b4368ecbf5064b4441d12f64d200d5453 install.ps1 +ddf0ce82c29f3408fdd293f18eadab7542eb2758d6a6ec589fa2f9b8a6bf150e install.ps1 diff --git a/install.sh b/install.sh index 5973cc0..c6d6266 100755 --- a/install.sh +++ b/install.sh @@ -47,6 +47,54 @@ finish() { echo "" } +# --- Version selection --- +# Install a specific version instead of the latest. Provide it either as an +# environment variable or a CLI flag: +# curl -sSL https://cli.dailybot.com/install.sh | DAILYBOT_VERSION=1.15.0 bash +# curl -sSL https://cli.dailybot.com/install.sh | bash -s -- --version 1.15.0 +# An empty value means "install the latest published version". +VERSION="${DAILYBOT_VERSION:-}" + +while [ $# -gt 0 ]; do + case "$1" in + --version) + VERSION="${2:-}" + shift + [ $# -gt 0 ] && shift + ;; + --version=*) + VERSION="${1#--version=}" + shift + ;; + *) + shift + ;; + esac +done + +# Reject anything that is not a plain version token so it cannot be smuggled +# into the pip spec or the release download URL. +case "$VERSION" in + "") ;; + *[!0-9A-Za-z.+-]*) + error "Invalid version '$VERSION'. Expected a version like 1.15.0." + exit 1 + ;; +esac + +# Emit the pip requirement: "dailybot-cli" or "dailybot-cli==". +pip_spec() { + if [ -n "$VERSION" ]; then + printf '%s==%s' "$PACKAGE" "$VERSION" + else + printf '%s' "$PACKAGE" + fi +} + +if [ -n "$VERSION" ]; then + info "Requested Dailybot CLI version: $VERSION" +fi + # --- Detect OS --- OS="$(uname -s)" @@ -55,20 +103,26 @@ OS="$(uname -s)" # macOS → Homebrew # ============================================================================= if [ "$OS" = "Darwin" ]; then - if ! has brew; then - error "Homebrew is required on macOS." - echo "" - echo " Install Homebrew first:" - echo ' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' - echo "" - echo " Then re-run this script." - exit 1 - fi + # Homebrew always installs the latest formula version. When a specific + # version is requested we fall through to the pip path, which can pin it. + if [ -n "$VERSION" ]; then + info "Homebrew installs only the latest release; using pip to install $VERSION..." + else + if ! has brew; then + error "Homebrew is required on macOS." + echo "" + echo " Install Homebrew first:" + echo ' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' + echo "" + echo " Then re-run this script." + exit 1 + fi - info "Installing via Homebrew..." - brew install dailybothq/tap/dailybot - finish - exit 0 + info "Installing via Homebrew..." + brew install dailybothq/tap/dailybot + finish + exit 0 + fi fi # ============================================================================= @@ -86,11 +140,17 @@ if [ "$OS" = "Linux" ]; then return 1 fi - latest=$(curl -sI "https://github.com/$REPO/releases/latest" \ - | grep -i "^location:" | sed 's/.*tag\///' | tr -d '\r\n') + if [ -n "$VERSION" ]; then + # Pin the requested release tag; if its binary asset is missing + # the caller falls back to a pinned pip install. + latest="v$VERSION" + else + latest=$(curl -sI "https://github.com/$REPO/releases/latest" \ + | grep -i "^location:" | sed 's/.*tag\///' | tr -d '\r\n') - if [ -z "$latest" ]; then - return 1 + if [ -z "$latest" ]; then + return 1 + fi fi url="https://github.com/$REPO/releases/download/$latest/dailybot-linux-x86_64" @@ -157,7 +217,7 @@ installed=false # 1. pipx (preferred — isolated env, manages PATH) if ! $installed && has pipx; then info "Installing with pipx..." - if pipx install "$PACKAGE" --force 2>&1; then + if pipx install "$(pip_spec)" --force 2>&1; then installed=true else warn "pipx install failed, trying next method..." @@ -167,7 +227,7 @@ fi # 2. uv tool (same benefits as pipx) if ! $installed && has uv; then info "Installing with uv..." - if uv tool install "$PACKAGE" --force 2>&1; then + if uv tool install "$(pip_spec)" --force 2>&1; then installed=true else warn "uv install failed, trying next method..." @@ -177,7 +237,7 @@ fi # 3. pip inside an active virtualenv if ! $installed && in_virtualenv; then info "Virtualenv detected, installing with pip..." - if $PYTHON -m pip install --upgrade "$PACKAGE" 2>&1; then + if $PYTHON -m pip install --upgrade "$(pip_spec)" 2>&1; then installed=true else warn "pip install failed inside virtualenv." @@ -197,11 +257,11 @@ if ! $installed; then fi info "Installing with pip..." - if $PYTHON -m pip install --upgrade "$PACKAGE" 2>&1; then + if $PYTHON -m pip install --upgrade "$(pip_spec)" 2>&1; then installed=true else warn "System pip install failed, trying --user install..." - if $PYTHON -m pip install --user --upgrade "$PACKAGE" 2>&1; then + if $PYTHON -m pip install --user --upgrade "$(pip_spec)" 2>&1; then installed=true user_bin="$($PYTHON -c "import site; print(site.getusersitepackages().replace('/lib/python', '/bin').split('/lib/')[0] + '/bin')" 2>/dev/null || echo "$HOME/.local/bin")" @@ -224,9 +284,9 @@ if ! $installed; then error "All installation methods failed." echo "" echo " You can try manually:" - echo " pipx install $PACKAGE" + echo " pipx install $(pip_spec)" echo " # or" - echo " pip install $PACKAGE" + echo " pip install $(pip_spec)" exit 1 fi diff --git a/install.sh.sha256 b/install.sh.sha256 index 149526b..8c555f6 100644 --- a/install.sh.sha256 +++ b/install.sh.sha256 @@ -1 +1 @@ -c43edcc96230904e4ae018f0c587772353353e64ac85c32fc752df5fcb190070 install.sh +17442425a345f9fc14472ef0b4658c77464918dd529379d2e7a7edd4e41dd7c0 install.sh