# Copyright 2026 Alibaba Group # Licensed under the Apache License, Version 2.0 # # One-command installer for dws dev on native Windows (PowerShell). # Downloads the dev binary (dws.exe) + dingtalk-misc skill (hosts open-platform app docs) from the DingTalk-Real-AI GitHub Releases. # # Usage: # irm https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install-devapp.ps1 | iex # # 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_ARCH architecture override (amd64 or arm64) # DWS_INSTALL_DIR binary dir (default: ~/.local/bin) # DWS_NO_SKILLS set 1 to skip the dev skill $ErrorActionPreference = "Stop" $Repo = if ($env:DEVAPP_REPO) { $env:DEVAPP_REPO } else { "DingTalk-Real-AI/dingtalk-workspace-cli" } $Version = $env:DEVAPP_VERSION $InstallDir = if ($env:DWS_INSTALL_DIR) { $env:DWS_INSTALL_DIR } else { Join-Path $HOME ".local\bin" } $NoSkills = $env:DWS_NO_SKILLS -eq "1" $SkillName = "dingtalk-misc" # vercel-labs/skills agents.ts (c6f69c6). Each row is # id|universal|effective global directory; '-' means no global directory. $AgentRegistryRows = @( "aider-desk|0|.aider-desk\skills", "amp|1|.config\agents\skills", "antigravity|1|.gemini\antigravity\skills", "antigravity-cli|1|.gemini\antigravity-cli\skills", "astrbot|0|.astrbot\data\skills", "autohand-code|0|.autohand\skills", "augment|0|.augment\skills", "bob|0|.bob\skills", "claude-code|0|.claude\skills", "openclaw|0|.openclaw\skills", "cline|1|.agents\skills", "codearts-agent|0|.codeartsdoer\skills", "codebuddy|0|.codebuddy\skills", "codemaker|0|.codemaker\skills", "codestudio|0|.codestudio\skills", "codex|1|.codex\skills", "command-code|0|.commandcode\skills", "continue|0|.continue\skills", "cortex|0|.snowflake\cortex\skills", "crush|0|.config\crush\skills", "cursor|1|.cursor\skills", "deepagents|1|.deepagents\agent\skills", "devin|0|.config\devin\skills", "dexto|1|.agents\skills", "droid|0|.factory\skills", "eve|0|-", "firebender|1|.firebender\skills", "forgecode|0|.forge\skills", "gemini-cli|1|.gemini\skills", "github-copilot|1|.copilot\skills", "goose|0|.config\goose\skills", "grok|0|.grok\skills", "hermes-agent|0|.hermes\skills", "inference-sh|0|.inferencesh\skills", "jazz|0|.jazz\skills", "junie|0|.junie\skills", "iflow-cli|0|.iflow\skills", "kilo|0|.kilocode\skills", "kimchi|0|.config\kimchi\harness\skills", "kimi-code-cli|1|.agents\skills", "kiro-cli|0|.kiro\skills", "kode|0|.kode\skills", "lingma|0|.lingma\skills", "loaf|1|.agents\skills", "mcpjam|0|.mcpjam\skills", "minimax-code|0|.minimax\skills", "mistral-vibe|0|.vibe\skills", "moxby|0|.moxby\skills", "mux|0|.mux\skills", "opencode|1|.config\opencode\skills", "openhands|0|.openhands\skills", "ona|0|.ona\skills", "pi|0|.pi\agent\skills", "qoder|0|.qoder\skills", "qoder-cn|0|.qoder-cn\skills", "qwen-code|0|.qwen\skills", "replit|1|.config\agents\skills", "reasonix|0|.reasonix\skills", "rovodev|0|.rovodev\skills", "roo|0|.roo\skills", "tabnine-cli|0|.tabnine\agent\skills", "terramind|0|.terramind\skills", "tinycloud|0|.tinycloud\skills", "trae|0|.trae\skills", "trae-cn|0|.trae-cn\skills", "warp|1|.agents\skills", "windsurf|0|.codeium\windsurf\skills", "zed|1|.agents\skills", "zcode|0|.zcode\skills", "zencoder|0|.zencoder\skills", "zenflow|0|.zencoder\skills", "neovate|0|.neovate\skills", "pochi|0|.pochi\skills", "promptscript|1|-", "adal|0|.adal\skills", "universal|1|.config\agents\skills" ) $LegacyAgentCleanupRows = @( "dws-qoderwork|0|.qoderwork\skills", "dws-legacy-github|1|.github\skills", "dws-legacy-amp|1|.amp\skills", "dws-legacy-cline|1|.cline\skills", "dws-legacy-windsurf|1|.windsurf\skills" ) function Say($m) { Write-Host " $m" } function Die($m) { Write-Host " X $m" -ForegroundColor Red; exit 1 } function Resolve-AgentBase([string]$row) { $parts = $row.Split('|') $id = $parts[0] if ($parts[2] -eq "-") { return $null } switch ($id) { "autohand-code" { if ($env:AUTOHAND_HOME) { return Join-Path $env:AUTOHAND_HOME "skills" } } "claude-code" { if ($env:CLAUDE_CONFIG_DIR) { return Join-Path $env:CLAUDE_CONFIG_DIR "skills" } } "codex" { if ($env:CODEX_HOME) { return Join-Path $env:CODEX_HOME "skills" } } "grok" { if ($env:GROK_HOME) { return Join-Path $env:GROK_HOME "skills" } } "hermes-agent" { if ($env:HERMES_HOME) { return Join-Path $env:HERMES_HOME "skills" } } "mistral-vibe" { if ($env:VIBE_HOME) { return Join-Path $env:VIBE_HOME "skills" } } "openclaw" { foreach ($legacy in @(".openclaw", ".clawdbot", ".moltbot")) { $candidate = Join-Path $HOME $legacy if (Test-Path $candidate -PathType Container) { return Join-Path $candidate "skills" } } } { $_ -in @("amp", "replit", "universal") } { $xdg = if ($env:XDG_CONFIG_HOME) { $env:XDG_CONFIG_HOME } else { Join-Path $HOME ".config" } return Join-Path $xdg "agents\skills" } { $_ -in @("crush", "devin", "goose", "kimchi", "opencode") } { $xdg = if ($env:XDG_CONFIG_HOME) { $env:XDG_CONFIG_HOME } else { Join-Path $HOME ".config" } $child = switch ($id) { "crush" { "crush\skills" }; "devin" { "devin\skills" }; "goose" { "goose\skills" }; "kimchi" { "kimchi\harness\skills" }; default { "opencode\skills" } } return Join-Path $xdg $child } } return Join-Path $HOME $parts[2] } function Test-AgentBaseDetected([string]$id, [string]$base) { $parent = Split-Path $base -Parent switch ($id) { "kimchi" { return Test-Path (Split-Path $parent -Parent) -PathType Container } "tabnine-cli" { return Test-Path (Split-Path $parent -Parent) -PathType Container } "zcode" { return (Test-Path $parent -PathType Container) -or (Test-Path "/Applications/ZCode.app" -PathType Container) } "minimax-code" { return (Test-Path $parent -PathType Container) -or (Test-Path "/Applications/MiniMax Code.app" -PathType Container) } default { return Test-Path $parent -PathType Container } } } function Test-PathLexically([string]$path) { try { Get-Item -LiteralPath $path -Force -ErrorAction Stop | Out-Null; return $true } catch {} $parent = Split-Path $path -Parent; $leaf = Split-Path $path -Leaf if (!(Test-Path $parent -PathType Container)) { return $false } return $null -ne (Get-ChildItem -LiteralPath $parent -Force -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $leaf } | Select-Object -First 1) } function Backup-DevSkill([string]$path) { if (!(Test-PathLexically $path)) { return } $backupRoot = Join-Path $HOME ".dws\skill-backups\$([DateTime]::UtcNow.ToString('yyyyMMdd-HHmmss'))" New-Item -ItemType Directory -Path $backupRoot -Force -ErrorAction Stop | Out-Null $target = Join-Path $backupRoot (Split-Path $path -Leaf) while (Test-PathLexically $target) { $target += "-$([guid]::NewGuid().ToString('N'))" } Move-Item -LiteralPath $path -Destination $target -ErrorAction Stop } function Get-Arch { if ($env:DWS_ARCH -eq "amd64" -or $env:DWS_ARCH -eq "arm64") { return $env:DWS_ARCH } try { switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) { "X64" { return "amd64" } "Arm64" { return "arm64" } } } catch {} switch ($env:PROCESSOR_ARCHITECTURE) { "AMD64" { return "amd64" } "ARM64" { return "arm64" } default { Die "Could not detect architecture. Set DWS_ARCH to amd64 or arm64." } } } # 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). if (-not $Version) { try { $rel = Invoke-RestMethod -Uri "https://api.github.com/repos/$Repo/releases?per_page=1" ` -Headers @{ "User-Agent" = "dws-devapp-installer" } -UseBasicParsing $Version = $rel[0].tag_name } catch {} if (-not $Version) { Die "No release found on $Repo. Set DEVAPP_VERSION to a published release tag." } } $arch = Get-Arch $tmp = Join-Path $env:TEMP ("dws-dev-" + [System.Guid]::NewGuid().ToString()) New-Item -ItemType Directory -Path $tmp -Force | Out-Null Write-Host "" Say "dws dev installer (Windows, pre-built binary)" Say "Repo: $Repo" Say "Version: $Version" Say "Target: windows/$arch" Write-Host "" # 1) binary $asset = "dws-windows-$arch.zip" $zip = Join-Path $tmp $asset Say "Downloading $asset ..." try { Invoke-WebRequest -Uri "https://github.com/$Repo/releases/download/$Version/$asset" ` -OutFile $zip -UseBasicParsing } catch { Die "Binary download failed - does release $Version have $asset?" } Expand-Archive -Path $zip -DestinationPath $tmp -Force $exe = Get-ChildItem -Path $tmp -Recurse -Filter "dws.exe" | Select-Object -First 1 if (-not $exe) { Die "dws.exe not found inside $asset" } if (-not (Test-Path $InstallDir)) { New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null } Copy-Item -Path $exe.FullName -Destination (Join-Path $InstallDir "dws.exe") -Force Say "Binary -> $InstallDir\dws.exe" # 2) dev skill from the release's skills bundle if (-not $NoSkills) { try { $skzip = Join-Path $tmp "dws-skills.zip" Invoke-WebRequest -Uri "https://github.com/$Repo/releases/download/$Version/dws-skills.zip" ` -OutFile $skzip -UseBasicParsing $skdir = Join-Path $tmp "sk" Expand-Archive -Path $skzip -DestinationPath $skdir -Force $src = $null foreach ($c in @("multi\$SkillName", "skills\multi\$SkillName", "$SkillName")) { $p = Join-Path $skdir $c if (Test-Path (Join-Path $p "SKILL.md")) { $src = $p; break } } if ($src) { # cache so `dws skill setup --mode multi` can find a source later $cache = Join-Path $HOME ".dws\skills\multi\$SkillName" if (Test-Path $cache) { Remove-Item -Recurse -Force $cache } New-Item -ItemType Directory -Path $cache -Force | Out-Null Copy-Item -Path "$src\*" -Destination $cache -Recurse -Force $canonicalBase = Join-Path $HOME ".agents\skills" $canonical = Join-Path $canonicalBase $SkillName Backup-DevSkill $canonical New-Item -ItemType Directory -Path $canonical -Force | Out-Null Copy-Item -Path "$src\*" -Destination $canonical -Recurse -Force $installed = 1 $seen = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($row in @($AgentRegistryRows) + @($LegacyAgentCleanupRows)) { $parts = $row.Split('|'); $universal = $parts[1] -eq "1" $base = Resolve-AgentBase $row if ([string]::IsNullOrWhiteSpace($base)) { continue } $baseKey = [System.IO.Path]::GetFullPath($base).TrimEnd([char[]]@('\', '/')) if (!$seen.Add($baseKey) -or $baseKey -eq [System.IO.Path]::GetFullPath($canonicalBase).TrimEnd([char[]]@('\', '/'))) { continue } if (!(Test-AgentBaseDetected $parts[0] $base)) { continue } $dest = Join-Path $base $SkillName if ($universal) { Backup-DevSkill $dest; continue } New-Item -ItemType Directory -Path $base -Force | Out-Null Backup-DevSkill $dest try { New-Item -ItemType Junction -Path $dest -Target ([System.IO.Path]::GetFullPath($canonical)) -ErrorAction Stop | Out-Null } catch { New-Item -ItemType Directory -Path $dest -Force | Out-Null; Copy-Item -Path "$src\*" -Destination $dest -Recurse -Force } $installed++ } Say "Skill dingtalk-misc -> $installed agent dir(s)" } else { Say "(dingtalk-misc not found in skills bundle; skipped)" } } catch { Say "(skill install skipped: $($_.Exception.Message))" } } Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue Write-Host "" Say "Done. Next steps:" Say " dws version" Say " dws auth login" Say " dws dev --help --format json" Write-Host "" if (($env:Path -split ';') -notcontains $InstallDir) { Say "Note: $InstallDir is not on PATH. Add it (new terminal after):" Say " [Environment]::SetEnvironmentVariable('Path', `"`$env:Path;$InstallDir`", 'User')" }