#!/bin/sh # Copyright 2026 Alibaba Group # Licensed under the Apache License, Version 2.0 # # One-command installer for dws dev — pre-built binary, no build tools. # Downloads the dev binary + dingtalk-misc skill (hosts open-platform app docs) from the DingTalk-Real-AI GitHub Releases. # Requires only curl + tar (no go / make / git). # # Usage: # curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install-devapp.sh | sh # # Env (all optional): # DEVAPP_REPO repo holding dev releases (default: DingTalk-Real-AI/dingtalk-workspace-cli) # DEVAPP_VERSION pin a release tag (default: latest release) # DWS_INSTALL_DIR binary dir (default: ~/.local/bin) # DWS_NO_SKILLS set 1 to skip the dev skill set -eu DEVAPP_REPO="${DEVAPP_REPO:-DingTalk-Real-AI/dingtalk-workspace-cli}" DEVAPP_VERSION="${DEVAPP_VERSION:-}" INSTALL_DIR="${DWS_INSTALL_DIR:-$HOME/.local/bin}" NO_SKILLS="${DWS_NO_SKILLS:-0}" SKILL_NAME="dingtalk-misc" say() { printf ' %s\n' "$@"; } err() { printf ' ❌ %s\n' "$@" >&2; exit 1; } need_cmd() { command -v "$1" >/dev/null 2>&1 || err "Missing required command: $1"; } need_cmd curl detect_os() { case "$(uname -s)" in Linux*) echo linux ;; Darwin*) echo darwin ;; MINGW*|MSYS*|CYGWIN*) echo windows ;; # Git Bash / MSYS2 / Cygwin on Windows *) err "Unsupported OS: $(uname -s). On native Windows use install-devapp.ps1." ;; esac } detect_arch() { case "$(uname -m)" in x86_64|amd64) echo amd64 ;; arm64|aarch64) echo arm64 ;; *) err "Unsupported architecture: $(uname -m)" ;; esac } # Read the releases list (newest first) and take the top tag, so this also works # if a release is ever published as a prerelease (which /releases/latest skips). # Prefer `gh` CLI (authenticated, 5 000 req/h) over raw curl (60 req/h, easily rate-limited). resolve_version() { [ -n "$DEVAPP_VERSION" ] && return 0 # Try gh CLI first (authenticated, much higher rate limit) if command -v gh >/dev/null 2>&1; then DEVAPP_VERSION="$(gh api "repos/${DEVAPP_REPO}/releases?per_page=1" --jq '.[0].tag_name' 2>/dev/null || true)" [ -n "$DEVAPP_VERSION" ] && return 0 fi # Fallback: unauthenticated curl (may be rate-limited) _tmpfile="$(mktemp)" _http_code="$(curl -sSL -o "$_tmpfile" -w '%{http_code}' "https://api.github.com/repos/${DEVAPP_REPO}/releases?per_page=1" 2>/dev/null || echo "000")" if [ "$_http_code" = "403" ] || [ "$_http_code" = "429" ]; then rm -f "$_tmpfile" err "GitHub API rate limit hit (HTTP ${_http_code}). Install the GitHub CLI (gh) or set DEVAPP_VERSION explicitly." fi DEVAPP_VERSION="$(grep -m1 '"tag_name"' "$_tmpfile" | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/')" rm -f "$_tmpfile" [ -n "$DEVAPP_VERSION" ] || err "No release found on ${DEVAPP_REPO}. Set DEVAPP_VERSION to a published release tag." } install_skill() { bundle="$1" # extracted skills bundle dir src="" for c in "$bundle/multi/$SKILL_NAME" "$bundle/skills/multi/$SKILL_NAME" "$bundle/$SKILL_NAME"; do [ -f "$c/SKILL.md" ] && src="$c" && break done [ -n "$src" ] || { say " (dingtalk-misc not found in skills bundle; skipped)"; return 0; } # cache so `dws skill setup --mode multi` can find a source later cache="$HOME/.dws/skills/multi/$SKILL_NAME" rm -rf "$cache"; mkdir -p "$cache"; cp -R "$src/." "$cache/" installed=0; idx=0 for agent_dir in \ .agents/skills .claude/skills .cursor/skills .qoder/skills .qoderwork/skills \ .gemini/skills .codex/skills .github/skills .windsurf/skills .augment/skills \ .cline/skills .amp/skills .kiro/skills .trae/skills .openclaw/skills .hermes/skills \ .config/opencode/skills do base="$HOME/$agent_dir" if [ "$idx" -gt 0 ] && [ ! -e "$(dirname "$base")" ]; then idx=$((idx + 1)); continue; fi dest="$base/$SKILL_NAME"; rm -rf "$dest"; mkdir -p "$dest"; cp -R "$src/." "$dest/" installed=$((installed + 1)); idx=$((idx + 1)) done say "✅ Skill dingtalk-misc → ${installed} agent dir(s)" } main() { resolve_version os="$(detect_os)"; arch="$(detect_arch)" tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT INT TERM printf '\n' say "dws dev installer (pre-built binary)" say "Repo: ${DEVAPP_REPO}" say "Version: ${DEVAPP_VERSION}" say "Target: ${os}/${arch}" printf '\n' # 1) binary (already ad-hoc signed by CI; copy does not break the signature) if [ "$os" = "windows" ]; then asset="dws-windows-${arch}.zip"; binname="dws.exe" else asset="dws-${os}-${arch}.tar.gz"; binname="dws" fi say "⬇ Downloading ${asset} ..." curl -fsSL "https://github.com/${DEVAPP_REPO}/releases/download/${DEVAPP_VERSION}/${asset}" -o "$tmp/$asset" \ || err "Binary download failed — does release ${DEVAPP_VERSION} have ${asset}?" if [ "$os" = "windows" ]; then need_cmd unzip; unzip -q "$tmp/$asset" -d "$tmp" else need_cmd tar; tar -xzf "$tmp/$asset" -C "$tmp" fi [ -f "$tmp/$binname" ] || err "${binname} not found inside ${asset}" mkdir -p "$INSTALL_DIR" cp "$tmp/$binname" "$INSTALL_DIR/$binname"; chmod +x "$INSTALL_DIR/$binname" 2>/dev/null || true say "✅ Binary → ${INSTALL_DIR}/${binname}" # 2) dev skill from the release's skills bundle if [ "$NO_SKILLS" != "1" ]; then if curl -fsSL "https://github.com/${DEVAPP_REPO}/releases/download/${DEVAPP_VERSION}/dws-skills.zip" -o "$tmp/skills.zip" 2>/dev/null; then mkdir -p "$tmp/sk" if command -v unzip >/dev/null 2>&1; then unzip -q "$tmp/skills.zip" -d "$tmp/sk"; else tar -xf "$tmp/skills.zip" -C "$tmp/sk"; fi say "" install_skill "$tmp/sk" else say " (no dws-skills.zip in release ${DEVAPP_VERSION}; skill skipped)" fi fi printf '\n' say "🎉 Done. Next steps:" say " dws version" say " dws auth login" say " dws dev --help --format json" say " dws dev app list --format json" printf '\n' case ":$PATH:" in *":$INSTALL_DIR:"*) ;; *) say "Note: ${INSTALL_DIR} is not on \$PATH — add it so 'dws' is found." ;; esac } main