From 49134966e31892d13834c9ea5f8ff0d7094f6521 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Thu, 13 Aug 2026 21:56:58 -0700 Subject: [PATCH 1/4] chore(versions): refresh agent CLI pins - go 1.26.5 -> 1.26.6 - claude-code 2.1.226 -> 2.1.232 - cctrace 0.38.1 -> 0.40.0 - gemini-cli 0.54.4 -> 0.55.1 - grok-cli 1.0.0 -> 1.0.3 - kimi-code 0.34.0 -> 0.36.0 - opencode 1.18.15 -> 1.18.18 - ccx v0.14.0 -> v0.15.0 - cloakbrowser 0.5.6 -> 0.5.7 Co-Authored-By: Claude Fable 5 --- versions.env | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/versions.env b/versions.env index 14153ed..8bd8c04 100644 --- a/versions.env +++ b/versions.env @@ -2,27 +2,27 @@ # Update this file when we intentionally move default toolchain or CLI versions. NODE_MAJOR=22 -GO_VERSION=1.26.5 +GO_VERSION=1.26.6 PYTHON_VERSION=3.14t DELTA_VERSION=0.19.2 TMUX_VERSION=3.6a TMUX_SHA256=b6d8d9c76585db8ef5fa00d4931902fa4b8cbe8166f528f44fc403961a3f3759 -CLAUDE_CODE_VERSION=2.1.226 -CCTRACE_VERSION=0.38.1 +CLAUDE_CODE_VERSION=2.1.232 +CCTRACE_VERSION=0.40.0 CODEX_VERSION=0.147.0 -GEMINI_CLI_VERSION=0.54.4 -GROK_CLI_VERSION=1.0.0 -KIMI_CODE_VERSION=0.34.0 -OPENCODE_VERSION=1.18.15 +GEMINI_CLI_VERSION=0.55.1 +GROK_CLI_VERSION=1.0.3 +KIMI_CODE_VERSION=0.36.0 +OPENCODE_VERSION=1.18.18 PI_CODING_AGENT_VERSION=0.84.1 -CCX_VERSION=v0.14.0 +CCX_VERSION=v0.15.0 COPILOT_API_VERSION=0ea08febdd7e3e055b03dd298bf57e669500b5c1 PLAYWRIGHT_VERSION=1.62.1 # CloakBrowser npm wrapper version. This also pins the Chromium binary: # the wrapper hardcodes per-arch free-binary versions (linux-x64 146.x.x.5, # linux-arm64 146.x.x.3), so bumping the wrapper is what moves Chromium. -CLOAKBROWSER_WRAPPER_VERSION=0.5.6 +CLOAKBROWSER_WRAPPER_VERSION=0.5.7 # Kimi WebBridge daemon + extension. No npm package; resolved from # cdn.kimi.com/webbridge/latest/version.json. Not consumed by any # build yet — Dockerfile.cloak starts baking it in #543. From 2612f866778336f6351322d1ff99206cd02d21df Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Thu, 13 Aug 2026 22:10:57 -0700 Subject: [PATCH 2/4] feat(agents): add dsh as the 8th agent - agents/dsh.sh: default credentials mounts ~/.dsh rw; api-key passes DEEPSEEK_API_KEY as env, mounts nothing -- dsh resolves env BEFORE .credentials.yaml (reverse of pi), so no blank overlay and the host key is scrubbed from credentials mode where it would outrank the mounted credentials - unattended posture: DSH_PERMISSION_MODE=danger-full-access (env is the only switch; dsh scrubs DSH_* from project-discovered env, so only deva's injection counts); DSH_HOME pinned to /home/deva/.dsh (developer preview, defaults can move; the issue's ~/n worry was a docs misread -- real default is ~/.dsh) - deva.sh registration: auth tag, dsh_version label, canonical .dsh mounts, autolink, status walk, env scrub, config-home scaffolding - pin DSH_VERSION=0.1.0-rc.6 (@deepseek-ai/dsh) through versions.env, Dockerfiles, Makefile, install/pin/resolve/upgrade scripts, CI, nightly-images, release workflows; no updater exists, nothing to neutralize - scripts/test-dsh-auth.sh: 13 hermetic dry-run asserts, wired into CI - skills interop: dsh reads ~/.agents/skills + /.agents/skills (already wired for claude); plugins surface held back on purpose (manifest format in flux) Close #556 Co-Authored-By: Claude Fable 5 --- .deva.example | 4 +- .github/workflows/ci.yml | 11 ++++ .github/workflows/nightly-images.yml | 4 ++ .github/workflows/release.yml | 4 ++ CHANGELOG.md | 20 ++++++ DEV-LOGS.md | 5 ++ Dockerfile | 2 + Dockerfile.rust | 2 + Makefile | 17 ++++- README.md | 5 +- README.zh-CN.md | 5 +- agents/dsh.sh | 85 ++++++++++++++++++++++++ agents/shared_auth.sh | 4 ++ deva.sh | 68 +++++++++++++++++--- docs/advanced-usage.md | 9 +-- docs/authentication.md | 44 +++++++++++++ docs/how-it-works.md | 10 +-- docs/index.md | 4 +- docs/philosophy.md | 2 +- docs/quick-start.md | 10 +++ docs/troubleshooting.md | 2 +- install.sh | 3 + llms.txt | 6 +- scripts/install-agent-tooling.sh | 10 ++- scripts/release-utils.sh | 1 + scripts/resolve-tool-versions.sh | 1 + scripts/test-dsh-auth.sh | 93 +++++++++++++++++++++++++++ scripts/test-install-agent-tooling.sh | 4 +- scripts/update-version-pins.sh | 2 + scripts/version-pins.sh | 2 + scripts/version-upgrade.sh | 16 ++++- versions.env | 1 + 32 files changed, 419 insertions(+), 37 deletions(-) create mode 100644 agents/dsh.sh create mode 100755 scripts/test-dsh-auth.sh diff --git a/.deva.example b/.deva.example index 1ed37a0..8016826 100644 --- a/.deva.example +++ b/.deva.example @@ -61,10 +61,10 @@ EPHEMERAL=false # Hybrid Agent Setup: # # Hybrid is the DEFAULT. deva walks every populated subdir under -# ~/.config/deva/ (claude, codex, gemini, grok, kimi, opencode, pi) and mounts each agent's +# ~/.config/deva/ (claude, codex, gemini, grok, kimi, opencode, pi, dsh) and mounts each agent's # canonical entries into the container. Populated = you either # hand-created the subdir or autolink symlinked it from legacy -# ~/.claude, ~/.codex, ~/.gemini, ~/.grok, ~/.kimi-code, ~/.pi, or the +# ~/.claude, ~/.codex, ~/.gemini, ~/.grok, ~/.kimi-code, ~/.pi, ~/.dsh, or the # opencode XDG dirs (~/.config/opencode + ~/.local/share/opencode + # ~/.local/state/opencode) on first run. # diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87d1f63..34e080e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,6 +86,7 @@ jobs: --build-arg KIMI_CODE_VERSION="${{ steps.pins.outputs.kimi_code_version }}" \ --build-arg OPENCODE_VERSION="${{ steps.pins.outputs.opencode_version }}" \ --build-arg PI_CODING_AGENT_VERSION="${{ steps.pins.outputs.pi_coding_agent_version }}" \ + --build-arg DSH_VERSION="${{ steps.pins.outputs.dsh_version }}" \ --build-arg CCX_VERSION="${{ steps.pins.outputs.ccx_version }}" \ --build-arg COPILOT_API_VERSION="${{ steps.pins.outputs.copilot_api_version }}" \ . @@ -111,6 +112,7 @@ jobs: KIMI_CODE_VERSION="${{ steps.pins.outputs.kimi_code_version }}" \ OPENCODE_VERSION="${{ steps.pins.outputs.opencode_version }}" \ PI_CODING_AGENT_VERSION="${{ steps.pins.outputs.pi_coding_agent_version }}" \ + DSH_VERSION="${{ steps.pins.outputs.dsh_version }}" \ CCX_VERSION="${{ steps.pins.outputs.ccx_version }}" \ COPILOT_API_VERSION="${{ steps.pins.outputs.copilot_api_version }}" \ PLAYWRIGHT_VERSION="${{ steps.pins.outputs.playwright_version }}" \ @@ -139,6 +141,7 @@ jobs: deva.sh kimi -Q -- --version deva.sh opencode -Q -- --version deva.sh pi -Q -- --version + deva.sh dsh -Q -- --version - name: Smoke Claude --chrome mount assembly shell: bash @@ -208,6 +211,14 @@ jobs: DEVA_DOCKER_TAG=ci \ ./scripts/test-pi-auth.sh + - name: Smoke dsh auth wiring + shell: bash + run: | + set -euo pipefail + DEVA_DOCKER_IMAGE=deva-smoke \ + DEVA_DOCKER_TAG=ci \ + ./scripts/test-dsh-auth.sh + - name: Smoke version targets shell: bash run: | diff --git a/.github/workflows/nightly-images.yml b/.github/workflows/nightly-images.yml index a6aa21c..c298137 100644 --- a/.github/workflows/nightly-images.yml +++ b/.github/workflows/nightly-images.yml @@ -58,6 +58,7 @@ jobs: kimi_code_version: ${{ steps.versions.outputs.kimi_code_version }} opencode_version: ${{ steps.versions.outputs.opencode_version }} pi_coding_agent_version: ${{ steps.versions.outputs.pi_coding_agent_version }} + dsh_version: ${{ steps.versions.outputs.dsh_version }} ccx_version: ${{ steps.versions.outputs.ccx_version }} copilot_api_version: ${{ steps.versions.outputs.copilot_api_version }} steps: @@ -88,6 +89,7 @@ jobs: - Kimi Code: \`${{ steps.versions.outputs.kimi_code_version }}\` - opencode: \`${{ steps.versions.outputs.opencode_version }}\` - pi: \`${{ steps.versions.outputs.pi_coding_agent_version }}\` + - dsh: \`${{ steps.versions.outputs.dsh_version }}\` - ccx: \`${{ steps.versions.outputs.ccx_version }}\` - Copilot API: \`${{ steps.versions.outputs.copilot_api_version }}\` - Stamp: \`${{ steps.versions.outputs.stamp }}\` @@ -152,6 +154,7 @@ jobs: KIMI_CODE_VERSION=${{ needs.resolve-versions.outputs.kimi_code_version }} OPENCODE_VERSION=${{ needs.resolve-versions.outputs.opencode_version }} PI_CODING_AGENT_VERSION=${{ needs.resolve-versions.outputs.pi_coding_agent_version }} + DSH_VERSION=${{ needs.resolve-versions.outputs.dsh_version }} CCX_VERSION=${{ needs.resolve-versions.outputs.ccx_version }} COPILOT_API_VERSION=${{ needs.resolve-versions.outputs.copilot_api_version }} @@ -209,6 +212,7 @@ jobs: KIMI_CODE_VERSION=${{ needs.resolve-versions.outputs.kimi_code_version }} OPENCODE_VERSION=${{ needs.resolve-versions.outputs.opencode_version }} PI_CODING_AGENT_VERSION=${{ needs.resolve-versions.outputs.pi_coding_agent_version }} + DSH_VERSION=${{ needs.resolve-versions.outputs.dsh_version }} CCX_VERSION=${{ needs.resolve-versions.outputs.ccx_version }} PLAYWRIGHT_VERSION=${{ needs.load-version-pins.outputs.playwright_version }} RUST_TOOLCHAINS=${{ needs.load-version-pins.outputs.rust_toolchains }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a673800..8c8466a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,6 +54,7 @@ jobs: kimi_code_version: ${{ steps.pins.outputs.kimi_code_version }} opencode_version: ${{ steps.pins.outputs.opencode_version }} pi_coding_agent_version: ${{ steps.pins.outputs.pi_coding_agent_version }} + dsh_version: ${{ steps.pins.outputs.dsh_version }} ccx_version: ${{ steps.pins.outputs.ccx_version }} copilot_api_version: ${{ steps.pins.outputs.copilot_api_version }} playwright_version: ${{ steps.pins.outputs.playwright_version }} @@ -93,6 +94,7 @@ jobs: - Kimi Code: \`${{ steps.pins.outputs.kimi_code_version }}\` - opencode: \`${{ steps.pins.outputs.opencode_version }}\` - pi: \`${{ steps.pins.outputs.pi_coding_agent_version }}\` + - dsh: \`${{ steps.pins.outputs.dsh_version }}\` - ccx: \`${{ steps.pins.outputs.ccx_version }}\` - Copilot API: \`${{ steps.pins.outputs.copilot_api_version }}\` - Playwright: \`${{ steps.pins.outputs.playwright_version }}\` @@ -160,6 +162,7 @@ jobs: KIMI_CODE_VERSION=${{ needs.load-version-pins.outputs.kimi_code_version }} OPENCODE_VERSION=${{ needs.load-version-pins.outputs.opencode_version }} PI_CODING_AGENT_VERSION=${{ needs.load-version-pins.outputs.pi_coding_agent_version }} + DSH_VERSION=${{ needs.load-version-pins.outputs.dsh_version }} CCX_VERSION=${{ needs.load-version-pins.outputs.ccx_version }} COPILOT_API_VERSION=${{ needs.load-version-pins.outputs.copilot_api_version }} @@ -219,6 +222,7 @@ jobs: KIMI_CODE_VERSION=${{ needs.load-version-pins.outputs.kimi_code_version }} OPENCODE_VERSION=${{ needs.load-version-pins.outputs.opencode_version }} PI_CODING_AGENT_VERSION=${{ needs.load-version-pins.outputs.pi_coding_agent_version }} + DSH_VERSION=${{ needs.load-version-pins.outputs.dsh_version }} CCX_VERSION=${{ needs.load-version-pins.outputs.ccx_version }} PLAYWRIGHT_VERSION=${{ needs.load-version-pins.outputs.playwright_version }} RUST_TOOLCHAINS=${{ needs.load-version-pins.outputs.rust_toolchains }} diff --git a/CHANGELOG.md b/CHANGELOG.md index fc7dec4..72390a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 scripts/test-help-surface.sh pins the routing (24 asserts) ### Added +- dsh (deepseek-ai/deepseek-harness, DeepSeek's official agent harness, + launched 2026-08-13) as the 8th agent: `deva.sh dsh` (#556). One + dot-dir — everything persists under `~/.dsh` (`$DSH_HOME`, pinned by + deva; the issue's `~/n` worry was a docs misread): .credentials.yaml, + settings.yaml, profiles/, skills/. Default `credentials` mounts + `~/.dsh` writable; `--auth-with api-key` passes DEEPSEEK_API_KEY as + env and mounts nothing — dsh reads env BEFORE .credentials.yaml + (reverse of pi), so the injected key always decides billing and no + blank overlay is needed; for the same reason a host key is scrubbed + from credentials-mode runs. Unattended posture via + DSH_PERMISSION_MODE=danger-full-access (env is the only switch; dsh + scrubs DSH_* from project env, so only deva's injection counts). + No updater to neutralize (plain npm distribution, no phone-home). + Skills interop free of charge: dsh reads ~/.agents/skills + + /.agents/skills, the dirs deva already wires for claude. + Plugin/marketplace surface deliberately not wired (manifest format + in flux pre-launch). Pinned hard: @deepseek-ai/dsh@0.1.0-rc.6 + (DSH_VERSION) — developer preview, every bump is a potential break; + nightly-images tracks drift. scripts/test-dsh-auth.sh (13 asserts). + `--trace` rejected until cctrace ships a dsh profile - pi (earendil-works/pi, the minimal multi-provider agent harness by badlogic) as the 7th agent: `deva.sh pi` (#552). One dot-dir — everything persists under `~/.pi/agent/` (auth.json, sessions, diff --git a/DEV-LOGS.md b/DEV-LOGS.md index 710cb98..4ed5a0b 100644 --- a/DEV-LOGS.md +++ b/DEV-LOGS.md @@ -13,6 +13,11 @@ - Minimal markdown markers, no unnecessary formatting, minimal emojis. - Reference issue numbers in the format `#` for easy linking. +# [2026-08-13] Dev Log: dsh as the 8th agent #556 +- Why: DeepSeek shipped its official harness (deepseek-ai/deepseek-harness) on launch day; developer preview with promised breaking changes is exactly what a container posture is for. +- What: agents/dsh.sh (default `credentials` mounts ~/.dsh rw; api-key passes DEEPSEEK_API_KEY, mounts nothing — dsh resolves env BEFORE .credentials.yaml, reverse of pi, so no blank overlay and host-key scrub matters in credentials mode), deva.sh registration (auth tag, dsh_version label, mounts, autolink ~/.dsh, status, env scrub), DSH_PERMISSION_MODE=danger-full-access + DSH_HOME pinned at launch, image pin DSH_VERSION=0.1.0-rc.6 through versions.env/Makefile/scripts/CI, scripts/test-dsh-auth.sh (13 asserts). Traps dodged: DSH_HOME default is ~/.dsh not `~/n` (issue text was a docs misread); DSH_* is scrubbed from project-discovered env by dsh itself, so the permission mode must ride the invoking env — which deva's -e injection is; no updater exists, nothing to neutralize. +- Result: 8 agents. test-dsh-auth 13/13, existing auth suites green. Skills interop (~/.agents/skills) works with the wiring claude already has. --trace deferred (no cctrace dsh profile); plugins surface held back on purpose. + # [2026-08-12] Dev Log: layered help #554 - Why: the help surface was one 130-line wall; `ps --help` printed the same wall; cloak VNC passwords sat next to -v. First shot of the framework-vnext wrapper-UX track (W1). - What: usage() split into a 43-line summary + usage_full() (`help all`) + usage_command() per management command. Routing rides the existing PRE_ARGS scan: help tokens set HELP_REQUESTED instead of exiting, so the same pass that sees --help already knows MANAGEMENT_MODE — `help ps`, `--help ps`, `ps --help` all converge; `all` only counts after a help token so it stays a free word otherwise. tmux untouched (dispatches before the scan); launch help stays global; sentinel passthrough proven untouched by dry-run assert. diff --git a/Dockerfile b/Dockerfile index 8aa8466..ac73ddf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -215,6 +215,7 @@ ARG GROK_CLI_VERSION=0.2.93 ARG KIMI_CODE_VERSION=0.28.0 ARG OPENCODE_VERSION=1.18.14 ARG PI_CODING_AGENT_VERSION=0.84.1 +ARG DSH_VERSION=0.1.0-rc.6 # Record key tool versions as labels for quick inspection LABEL org.opencontainers.image.claude_code_version=${CLAUDE_CODE_VERSION} @@ -224,6 +225,7 @@ LABEL org.opencontainers.image.grok_cli_version=${GROK_CLI_VERSION} LABEL org.opencontainers.image.kimi_code_version=${KIMI_CODE_VERSION} LABEL org.opencontainers.image.opencode_version=${OPENCODE_VERSION} LABEL org.opencontainers.image.pi_coding_agent_version=${PI_CODING_AGENT_VERSION} +LABEL org.opencontainers.image.dsh_version=${DSH_VERSION} ARG CCX_VERSION=v0.7.0 diff --git a/Dockerfile.rust b/Dockerfile.rust index 2ba0e16..9d68540 100644 --- a/Dockerfile.rust +++ b/Dockerfile.rust @@ -15,6 +15,7 @@ ARG GROK_CLI_VERSION=0.2.93 ARG KIMI_CODE_VERSION=0.28.0 ARG OPENCODE_VERSION=1.18.14 ARG PI_CODING_AGENT_VERSION=0.84.1 +ARG DSH_VERSION=0.1.0-rc.6 ARG CCX_VERSION=v0.7.0 ARG PLAYWRIGHT_VERSION=1.60.0 ARG RUST_TOOLCHAINS="stable" @@ -28,6 +29,7 @@ LABEL org.opencontainers.image.grok_cli_version=${GROK_CLI_VERSION} LABEL org.opencontainers.image.kimi_code_version=${KIMI_CODE_VERSION} LABEL org.opencontainers.image.opencode_version=${OPENCODE_VERSION} LABEL org.opencontainers.image.pi_coding_agent_version=${PI_CODING_AGENT_VERSION} +LABEL org.opencontainers.image.dsh_version=${DSH_VERSION} LABEL org.opencontainers.image.ccx_version=${CCX_VERSION} LABEL org.opencontainers.image.playwright_version=${PLAYWRIGHT_VERSION} diff --git a/Makefile b/Makefile index 50dadde..e2b4660 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,7 @@ GROK_CLI_VERSION ?= 0.2.93 KIMI_CODE_VERSION ?= 0.28.0 OPENCODE_VERSION ?= 1.18.14 PI_CODING_AGENT_VERSION ?= 0.84.1 +DSH_VERSION ?= 0.1.0-rc.6 CCX_VERSION ?= v0.1.4 COPILOT_API_VERSION ?= 0ea08febdd7e3e055b03dd298bf57e669500b5c1 PLAYWRIGHT_VERSION ?= 1.60.0 @@ -94,6 +95,7 @@ AGENT_BUILD_ARGS := \ --build-arg KIMI_CODE_VERSION=$(KIMI_CODE_VERSION) \ --build-arg OPENCODE_VERSION=$(OPENCODE_VERSION) \ --build-arg PI_CODING_AGENT_VERSION=$(PI_CODING_AGENT_VERSION) \ + --build-arg DSH_VERSION=$(DSH_VERSION) \ --build-arg CCX_VERSION=$(CCX_VERSION) MAIN_BUILD_ARGS := $(TOOLCHAIN_BUILD_ARGS) $(AGENT_BUILD_ARGS) \ @@ -123,6 +125,7 @@ VERSION_QUERY_OVERRIDES := \ $(if $(filter command line environment environment\ override override,$(origin KIMI_CODE_VERSION)),KIMI_CODE_VERSION=$(KIMI_CODE_VERSION)) \ $(if $(filter command line environment environment\ override override,$(origin OPENCODE_VERSION)),OPENCODE_VERSION=$(OPENCODE_VERSION)) \ $(if $(filter command line environment environment\ override override,$(origin PI_CODING_AGENT_VERSION)),PI_CODING_AGENT_VERSION=$(PI_CODING_AGENT_VERSION)) \ + $(if $(filter command line environment environment\ override override,$(origin DSH_VERSION)),DSH_VERSION=$(DSH_VERSION)) \ $(if $(filter command line environment environment\ override override,$(origin CCX_VERSION)),CCX_VERSION=$(CCX_VERSION)) \ $(if $(filter command line environment environment\ override override,$(origin COPILOT_API_VERSION)),COPILOT_API_VERSION=$(COPILOT_API_VERSION)) \ $(if $(filter command line environment environment\ override override,$(origin PLAYWRIGHT_VERSION)),PLAYWRIGHT_VERSION=$(PLAYWRIGHT_VERSION)) \ @@ -171,10 +174,11 @@ build-main: build-network-check prev_kimi=$$(docker inspect --format='{{ index .Config.Labels "org.opencontainers.image.kimi_code_version" }}' $(MAIN_IMAGE) 2>/dev/null || true); \ prev_opencode=$$(docker inspect --format='{{ index .Config.Labels "org.opencontainers.image.opencode_version" }}' $(MAIN_IMAGE) 2>/dev/null || true); \ prev_pi=$$(docker inspect --format='{{ index .Config.Labels "org.opencontainers.image.pi_coding_agent_version" }}' $(MAIN_IMAGE) 2>/dev/null || true); \ + prev_dsh=$$(docker inspect --format='{{ index .Config.Labels "org.opencontainers.image.dsh_version" }}' $(MAIN_IMAGE) 2>/dev/null || true); \ fmt() { v="$$1"; if [ -z "$$v" ] || [ "$$v" = "" ]; then echo "-"; else case "$$v" in v*) echo "$$v";; *) echo "v$$v";; esac; fi; }; \ - curC=$$(fmt "$$prev_claude"); curX=$$(fmt "$$prev_codex"); curG=$$(fmt "$$prev_gemini"); curK=$$(fmt "$$prev_grok"); curKi=$$(fmt "$$prev_kimi"); curO=$$(fmt "$$prev_opencode"); curP=$$(fmt "$$prev_pi"); \ - tgtC=$$(fmt "$(CLAUDE_CODE_VERSION)"); tgtX=$$(fmt "$(CODEX_VERSION)"); tgtG=$$(fmt "$(GEMINI_CLI_VERSION)"); tgtK=$$(fmt "$(GROK_CLI_VERSION)"); tgtKi=$$(fmt "$(KIMI_CODE_VERSION)"); tgtO=$$(fmt "$(OPENCODE_VERSION)"); tgtP=$$(fmt "$(PI_CODING_AGENT_VERSION)"); \ - if [ "$$curC" = "$$tgtC" ] && [ "$$curX" = "$$tgtX" ] && [ "$$curG" = "$$tgtG" ] && [ "$$curK" = "$$tgtK" ] && [ "$$curKi" = "$$tgtKi" ] && [ "$$curO" = "$$tgtO" ] && [ "$$curP" = "$$tgtP" ]; then \ + curC=$$(fmt "$$prev_claude"); curX=$$(fmt "$$prev_codex"); curG=$$(fmt "$$prev_gemini"); curK=$$(fmt "$$prev_grok"); curKi=$$(fmt "$$prev_kimi"); curO=$$(fmt "$$prev_opencode"); curP=$$(fmt "$$prev_pi"); curD=$$(fmt "$$prev_dsh"); \ + tgtC=$$(fmt "$(CLAUDE_CODE_VERSION)"); tgtX=$$(fmt "$(CODEX_VERSION)"); tgtG=$$(fmt "$(GEMINI_CLI_VERSION)"); tgtK=$$(fmt "$(GROK_CLI_VERSION)"); tgtKi=$$(fmt "$(KIMI_CODE_VERSION)"); tgtO=$$(fmt "$(OPENCODE_VERSION)"); tgtP=$$(fmt "$(PI_CODING_AGENT_VERSION)"); tgtD=$$(fmt "$(DSH_VERSION)"); \ + if [ "$$curC" = "$$tgtC" ] && [ "$$curX" = "$$tgtX" ] && [ "$$curG" = "$$tgtG" ] && [ "$$curK" = "$$tgtK" ] && [ "$$curKi" = "$$tgtKi" ] && [ "$$curO" = "$$tgtO" ] && [ "$$curP" = "$$tgtP" ] && [ "$$curD" = "$$tgtD" ]; then \ echo "Claude: $$tgtC (no change)"; \ echo "Codex: $$tgtX (no change)"; \ echo "Gemini: $$tgtG (no change)"; \ @@ -182,6 +186,7 @@ build-main: build-network-check echo "Kimi: $$tgtKi (no change)"; \ echo "opencode: $$tgtO (no change)"; \ echo "pi: $$tgtP (no change)"; \ + echo "dsh: $$tgtD (no change)"; \ echo "Already up-to-date"; \ else \ if [ "$$curC" = "$$tgtC" ]; then \ @@ -219,6 +224,11 @@ build-main: build-network-check else \ echo "pi: $$curP -> $$tgtP"; \ fi; \ + if [ "$$curD" = "$$tgtD" ]; then \ + echo "dsh: $$tgtD (no change)"; \ + else \ + echo "dsh: $$curD -> $$tgtD"; \ + fi; \ fi @echo "Hint: override via GO_VERSION=... CLAUDE_CODE_VERSION=... or run 'make versions-pin'" docker build $(DOCKER_BUILD_FLAGS) -f $(DOCKERFILE) $(MAIN_BUILD_ARGS) -t $(MAIN_IMAGE) . @@ -519,6 +529,7 @@ help: @echo " KIMI_CODE_VERSION Kimi Code CLI version (default: $(KIMI_CODE_VERSION))" @echo " OPENCODE_VERSION opencode CLI version (default: $(OPENCODE_VERSION))" @echo " PI_CODING_AGENT_VERSION pi CLI version (default: $(PI_CODING_AGENT_VERSION))" + @echo " DSH_VERSION dsh CLI version (default: $(DSH_VERSION))" @echo " CCX_VERSION Atlas CLI version (default: $(CCX_VERSION))" @echo " PLAYWRIGHT_VERSION Playwright version (default: $(PLAYWRIGHT_VERSION))" @echo " CLOAKBROWSER_WRAPPER_VERSION CloakBrowser npm wrapper version (default: $(CLOAKBROWSER_WRAPPER_VERSION))" diff --git a/README.md b/README.md index 36ee002..e00d2a6 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ English | [简体中文](README.zh-CN.md) [![Docs](https://img.shields.io/badge/docs-docs.deva.sh-111111)](https://docs.deva.sh) [![License](https://img.shields.io/github/license/thevibeworks/deva)](LICENSE) -Run Claude Code, Codex, Gemini, Grok, Kimi, opencode, and pi inside Docker without pretending the agents' own permission prompts are the thing keeping you safe. +Run Claude Code, Codex, Gemini, Grok, Kimi, opencode, pi, and dsh inside Docker without pretending the agents' own permission prompts are the thing keeping you safe. The container is the sandbox, mounts are the contract. What that buys you: -- Full-speed agents. Permission prompts exist because the blast radius is your host. Make the blast radius a container and YOLO stops being reckless — all seven agents run with their permission systems off (`claude --dangerously-skip-permissions`, `codex --dangerously-bypass-approvals-and-sandbox`, `gemini --yolo`, `grok --always-approve`, `kimi --yolo`, opencode via `OPENCODE_PERMISSION` allow-all, pi has no permission system by design). Worst case dies with the container. +- Full-speed agents. Permission prompts exist because the blast radius is your host. Make the blast radius a container and YOLO stops being reckless — all eight agents run with their permission systems off (`claude --dangerously-skip-permissions`, `codex --dangerously-bypass-approvals-and-sandbox`, `gemini --yolo`, `grok --always-approve`, `kimi --yolo`, opencode via `OPENCODE_PERMISSION` allow-all, pi has no permission system by design, dsh via `DSH_PERMISSION_MODE=danger-full-access`). Worst case dies with the container. - The vendor's code never sees your host. Agent CLIs are fast-moving npm trees with auto-updaters. Here they are born inside the image: no `~/.ssh`, no `~/.aws`, no shell env soup, no browser profiles. Nothing to harvest. - Everything that crosses the boundary is explicit. Files: mounts. Secrets: the env you pass. Network: the posture you pick — default bridge, your proxy, `--host-net`, or nothing. If you didn't wire it, the agent doesn't have it. - Identity is a launch flag, not a global singleton. Per-agent config homes under `~/.config/deva/`, `--auth-with` / `--config-home` for the second account or API-key billing. Switch accounts per run; project, sessions, and container state stay put. @@ -41,6 +41,7 @@ deva.sh grok deva.sh kimi deva.sh opencode deva.sh pi +deva.sh dsh deva.sh claude --rm # throwaway container deva.sh claude --debug --dry-run # inspect the docker run before trusting it diff --git a/README.zh-CN.md b/README.zh-CN.md index 5311b1c..eab0d9c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -7,11 +7,11 @@ [![Docs](https://img.shields.io/badge/docs-docs.deva.sh-111111)](https://docs.deva.sh) [![License](https://img.shields.io/github/license/thevibeworks/deva)](LICENSE) -在 Docker 里跑 Claude Code、Codex、Gemini、Grok、Kimi、opencode、pi 七个 agent CLI,并且不再假装 agent 自带的权限弹窗是在保护你。 +在 Docker 里跑 Claude Code、Codex、Gemini、Grok、Kimi、opencode、pi、dsh 八个 agent CLI,并且不再假装 agent 自带的权限弹窗是在保护你。 容器即沙箱,挂载即契约。这换来的是: -- 全速跑 agent。权限弹窗存在的理由是爆炸半径等于你的整机;把爆炸半径缩小到一个容器,YOLO 就不再是鲁莽 —— 七个 agent 全部关掉自带权限系统跑(`claude --dangerously-skip-permissions`、`codex --dangerously-bypass-approvals-and-sandbox`、`gemini --yolo`、`grok --always-approve`、`kimi --yolo`、opencode 走 `OPENCODE_PERMISSION` 全放行、pi 本身就没有权限系统)。最坏结果随容器一起销毁。 +- 全速跑 agent。权限弹窗存在的理由是爆炸半径等于你的整机;把爆炸半径缩小到一个容器,YOLO 就不再是鲁莽 —— 八个 agent 全部关掉自带权限系统跑(`claude --dangerously-skip-permissions`、`codex --dangerously-bypass-approvals-and-sandbox`、`gemini --yolo`、`grok --always-approve`、`kimi --yolo`、opencode 走 `OPENCODE_PERMISSION` 全放行、pi 本身就没有权限系统、dsh 走 `DSH_PERMISSION_MODE=danger-full-access`)。最坏结果随容器一起销毁。 - 厂商代码永远见不到你的主机。agent CLI 是快速迭代、带自动更新的 npm 依赖树;在这里它们出生在镜像里:没有 `~/.ssh`、没有 `~/.aws`、没有 shell 环境变量汤、没有浏览器档案。想收集也无从下手。 - 跨越边界的一切都是显式的。文件靠挂载,密钥靠你传的 env,网络靠你选的姿态 —— 默认 bridge、走你的代理、`--host-net`、或者干脆不联网。你没接的线,agent 就没有。 - 身份是启动参数,不是全局单例。`~/.config/deva/` 下按 agent 分家,`--auth-with` / `--config-home` 切第二个账号或 API-key 计费。按次切账号,项目、会话、容器状态原地不动。 @@ -41,6 +41,7 @@ deva.sh grok deva.sh kimi deva.sh opencode deva.sh pi +deva.sh dsh deva.sh claude --rm # 一次性容器,用完即扔 deva.sh claude --debug --dry-run # 先看 docker run 长啥样再信它 diff --git a/agents/dsh.sh b/agents/dsh.sh new file mode 100644 index 0000000..5287fe0 --- /dev/null +++ b/agents/dsh.sh @@ -0,0 +1,85 @@ +# shellcheck shell=bash + +# shellcheck disable=SC1091 +if [ -f "$(dirname "${BASH_SOURCE[0]}")/shared_auth.sh" ]; then + source "$(dirname "${BASH_SOURCE[0]}")/shared_auth.sh" +fi + +agent_prepare() { + local -a args + if [ $# -gt 0 ]; then + args=("$@") + else + args=() + fi + AGENT_COMMAND=("dsh") + + parse_auth_args "dsh" "${args[@]+"${args[@]}"}" + AUTH_METHOD="$PARSED_AUTH_METHOD" + local -a remaining_args=("${PARSED_REMAINING_ARGS[@]+"${PARSED_REMAINING_ARGS[@]}"}") + + filter_trace_flag "${remaining_args[@]+"${remaining_args[@]}"}" + local use_trace="$TRACE_FLAG_PRESENT" + remaining_args=("${TRACE_FILTERED_ARGS[@]+"${TRACE_FILTERED_ARGS[@]}"}") + + if [ "$use_trace" = true ]; then + auth_error "--trace is not supported for dsh yet" \ + "cctrace has no dsh profile" + fi + + # Container is the sandbox: dsh's own sandbox/approval subsystem + # defaults to workspace-write + ask. danger-full-access turns both + # off so unattended runs never stall on a prompt. Env is the only + # switch (no CLI flag), and dsh scrubs DSH_* from project-discovered + # env (.env, BASH_ENV) -- only this invoking-process env counts. + DOCKER_ARGS+=("-e" "DSH_PERMISSION_MODE=danger-full-access") + # dsh defaults its home to ~/.dsh today, but it is a developer + # preview with promised breaking changes; pin the location so a + # future default flip cannot strand mounted state. + DOCKER_ARGS+=("-e" "DSH_HOME=/home/deva/.dsh") + + AGENT_COMMAND+=("${remaining_args[@]+"${remaining_args[@]}"}") + + setup_dsh_auth "$AUTH_METHOD" +} + +setup_dsh_auth() { + local method="$1" + + case "$method" in + credentials) + AUTH_DETAILS="credentials (~/.dsh)" + # Everything dsh persists lives under ~/.dsh ($DSH_HOME): + # .credentials.yaml, settings.yaml, profiles/ (incl. pnpm + # node_modules -- container-built, so the mount must stay + # writable), skills/, attachments/. Only mount the host dir + # directly when no config-home mechanism is active. -Q bare + # mode: no mounts. Explicit/auto config-home: centralized + # mount handles it. + if [ "${QUICK_MODE:-false}" = false ] && [ "${CONFIG_HOME_FROM_CLI:-false}" = false ] && [ "${CONFIG_HOME_AUTO:-false}" = false ]; then + if [ ! -d "$HOME/.dsh" ]; then + echo "Warning: ~/.dsh directory not found, creating it" >&2 + mkdir -p "$HOME/.dsh" + fi + DOCKER_ARGS+=("-v" "$HOME/.dsh:/home/deva/.dsh") + fi + ;; + api-key) + # DEEPSEEK_API_KEY travels as env only. No mount: dsh + # resolves inherited env BEFORE ~/.dsh/.credentials.yaml, + # so the env key alone decides billing; leaving the home + # out keeps profiles/node_modules (host-arch pnpm trees) + # from crossing the boundary (same no-mount contract as + # grok/kimi/opencode/pi api-key). + if [ -z "${DEEPSEEK_API_KEY:-}" ]; then + auth_error "DEEPSEEK_API_KEY not set for --auth-with api-key" \ + "Set DEEPSEEK_API_KEY or use credentials mode (default)" + fi + DOCKER_ARGS+=("-e" "DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}") + AUTH_DETAILS="api-key (DEEPSEEK_API_KEY)" + ;; + *) + auth_error "auth method '$method' not implemented for dsh" + ;; + esac +} diff --git a/agents/shared_auth.sh b/agents/shared_auth.sh index db419bc..c6de0d7 100644 --- a/agents/shared_auth.sh +++ b/agents/shared_auth.sh @@ -541,6 +541,9 @@ parse_auth_args() { pi) supported_methods=(oauth api-key) ;; + dsh) + supported_methods=(credentials api-key) + ;; *) auth_error "Unknown agent: $agent_name" ;; @@ -610,6 +613,7 @@ parse_auth_args() { kimi) auth_method="oauth" ;; opencode) auth_method="oauth" ;; pi) auth_method="oauth" ;; + dsh) auth_method="credentials" ;; esac fi diff --git a/deva.sh b/deva.sh index 7b85e5a..9b5af9e 100755 --- a/deva.sh +++ b/deva.sh @@ -110,14 +110,14 @@ _step() { usage() { cat <<'USAGE' -deva.sh - Docker-based multi-agent launcher (Claude, Codex, Gemini, Grok, Kimi, opencode, pi) +deva.sh - Docker-based multi-agent launcher (Claude, Codex, Gemini, Grok, Kimi, opencode, pi, dsh) Usage: deva.sh [flags] [agent] [-- agent-flags] deva.sh [flags] Agents: - claude (default), codex, gemini, grok, kimi, opencode, pi + claude (default), codex, gemini, grok, kimi, opencode, pi, dsh Commands: ps List containers (current project; -g for all projects) @@ -269,7 +269,7 @@ USAGE usage_full() { cat <<'USAGE' -deva.sh - Docker-based multi-agent launcher (Claude, Codex, Gemini, Grok, Kimi, opencode, pi) +deva.sh - Docker-based multi-agent launcher (Claude, Codex, Gemini, Grok, Kimi, opencode, pi, dsh) Usage: deva.sh [deva flags] [agent] [-- agent-flags] @@ -376,6 +376,7 @@ Examples: deva.sh kimi # Launch kimi (oauth default; api-key via KIMI_CODE_API_KEY) deva.sh opencode # Launch opencode (oauth default; api-key via OPENCODE_API_KEY) deva.sh pi # Launch pi (oauth default; api-key via provider env keys) + deva.sh dsh # Launch dsh (credentials default; api-key via DEEPSEEK_API_KEY) deva.sh claude --rm # Ephemeral: deva-work-myapp-claude-12345 # Container management (current project) @@ -1108,7 +1109,7 @@ generate_auth_tag() { fi case "$agent:$auth_method" in - claude:claude|codex:chatgpt|gemini:oauth|gemini:gemini-app-oauth|grok:oauth|kimi:oauth|opencode:oauth|pi:oauth) + claude:claude|codex:chatgpt|gemini:oauth|gemini:gemini-app-oauth|grok:oauth|kimi:oauth|opencode:oauth|pi:oauth|dsh:credentials) printf '%s' "auth-default" return ;; @@ -1135,6 +1136,7 @@ generate_auth_tag() { opencode) key_val="${OPENCODE_API_KEY:-}" ;; # first set key in pi.sh's PI_API_KEY_VARS order pi) key_val="${ANTHROPIC_API_KEY:-${OPENAI_API_KEY:-${GEMINI_API_KEY:-${XAI_API_KEY:-${OPENROUTER_API_KEY:-}}}}}" ;; + dsh) key_val="${DEEPSEEK_API_KEY:-}" ;; esac if [ -n "$key_val" ] && [ ${#key_val} -ge 4 ]; then printf '%s' "api-key-${key_val: -4}" @@ -1183,6 +1185,7 @@ agent_version_tag() { kimi) label="org.opencontainers.image.kimi_code_version" ;; opencode) label="org.opencontainers.image.opencode_version" ;; pi) label="org.opencontainers.image.pi_coding_agent_version" ;; + dsh) label="org.opencontainers.image.dsh_version" ;; esac local ver="" @@ -1529,6 +1532,7 @@ categorize_mount() { elif [[ "$dest" == /home/deva/.claude* ]] || [[ "$dest" == /home/deva/.codex* ]] || \ [[ "$dest" == /home/deva/.gemini* ]] || [[ "$dest" == /home/deva/.grok* ]] || \ [[ "$dest" == /home/deva/.kimi-code* ]] || [[ "$dest" == /home/deva/.pi* ]] || \ + [[ "$dest" == /home/deva/.dsh* ]] || \ [[ "$dest" == /home/deva/.config/opencode* ]] || \ [[ "$dest" == /home/deva/.local/share/opencode* ]] || [[ "$dest" == /home/deva/.local/state/opencode* ]] || \ [ "$dest" = "/home/deva/.agents" ]; then @@ -1914,7 +1918,7 @@ cmd_status() { if [ -d "$config_root" ]; then echo "Agent Homes ($(shorten_path "$config_root")):" - for agent_name in claude codex gemini grok kimi opencode pi; do + for agent_name in claude codex gemini grok kimi opencode pi dsh; do local agent_dir="$config_root/$agent_name" if [ -d "$agent_dir" ]; then local canonical="" other_count=0 entry is_canonical @@ -2494,6 +2498,17 @@ should_skip_env_for_auth() { ;; esac ;; + dsh) + # The key only travels when deva injects it (api-key mode). dsh + # resolves inherited env BEFORE ~/.dsh/.credentials.yaml, so a + # host key leaking into credentials mode would silently override + # the mounted credentials and bill the wrong account. + case "$name" in + DEEPSEEK_API_KEY) + return 0 + ;; + esac + ;; esac return 1 @@ -2558,6 +2573,7 @@ agent_canonical_basenames() { # purpose (disposable models.json + self-update bin). opencode) printf '%s\n' '.config/opencode' '.local/share/opencode' '.local/state/opencode' ;; pi) printf '%s\n' '.pi' ;; + dsh) printf '%s\n' '.dsh' ;; *) return 0 ;; esac } @@ -2593,6 +2609,14 @@ pi_api_key_no_mount() { [ "$ACTIVE_AGENT" = "pi" ] && [ "${AUTH_METHOD:-}" = "api-key" ] } +# dsh api-key contract: DEEPSEEK_API_KEY travels as env only. dsh reads +# env before ~/.dsh/.credentials.yaml (no overlay needed -- the injected +# key always wins); the mount stays out so host-arch pnpm trees under +# profiles/ never cross the boundary. +dsh_api_key_no_mount() { + [ "$ACTIVE_AGENT" = "dsh" ] && [ "${AUTH_METHOD:-}" = "api-key" ] +} + # grok's self-updater writes Linux binaries into ~/.grok/bin and # ~/.grok/downloads — inside the auth dir we bind-mount. Verified against # @xai-official/grok 0.2.93: a mounted config.toml without the npm installer @@ -2636,6 +2660,9 @@ mount_agent_canonical() { if [ "$agent" = "pi" ] && pi_api_key_no_mount; then return 0 fi + if [ "$agent" = "dsh" ] && dsh_api_key_no_mount; then + return 0 + fi local entry src while IFS= read -r entry; do @@ -2692,7 +2719,7 @@ has_auth_override() { # Non-default --auth-with if [ -n "${AUTH_METHOD:-}" ]; then case "${ACTIVE_AGENT}:${AUTH_METHOD}" in - claude:claude|codex:chatgpt|gemini:oauth|gemini:gemini-app-oauth|grok:oauth|kimi:oauth|opencode:oauth|pi:oauth) ;; + claude:claude|codex:chatgpt|gemini:oauth|gemini:gemini-app-oauth|grok:oauth|kimi:oauth|opencode:oauth|pi:oauth|dsh:credentials) ;; *) return 0 ;; esac fi @@ -2707,6 +2734,7 @@ has_auth_override() { kimi) auth_vars="KIMI_CODE_API_KEY KIMI_API_KEY" ;; opencode) auth_vars="OPENCODE_API_KEY" ;; pi) auth_vars="ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY XAI_API_KEY OPENROUTER_API_KEY" ;; + dsh) auth_vars="DEEPSEEK_API_KEY" ;; esac local var @@ -4123,7 +4151,7 @@ if [ "$CONFIG_HOME_AUTO" = true ]; then fi if [ "$CONFIG_HOME_FROM_CLI" = true ] && [ -n "$CONFIG_HOME" ]; then - if [ -d "$CONFIG_HOME/claude" ] || [ -d "$CONFIG_HOME/codex" ] || [ -d "$CONFIG_HOME/gemini" ] || [ -d "$CONFIG_HOME/grok" ] || [ -d "$CONFIG_HOME/kimi" ] || [ -d "$CONFIG_HOME/opencode" ] || [ -d "$CONFIG_HOME/pi" ]; then + if [ -d "$CONFIG_HOME/claude" ] || [ -d "$CONFIG_HOME/codex" ] || [ -d "$CONFIG_HOME/gemini" ] || [ -d "$CONFIG_HOME/grok" ] || [ -d "$CONFIG_HOME/kimi" ] || [ -d "$CONFIG_HOME/opencode" ] || [ -d "$CONFIG_HOME/pi" ] || [ -d "$CONFIG_HOME/dsh" ]; then CONFIG_ROOT="$CONFIG_HOME" CONFIG_HOME="" CONFIG_HOME_AUTO=false @@ -4208,6 +4236,17 @@ autolink_legacy_into_deva_root() { [ -d "$CONFIG_ROOT/pi/.pi" ] || [ -L "$CONFIG_ROOT/pi/.pi" ] || mkdir -p "$CONFIG_ROOT/pi/.pi" fi + if [ -d "$HOME/.dsh" ]; then + [ -d "$CONFIG_ROOT/dsh" ] || mkdir -p "$CONFIG_ROOT/dsh" + if [ ! -e "$CONFIG_ROOT/dsh/.dsh" ] && [ ! -L "$CONFIG_ROOT/dsh/.dsh" ]; then + ln -s "$HOME/.dsh" "$CONFIG_ROOT/dsh/.dsh" + echo "autolink: ~/.dsh -> $CONFIG_ROOT/dsh/.dsh" >&2 + fi + fi + if [ -d "$CONFIG_ROOT" ]; then + [ -d "$CONFIG_ROOT/dsh/.dsh" ] || [ -L "$CONFIG_ROOT/dsh/.dsh" ] || mkdir -p "$CONFIG_ROOT/dsh/.dsh" + fi + # opencode: XDG-native, so the legacy homes and the links are nested. local _oc_entry while IFS= read -r _oc_entry; do @@ -4263,6 +4302,9 @@ if [ -n "$CONFIG_HOME" ] && [ "$DRY_RUN" != true ]; then pi) [ -d "$CONFIG_HOME/.pi" ] || mkdir -p "$CONFIG_HOME/.pi" ;; + dsh) + [ -d "$CONFIG_HOME/.dsh" ] || mkdir -p "$CONFIG_HOME/.dsh" + ;; esac fi @@ -4320,6 +4362,12 @@ if [ "$CONFIG_HOME_FROM_CLI" = true ] && [ -n "$CONFIG_HOME" ] && [ "$_config_ho echo "warning: $CONFIG_HOME/.pi/agent is empty; authentication will need to be set up" >&2 fi ;; + dsh) + # .credentials.yaml at the top of .dsh is the one that matters. + if [ ! -f "$CONFIG_HOME/.dsh/.credentials.yaml" ]; then + echo "warning: $CONFIG_HOME/.dsh has no .credentials.yaml; authentication will need to be set up" >&2 + fi + ;; esac fi @@ -4358,7 +4406,7 @@ _step "agent_prepare" if [ -n "${AUTH_METHOD:-}" ]; then case "${ACTIVE_AGENT}:${AUTH_METHOD}" in - claude:claude|codex:chatgpt|gemini:oauth|gemini:gemini-app-oauth|grok:oauth|kimi:oauth|opencode:oauth|pi:oauth) ;; + claude:claude|codex:chatgpt|gemini:oauth|gemini:gemini-app-oauth|grok:oauth|kimi:oauth|opencode:oauth|pi:oauth|dsh:credentials) ;; *) _needs_rewrite=true ;; esac @@ -4489,6 +4537,10 @@ else if [ -d "$HOME/.pi" ] && ! pi_api_key_no_mount; then DOCKER_ARGS+=("-v" "$HOME/.pi:/home/deva/.pi") fi + # dsh api-key is env-only (no mount); credentials wants ~/.dsh rw. + if [ -d "$HOME/.dsh" ] && ! dsh_api_key_no_mount; then + DOCKER_ARGS+=("-v" "$HOME/.dsh:/home/deva/.dsh") + fi fi fi _step "mount dispatch: done" diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index d903b48..5a7ba4c 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -51,7 +51,8 @@ Deva-root layout: ├── grok/ ├── kimi/ ├── opencode/ -└── pi/ +├── pi/ +└── dsh/ ``` ```bash @@ -189,9 +190,9 @@ verbatim and is never intercepted by deva. which records every API call the agent makes — messages, OAuth, usage/credits, MCP — not just the chat endpoint. Everything else on the line goes to the agent unchanged. Codex, Grok, and Kimi use cctrace client profiles -(cctrace >= 0.11) and always run MITM capture. opencode and pi have no -cctrace profile yet, so `deva.sh opencode --trace` and `deva.sh pi --trace` -are rejected (thevibeworks/cctrace#89). +(cctrace >= 0.11) and always run MITM capture. opencode, pi, and dsh have +no cctrace profile yet, so `--trace` on those agents is rejected +(thevibeworks/cctrace#89). When tracing is on, the entrypoint installs the cctrace MITM CA into the container's system trust store (`update-ca-certificates`) so subprocesses and diff --git a/docs/authentication.md b/docs/authentication.md index cd4cfb1..7fd98af 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -24,6 +24,7 @@ This guide documents what `deva.sh` actually supports, what env vars it reads, a | Kimi | `oauth` | `api-key` | `.kimi-code` (device-code), `KIMI_CODE_API_KEY` -> `KIMI_MODEL_*` | | opencode | `oauth` | `api-key` | `.local/share/opencode/auth.json` (device-code), `OPENCODE_API_KEY` | | pi | `oauth` | `api-key` | `.pi/agent/auth.json` (in-app `/login`), provider env keys (`ANTHROPIC_API_KEY`, ...) | +| dsh | `credentials` | `api-key` | `.dsh/.credentials.yaml`, `DEEPSEEK_API_KEY` | ## Claude @@ -499,6 +500,49 @@ deva.sh pi --auth-with api-key -- --provider anthropic Provider/model selection (`--provider`, `--model`) is pi's own CLI surface — pass it after `--`. +## dsh + +### Default: `--auth-with credentials` + +Mounts `~/.dsh` (`$DSH_HOME`; deva pins it to `/home/deva/.dsh` because +dsh is a developer preview and defaults can move). Everything dsh +persists lives there: `.credentials.yaml`, `settings.yaml`, `profiles/` +(including container-built pnpm trees — the mount stays writable), +`skills/`, `attachments/`. + +There is no login flow — either let dsh prompt for and store the key on +first run, or drop it into `.credentials.yaml` yourself. + +dsh's own sandbox/approval subsystem defaults to workspace-write + ask; +deva sets `DSH_PERMISSION_MODE=danger-full-access` so unattended runs +never stall — the container is the sandbox. That env is the only switch +(no CLI flag), and dsh scrubs `DSH_*` from project-discovered env +(`.env`, `BASH_ENV`), so only the injected process env counts. + +Skills interop: dsh reads Anthropic SKILL.md-compatible skills from +`~/.agents/skills` and `/.agents/skills` — the same dirs deva +already wires for claude. One skills dir serves both agents in the same +container. + +### `--auth-with api-key` + +Input (required): `DEEPSEEK_API_KEY` + +The key travels as env only; this mode mounts nothing. dsh resolves +inherited env BEFORE `.credentials.yaml`, so the injected key always +decides billing — and for the same reason a host `DEEPSEEK_API_KEY` is +scrubbed from credentials-mode runs, where it would silently outrank the +mounted credentials. No blank overlay is needed (reverse of pi). + +```bash +export DEEPSEEK_API_KEY=sk-... +deva.sh dsh --auth-with api-key +``` + +Held back on purpose: dsh's plugin/marketplace surface is in flux +(developer preview; the manifest format already broke pre-launch), so +deva wires the TUI + skills dirs only. + ## Config Homes And Auth Isolation Default homes live under: diff --git a/docs/how-it-works.md b/docs/how-it-works.md index 5f2a440..773d5d5 100644 --- a/docs/how-it-works.md +++ b/docs/how-it-works.md @@ -65,13 +65,14 @@ Default per-agent homes live under: ├── grok/ ├── kimi/ ├── opencode/ -└── pi/ +├── pi/ +└── dsh/ ``` `--config-home` supports two layouts: -- leaf home: `DIR/.claude`, `DIR/.claude.json`, `DIR/.codex`, `DIR/.gemini`, `DIR/.grok`, `DIR/.kimi-code`, `DIR/.pi`, or opencode's nested `DIR/.config/opencode` + `DIR/.local/share/opencode` + `DIR/.local/state/opencode` -- deva root: `DIR/claude`, `DIR/codex`, `DIR/gemini`, `DIR/grok`, `DIR/kimi`, `DIR/opencode`, `DIR/pi` +- leaf home: `DIR/.claude`, `DIR/.claude.json`, `DIR/.codex`, `DIR/.gemini`, `DIR/.grok`, `DIR/.kimi-code`, `DIR/.pi`, `DIR/.dsh`, or opencode's nested `DIR/.config/opencode` + `DIR/.local/share/opencode` + `DIR/.local/state/opencode` +- deva root: `DIR/claude`, `DIR/codex`, `DIR/gemini`, `DIR/grok`, `DIR/kimi`, `DIR/opencode`, `DIR/pi`, `DIR/dsh` `-Q` disables config-home resolution, autolink, and host config mounts entirely. @@ -89,6 +90,7 @@ Examples: - Kimi default: `.kimi-code` (device-code OAuth); api-key maps `KIMI_CODE_API_KEY` onto `KIMI_MODEL_*` - opencode default: the XDG trio with `auth.json` under `.local/share/opencode` (device-code OAuth); api-key passes `OPENCODE_API_KEY`, mounts nothing - pi default: `.pi` with `auth.json` under `.pi/agent` (in-app `/login`, tokens auto-refresh); api-key passes provider env keys (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, ...), mounts nothing +- dsh default: `.dsh` with `.credentials.yaml` at its top (no login flow); api-key passes `DEEPSEEK_API_KEY`, mounts nothing — dsh reads env before the credentials file, so no overlay is needed When non-default auth is active, deva mounts a blank overlay over the default credential file path so the agent cannot silently fall back to some unrelated OAuth state. That is the point of the overlay fix. @@ -120,7 +122,7 @@ Persistent is default: - one default container shape per project - reused across runs -- same workspace can run Claude, Codex, Gemini, Grok, Kimi, opencode, and pi in the same container when mounts, config, and auth line up +- same workspace can run Claude, Codex, Gemini, Grok, Kimi, opencode, pi, and dsh in the same container when mounts, config, and auth line up - different volumes, explicit config homes, or auth modes create separate persistent containers Ephemeral with `--rm`: diff --git a/docs/index.md b/docs/index.md index 0adead9..bf10ae8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ # deva.sh -Run Codex, Claude Code, Gemini, Grok, Kimi, opencode, and pi inside Docker without pretending +Run Codex, Claude Code, Gemini, Grok, Kimi, opencode, pi, and dsh inside Docker without pretending the agent's own sandbox is the thing keeping you safe. The container is the sandbox. Explicit mounts are the contract. @@ -24,7 +24,7 @@ If you want the internals instead of vague hand-waving: ## What This Is -- a Docker-based launcher for Codex, Claude, Gemini, Grok, Kimi, opencode, and pi +- a Docker-based launcher for Codex, Claude, Gemini, Grok, Kimi, opencode, pi, and dsh - one warm default container shape per project by default - explicit mount and env wiring instead of mystery behavior - per-agent config homes under `~/.config/deva/` diff --git a/docs/philosophy.md b/docs/philosophy.md index 2fe25c2..998daa3 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -39,7 +39,7 @@ Persistent per-project containers mean: - warm package caches - stateful shell history and scratch space -- fast switching between Claude, Codex, Gemini, Grok, Kimi, opencode, and pi +- fast switching between Claude, Codex, Gemini, Grok, Kimi, opencode, pi, and dsh `--rm` still exists. It just is not the default because the default should serve real work instead of screenshots. diff --git a/docs/quick-start.md b/docs/quick-start.md index 7fec185..a11b312 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -30,6 +30,7 @@ That installs: - `agents/kimi.sh` - `agents/opencode.sh` - `agents/pi.sh` +- `agents/dsh.sh` - `agents/shared_auth.sh` It also pulls `ghcr.io/thevibeworks/deva:latest`, with Docker Hub as fallback. @@ -81,6 +82,7 @@ deva.sh grok deva.sh kimi deva.sh opencode deva.sh pi +deva.sh dsh ``` That is one of the main reasons this wrapper exists. You do not need a separate pet workflow for every vendor. @@ -148,6 +150,14 @@ export ANTHROPIC_API_KEY=sk-ant-... deva.sh pi --auth-with api-key ``` +dsh with a DeepSeek API key (env outranks stored credentials, so +api-key mode mounts nothing): + +```bash +export DEEPSEEK_API_KEY=sk-... +deva.sh dsh --auth-with api-key +``` + More auth details live in [Authentication Guide](authentication.md). ## Useful Modes diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index e38050e..eedb973 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -84,7 +84,7 @@ If the dry-run shape is correct but the agent still cannot authenticate, the wra Symptom: -- first run warns that `.claude`, `.codex`, `.gemini`, `.grok`, `.kimi-code`, `.pi`, or `.local/share/opencode` is empty +- first run warns that `.claude`, `.codex`, `.gemini`, `.grok`, `.kimi-code`, `.pi`, `.dsh`, or `.local/share/opencode` is empty Meaning: diff --git a/install.sh b/install.sh index 92cef60..96bdf80 100644 --- a/install.sh +++ b/install.sh @@ -14,6 +14,7 @@ agent_files=( "kimi.sh" "opencode.sh" "pi.sh" + "dsh.sh" "shared_auth.sh" ) @@ -146,6 +147,7 @@ echo " - $INSTALL_DIR/agents/grok.sh" echo " - $INSTALL_DIR/agents/kimi.sh" echo " - $INSTALL_DIR/agents/opencode.sh" echo " - $INSTALL_DIR/agents/pi.sh" +echo " - $INSTALL_DIR/agents/dsh.sh" echo " - $INSTALL_DIR/agents/shared_auth.sh" echo "" echo "Quick start:" @@ -159,6 +161,7 @@ echo " deva.sh grok -- --help" echo " deva.sh kimi -- --help" echo " deva.sh opencode -- --help" echo " deva.sh pi -- --help" +echo " deva.sh dsh -- --help" echo " deva.sh shell" echo "" echo "warning: do not point deva at your real home directory with dangerous permissions enabled" diff --git a/llms.txt b/llms.txt index ae50a1e..f44a96f 100644 --- a/llms.txt +++ b/llms.txt @@ -1,6 +1,6 @@ # deva.sh -> Docker-first launcher for AI coding agents — Claude Code, Codex, Gemini, Grok, Kimi, opencode, and pi. +> Docker-first launcher for AI coding agents — Claude Code, Codex, Gemini, Grok, Kimi, opencode, pi, and dsh. > The container is the sandbox, mounts are the explicit contract, and one warm > project container serves all six agents. A bash script, not a framework. MIT. @@ -11,7 +11,8 @@ are disabled by design — isolation comes from Docker, not permission theater: `claude --dangerously-skip-permissions`, `codex --dangerously-bypass-approvals-and-sandbox`, `gemini --yolo`, `grok --always-approve`, `kimi --yolo`, opencode via -`OPENCODE_PERMISSION` allow-all, and pi (no permission system by design). Auth +`OPENCODE_PERMISSION` allow-all, pi (no permission system by design), and +dsh via `DSH_PERMISSION_MODE=danger-full-access`. Auth lives in per-agent config homes under `~/.config/deva/` with OAuth (default) or API-key modes per agent, switchable per run (`--auth-with`, `--config-home`) so accounts and billing modes swap without touching project or session state; @@ -33,6 +34,7 @@ deva.sh grok deva.sh kimi deva.sh opencode deva.sh pi +deva.sh dsh deva.sh claude -p cloak # stealth-browser image (CloakBrowser, headed Xvfb) deva.sh claude --debug --dry-run # inspect the docker run before trusting it ``` diff --git a/scripts/install-agent-tooling.sh b/scripts/install-agent-tooling.sh index 8834cee..29b609c 100644 --- a/scripts/install-agent-tooling.sh +++ b/scripts/install-agent-tooling.sh @@ -11,6 +11,7 @@ set -euo pipefail : "${KIMI_CODE_VERSION:?KIMI_CODE_VERSION is required}" : "${OPENCODE_VERSION:?OPENCODE_VERSION is required}" : "${PI_CODING_AGENT_VERSION:?PI_CODING_AGENT_VERSION is required}" +: "${DSH_VERSION:?DSH_VERSION is required}" CCTRACE_VERSION="${CCTRACE_VERSION:-0.4.0}" CCX_VERSION="${CCX_VERSION:-v0.7.0}" @@ -140,7 +141,7 @@ install_npm_agent_tooling() { log "Installing npm agent tooling" log "Proxy config:" log_proxy_config - log "Requested versions: claude=${CLAUDE_CODE_VERSION} codex=${CODEX_VERSION} gemini=${GEMINI_CLI_VERSION} grok=${GROK_CLI_VERSION} kimi=${KIMI_CODE_VERSION} opencode=${OPENCODE_VERSION} pi=${PI_CODING_AGENT_VERSION}" + log "Requested versions: claude=${CLAUDE_CODE_VERSION} codex=${CODEX_VERSION} gemini=${GEMINI_CLI_VERSION} grok=${GROK_CLI_VERSION} kimi=${KIMI_CODE_VERSION} opencode=${OPENCODE_VERSION} pi=${PI_CODING_AGENT_VERSION} dsh=${DSH_VERSION}" mkdir -p "$DEVA_HOME/.npm-global" "$DEVA_HOME/.local/bin" # opencode is XDG-native; pre-create its dirs as the deva user so docker @@ -151,6 +152,8 @@ install_npm_agent_tooling() { # pi keeps everything under ~/.pi/agent; pre-create it as the deva # user for the same bind-mount-parent reason. mkdir -p "$DEVA_HOME/.pi/agent" + # dsh keeps everything under ~/.dsh ($DSH_HOME); same reason. + mkdir -p "$DEVA_HOME/.dsh" npm config set prefix "$DEVA_HOME/.npm-global" check_npm_registry_dns @@ -163,6 +166,7 @@ install_npm_agent_tooling() { "@moonshot-ai/kimi-code@${KIMI_CODE_VERSION}" \ "opencode-ai@${OPENCODE_VERSION}" \ "@earendil-works/pi-coding-agent@${PI_CODING_AGENT_VERSION}" \ + "@deepseek-ai/dsh@${DSH_VERSION}" \ || die "npm install failed" npm cache clean --force @@ -181,7 +185,9 @@ install_npm_agent_tooling() { # pi phones pi.dev for an update check on startup; skip it so the # verify works in network-restricted builds. PI_SKIP_VERSION_CHECK=1 "$DEVA_HOME/.npm-global/bin/pi" --version - (npm list -g --depth=0 @anthropic-ai/claude-code @openai/codex @google/gemini-cli @xai-official/grok @moonshot-ai/kimi-code opencode-ai @earendil-works/pi-coding-agent || true) + # dsh has no updater and no phone-home; plain verify. + "$DEVA_HOME/.npm-global/bin/dsh" --version + (npm list -g --depth=0 @anthropic-ai/claude-code @openai/codex @google/gemini-cli @xai-official/grok @moonshot-ai/kimi-code opencode-ai @earendil-works/pi-coding-agent @deepseek-ai/dsh || true) } # grok's npm postinstall puts the real binary in ~/.grok/bin (the CLI's diff --git a/scripts/release-utils.sh b/scripts/release-utils.sh index 76f6f4d..9e05951 100755 --- a/scripts/release-utils.sh +++ b/scripts/release-utils.sh @@ -33,6 +33,7 @@ TOOL_REGISTRY=( "kimi-code|npm|@moonshot-ai/kimi-code|org.opencontainers.image.kimi_code_version|https://www.npmjs.com/package/@moonshot-ai/kimi-code||agent|main" "opencode|npm|opencode-ai|org.opencontainers.image.opencode_version|https://www.npmjs.com/package/opencode-ai|github:anomalyco/opencode|agent|main" "pi|npm|@earendil-works/pi-coding-agent|org.opencontainers.image.pi_coding_agent_version|https://www.npmjs.com/package/@earendil-works/pi-coding-agent|github:earendil-works/pi|agent|main" + "dsh|npm|@deepseek-ai/dsh|org.opencontainers.image.dsh_version|https://www.npmjs.com/package/@deepseek-ai/dsh|github:deepseek-ai/deepseek-harness|agent|main" "ccx|github-release|thevibeworks/ccx|org.opencontainers.image.ccx_version|https://github.com/thevibeworks/ccx|github:thevibeworks/ccx|agent|main" "copilot-api|github-commit|ericc-ch/copilot-api|org.opencontainers.image.copilot_api_version|https://github.com/ericc-ch/copilot-api||agent|main" "cctrace|npm|@thevibeworks/cctrace|org.opencontainers.image.cctrace_version|https://www.npmjs.com/package/@thevibeworks/cctrace|github:thevibeworks/cctrace|agent|main" diff --git a/scripts/resolve-tool-versions.sh b/scripts/resolve-tool-versions.sh index 4f32cc7..1de86bf 100644 --- a/scripts/resolve-tool-versions.sh +++ b/scripts/resolve-tool-versions.sh @@ -34,6 +34,7 @@ main() { resolve_tool "kimi_code_version" "kimi-code" resolve_tool "opencode_version" "opencode" resolve_tool "pi_coding_agent_version" "pi" + resolve_tool "dsh_version" "dsh" resolve_tool "ccx_version" "ccx" resolve_tool "copilot_api_version" "copilot-api" } diff --git a/scripts/test-dsh-auth.sh b/scripts/test-dsh-auth.sh new file mode 100755 index 0000000..b55ff59 --- /dev/null +++ b/scripts/test-dsh-auth.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# test-dsh-auth.sh - dsh agent auth wiring (~/.dsh mount + DEEPSEEK_API_KEY env) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +tmp_home="$(mktemp -d)" +cleanup() { rm -rf "$tmp_home"; } +trap cleanup EXIT + +fail=0 +run_dry() { + ( + cd "$REPO_ROOT" + HOME="$tmp_home" \ + XDG_CONFIG_HOME="$tmp_home/.config" \ + XDG_CACHE_HOME="$tmp_home/.cache" \ + DEVA_NO_DOCKER=1 \ + ./deva.sh "$@" + ) 2>&1 +} + +want() { + local desc="$1" needle="$2" hay="$3" + if grep -F -- "$needle" <<<"$hay" >/dev/null; then + echo " PASS $desc" + else + echo " FAIL $desc" + echo " expected to find: $needle" >&2 + fail=1 + fi +} +want_absent() { + local desc="$1" needle="$2" hay="$3" + if grep -F -- "$needle" <<<"$hay" >/dev/null; then + echo " FAIL $desc" + echo " expected absent: $needle" >&2 + fail=1 + else + echo " PASS $desc" + fi +} + +echo "=== dsh credentials (default) ===" +cred_out="$(run_dry dsh --debug --dry-run || true)" +want "auth method is credentials" "DEVA_AUTH_METHOD=credentials" "$cred_out" +want "permission bypass wired" "DSH_PERMISSION_MODE=danger-full-access" "$cred_out" +want "home pinned" "DSH_HOME=/home/deva/.dsh" "$cred_out" + +echo "=== dsh credentials: hybrid config-root mounts ~/.dsh ===" +# Seed the config-root layout an autolinked run leaves behind and assert +# the centralized walk (mount_agent_canonical) emits the mount. +mkdir -p "$tmp_home/.config/deva/dsh/.dsh" +hybrid_cred_out="$(run_dry dsh --dry-run || true)" +want "dsh home mounted" ":/home/deva/.dsh" "$hybrid_cred_out" + +echo "=== dsh api-key: DEEPSEEK_API_KEY as env, no mount ===" +apikey_out="$(DEEPSEEK_API_KEY=sk-ds-test-1234 run_dry dsh --auth-with api-key --dry-run -- --profile headless hi || true)" +want "key wired + redacted" "DEEPSEEK_API_KEY=" "$apikey_out" +want "key last-4 tags container" "--api-key-1234--" "$apikey_out" +want "passes agent args after --" "dsh --profile headless hi" "$apikey_out" +want_absent "no ~/.dsh mount in api-key mode" ":/home/deva/.dsh\"" "$apikey_out" + +echo "=== dsh api-key: no mount on the hybrid config-root path either ===" +hybrid_apikey_out="$(DEEPSEEK_API_KEY=sk-ds-test-1234 run_dry dsh --auth-with api-key --dry-run || true)" +want_absent "hybrid layout: no ~/.dsh mount in api-key mode" ":/home/deva/.dsh\"" "$hybrid_apikey_out" +rm -rf "$tmp_home/.config/deva/dsh" + +echo "=== dsh credentials: host DEEPSEEK_API_KEY must not leak ===" +# dsh resolves inherited env BEFORE ~/.dsh/.credentials.yaml, so a leaked +# host key would silently outrank the mounted credentials. +leak_out="$(DEEPSEEK_API_KEY=sk-host-leak-9999 run_dry dsh -e DEEPSEEK_API_KEY --dry-run || true)" +want_absent "host key filtered in credentials mode" "DEEPSEEK_API_KEY" "$leak_out" + +echo "=== dsh --trace: rejected until cctrace ships a profile ===" +trace_out="$(run_dry dsh --trace --dry-run || true)" +want "trace rejected" "--trace is not supported for dsh" "$trace_out" + +echo "=== dsh --trace after -- is passthrough ===" +trace_pass_out="$(run_dry dsh --dry-run -- --trace || true)" +want_absent "--trace after -- not absorbed" "--trace is not supported" "$trace_pass_out" +want "--trace passed to agent" "dsh --trace" "$trace_pass_out" + +echo "=== dsh api-key: missing key errors ===" +missing_out="$(DEEPSEEK_API_KEY= run_dry dsh --auth-with api-key --dry-run || true)" +want "errors when no key set" "DEEPSEEK_API_KEY not set" "$missing_out" + +if [ "$fail" -ne 0 ]; then + echo "FAIL: dsh auth wiring" >&2 + exit 1 +fi +echo "OK: dsh auth wiring" diff --git a/scripts/test-install-agent-tooling.sh b/scripts/test-install-agent-tooling.sh index 3a519ba..074f9fa 100755 --- a/scripts/test-install-agent-tooling.sh +++ b/scripts/test-install-agent-tooling.sh @@ -34,7 +34,7 @@ config) ;; install) mkdir -p "$DEVA_HOME/.npm-global/bin" - for bin in claude codex gemini grok kimi opencode pi; do + for bin in claude codex gemini grok kimi opencode pi dsh; do cat >"$DEVA_HOME/.npm-global/bin/$bin" <<'BIN' #!/usr/bin/env bash case "$(basename "$0")" in @@ -45,6 +45,7 @@ case "$(basename "$0")" in kimi) echo "__KIMI_CODE_VERSION__" ;; opencode) echo "__OPENCODE_VERSION__" ;; pi) echo "__PI_CODING_AGENT_VERSION__" ;; + dsh) echo "__DSH_VERSION__" ;; esac BIN chmod +x "$DEVA_HOME/.npm-global/bin/$bin" @@ -90,6 +91,7 @@ sed -i \ -e "s#__KIMI_CODE_VERSION__#$KIMI_CODE_VERSION#g" \ -e "s#__OPENCODE_VERSION__#$OPENCODE_VERSION#g" \ -e "s#__PI_CODING_AGENT_VERSION__#$PI_CODING_AGENT_VERSION#g" \ + -e "s#__DSH_VERSION__#$DSH_VERSION#g" \ "$fake_bin/npm" cat >"$fake_bin/curl" <<'EOF' diff --git a/scripts/update-version-pins.sh b/scripts/update-version-pins.sh index 71d2370..b1a0eca 100755 --- a/scripts/update-version-pins.sh +++ b/scripts/update-version-pins.sh @@ -135,6 +135,7 @@ registry_tool() { KIMI_CODE_VERSION) echo "kimi-code" ;; OPENCODE_VERSION) echo "opencode" ;; PI_CODING_AGENT_VERSION) echo "pi" ;; + DSH_VERSION) echo "dsh" ;; CCX_VERSION) echo "ccx" ;; COPILOT_API_VERSION) echo "copilot-api" ;; PLAYWRIGHT_VERSION) echo "playwright" ;; @@ -232,6 +233,7 @@ main() { pin "Kimi Code" KIMI_CODE_VERSION npm "@moonshot-ai/kimi-code" pin "opencode" OPENCODE_VERSION npm "opencode-ai" pin "pi" PI_CODING_AGENT_VERSION npm "@earendil-works/pi-coding-agent" + pin "dsh" DSH_VERSION npm "@deepseek-ai/dsh" pin "CCX" CCX_VERSION git-tag "https://github.com/thevibeworks/ccx.git" pin "Copilot API" COPILOT_API_VERSION git-commit "https://github.com/ericc-ch/copilot-api.git" "refs/heads/master" diff --git a/scripts/version-pins.sh b/scripts/version-pins.sh index 13a927b..d377239 100644 --- a/scripts/version-pins.sh +++ b/scripts/version-pins.sh @@ -21,6 +21,7 @@ VERSION_PIN_VARS=( KIMI_CODE_VERSION OPENCODE_VERSION PI_CODING_AGENT_VERSION + DSH_VERSION CCX_VERSION COPILOT_API_VERSION PLAYWRIGHT_VERSION @@ -89,6 +90,7 @@ GROK_CLI_VERSION=$GROK_CLI_VERSION KIMI_CODE_VERSION=$KIMI_CODE_VERSION OPENCODE_VERSION=$OPENCODE_VERSION PI_CODING_AGENT_VERSION=$PI_CODING_AGENT_VERSION +DSH_VERSION=$DSH_VERSION CCX_VERSION=$CCX_VERSION COPILOT_API_VERSION=$COPILOT_API_VERSION PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION diff --git a/scripts/version-upgrade.sh b/scripts/version-upgrade.sh index a9714d8..6880a28 100755 --- a/scripts/version-upgrade.sh +++ b/scripts/version-upgrade.sh @@ -20,6 +20,7 @@ _CLI_GROK="${GROK_CLI_VERSION:-}" _CLI_KIMI="${KIMI_CODE_VERSION:-}" _CLI_OPENCODE="${OPENCODE_VERSION:-}" _CLI_PI="${PI_CODING_AGENT_VERSION:-}" +_CLI_DSH="${DSH_VERSION:-}" _CLI_CCX="${CCX_VERSION:-}" _CLI_COPILOT="${COPILOT_API_VERSION:-}" _CLI_PLAYWRIGHT="${PLAYWRIGHT_VERSION:-}" @@ -73,7 +74,8 @@ Options: --only LIST Upgrade only these tools (comma-separated); the rest stay pinned to versions.env. Tools: claude-code, cctrace, codex, gemini-cli, grok-cli, kimi-code, - opencode, pi, ccx, copilot-api, playwright, cloakbrowser, + opencode, pi, dsh, ccx, copilot-api, playwright, + cloakbrowser, kimi-webbridge -h, --help Show this help @@ -93,6 +95,7 @@ Environment: KIMI_CODE_VERSION Override kimi-code version OPENCODE_VERSION Override opencode version PI_CODING_AGENT_VERSION Override pi version + DSH_VERSION Override dsh version CCX_VERSION Override ccx version COPILOT_API_VERSION Override copilot-api version PLAYWRIGHT_VERSION Override playwright version (rust image only) @@ -123,7 +126,7 @@ apply_only_filter() { [[ -n $ONLY ]] || return 0 local tool - local known="claude-code cctrace codex gemini-cli grok-cli kimi-code opencode pi ccx copilot-api playwright cloakbrowser kimi-webbridge" + local known="claude-code cctrace codex gemini-cli grok-cli kimi-code opencode pi dsh ccx copilot-api playwright cloakbrowser kimi-webbridge" for tool in ${ONLY//,/ }; do case " $known " in *" $tool "*) ;; @@ -141,6 +144,7 @@ apply_only_filter() { tool_selected kimi-code || _CLI_KIMI="${_CLI_KIMI:-$KIMI_CODE_VERSION}" tool_selected opencode || _CLI_OPENCODE="${_CLI_OPENCODE:-$OPENCODE_VERSION}" tool_selected pi || _CLI_PI="${_CLI_PI:-$PI_CODING_AGENT_VERSION}" + tool_selected dsh || _CLI_DSH="${_CLI_DSH:-$DSH_VERSION}" tool_selected ccx || _CLI_CCX="${_CLI_CCX:-$CCX_VERSION}" tool_selected copilot-api || _CLI_COPILOT="${_CLI_COPILOT:-$COPILOT_API_VERSION}" tool_selected playwright || _CLI_PLAYWRIGHT="${_CLI_PLAYWRIGHT:-$PLAYWRIGHT_VERSION}" @@ -233,7 +237,7 @@ main() { # Resolve build versions early so we can show the manifest before countdown. # CLI override wins; otherwise use whatever load_versions fetched. - local claude_ver cctrace_ver codex_ver gemini_ver grok_ver kimi_ver opencode_ver pi_ver ccx_ver copilot_ver playwright_ver + local claude_ver cctrace_ver codex_ver gemini_ver grok_ver kimi_ver opencode_ver pi_ver dsh_ver ccx_ver copilot_ver playwright_ver claude_ver="${_CLI_CLAUDE_CODE:-$(get_latest "claude-code")}" cctrace_ver="${_CLI_CCTRACE:-$(get_latest "cctrace")}" codex_ver="${_CLI_CODEX:-$(get_latest "codex")}" @@ -242,6 +246,7 @@ main() { kimi_ver="${_CLI_KIMI:-$(get_latest "kimi-code")}" opencode_ver="${_CLI_OPENCODE:-$(get_latest "opencode")}" pi_ver="${_CLI_PI:-$(get_latest "pi")}" + dsh_ver="${_CLI_DSH:-$(get_latest "dsh")}" ccx_ver="${_CLI_CCX:-$(get_latest "ccx")}" copilot_ver="${_CLI_COPILOT:-$(get_latest "copilot-api")}" playwright_ver="${_CLI_PLAYWRIGHT:-${PLAYWRIGHT_VERSION}}" @@ -254,6 +259,7 @@ main() { [[ -z $kimi_ver ]] && missing+=("KIMI_CODE_VERSION") [[ -z $opencode_ver ]] && missing+=("OPENCODE_VERSION") [[ -z $pi_ver ]] && missing+=("PI_CODING_AGENT_VERSION") + [[ -z $dsh_ver ]] && missing+=("DSH_VERSION") [[ -z $ccx_ver ]] && missing+=("CCX_VERSION") [[ -z $copilot_ver ]] && missing+=("COPILOT_API_VERSION") [[ -z $playwright_ver ]] && missing+=("PLAYWRIGHT_VERSION") @@ -274,6 +280,7 @@ main() { "Kimi Code|kimi_ver|_CLI_KIMI|kimi-code" "opencode|opencode_ver|_CLI_OPENCODE|opencode" "pi|pi_ver|_CLI_PI|pi" + "dsh|dsh_ver|_CLI_DSH|dsh" "CCX|ccx_ver|_CLI_CCX|ccx" "Copilot API|copilot_ver|_CLI_COPILOT|copilot-api" "Playwright|playwright_ver|_CLI_PLAYWRIGHT|playwright" @@ -404,6 +411,7 @@ main() { --build-arg KIMI_CODE_VERSION="$kimi_ver" \ --build-arg OPENCODE_VERSION="$opencode_ver" \ --build-arg PI_CODING_AGENT_VERSION="$pi_ver" \ + --build-arg DSH_VERSION="$dsh_ver" \ --build-arg CCX_VERSION="$ccx_ver" \ --build-arg COPILOT_API_VERSION="$copilot_ver" \ -t "$BUILD_IMAGE" . @@ -421,6 +429,7 @@ main() { --build-arg KIMI_CODE_VERSION="$kimi_ver" \ --build-arg OPENCODE_VERSION="$opencode_ver" \ --build-arg PI_CODING_AGENT_VERSION="$pi_ver" \ + --build-arg DSH_VERSION="$dsh_ver" \ --build-arg CCX_VERSION="$ccx_ver" \ --build-arg PLAYWRIGHT_VERSION="$playwright_ver" \ --build-arg RUST_TOOLCHAINS="$RUST_TOOLCHAINS" \ @@ -452,6 +461,7 @@ main() { KIMI_CODE_VERSION="$kimi_ver" OPENCODE_VERSION="$opencode_ver" PI_CODING_AGENT_VERSION="$pi_ver" + DSH_VERSION="$dsh_ver" CCX_VERSION="$ccx_ver" COPILOT_API_VERSION="$copilot_ver" PLAYWRIGHT_VERSION="$playwright_ver" diff --git a/versions.env b/versions.env index 8bd8c04..a91efad 100644 --- a/versions.env +++ b/versions.env @@ -16,6 +16,7 @@ GROK_CLI_VERSION=1.0.3 KIMI_CODE_VERSION=0.36.0 OPENCODE_VERSION=1.18.18 PI_CODING_AGENT_VERSION=0.84.1 +DSH_VERSION=0.1.0-rc.6 CCX_VERSION=v0.15.0 COPILOT_API_VERSION=0ea08febdd7e3e055b03dd298bf57e669500b5c1 PLAYWRIGHT_VERSION=1.62.1 From 3616de0169aa3d993e4f67942f688258f5519939 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Thu, 13 Aug 2026 22:22:08 -0700 Subject: [PATCH 3/4] test(release-utils): dsh joins the agent/main tool lists Missed in the dsh commit: tests/test_release_utils.sh pins the registry tool lists; 70/70 again. Co-Authored-By: Claude Fable 5 --- tests/test_release_utils.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_release_utils.sh b/tests/test_release_utils.sh index 88d503c..6c8a7fa 100644 --- a/tests/test_release_utils.sh +++ b/tests/test_release_utils.sh @@ -129,7 +129,7 @@ assert_eq "caller 'image' not clobbered" "caller-image" "${leak_after##*|}" # ───── get_tools_by_group ───── section "get_tools_by_group" agent_tools="$(get_tools_by_group agent | sort | tr '\n' ' ' | sed 's/ $//')" -expected_agent="cctrace ccx claude-code codex copilot-api gemini-cli grok-cli kimi-code opencode pi" +expected_agent="cctrace ccx claude-code codex copilot-api dsh gemini-cli grok-cli kimi-code opencode pi" assert_eq "group=agent" "$expected_agent" "$agent_tools" browser_tools="$(get_tools_by_group browser | sort | tr '\n' ' ' | sed 's/ $//')" @@ -144,7 +144,7 @@ assert_eq "group=does-not-exist" "" "$nonexistent_group" # ───── get_tools_by_image ───── section "get_tools_by_image" main_tools="$(get_tools_by_image main | sort | tr '\n' ' ' | sed 's/ $//')" -expected_main="cctrace ccx claude-code codex copilot-api gemini-cli grok-cli kimi-code opencode pi" +expected_main="cctrace ccx claude-code codex copilot-api dsh gemini-cli grok-cli kimi-code opencode pi" assert_eq "image=main" "$expected_main" "$main_tools" rust_tools="$(get_tools_by_image rust | sort | tr '\n' ' ' | sed 's/ $//')" @@ -156,8 +156,8 @@ assert_eq "image=base (empty until Step 3)" "" "$base_tools" # ───── filter_tools: scope resolution ───── section "filter_tools: default (no scope)" default_all="$(unset GROUP TOOL IMAGE; filter_tools | sort | tr '\n' ' ' | sed 's/ $//')" -expected_all="cctrace ccx claude-code codex copilot-api gemini-cli grok-cli kimi-code opencode pi playwright" -assert_eq "default returns all 11" "$expected_all" "$default_all" +expected_all="cctrace ccx claude-code codex copilot-api dsh gemini-cli grok-cli kimi-code opencode pi playwright" +assert_eq "default returns all 12" "$expected_all" "$default_all" section "filter_tools: TOOL= scope" single="$(TOOL=claude-code GROUP= IMAGE= filter_tools)" From 9d339efca35ea58047f983c5cd4ebdf2608c7f0a Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Thu, 13 Aug 2026 22:32:41 -0700 Subject: [PATCH 4/4] test(version-upgrade): dsh fixtures for registry, packument, image label The hermetic fake registry hard-errors on unknown URLs by design; dsh needed dist-tags + packument fixtures, an image-label current for the outage fallback, and its pin in the written-pins assert. Co-Authored-By: Claude Fable 5 --- tests/version-upgrade.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/version-upgrade.sh b/tests/version-upgrade.sh index 456bd7d..1645018 100644 --- a/tests/version-upgrade.sh +++ b/tests/version-upgrade.sh @@ -30,6 +30,7 @@ inspect) "org.opencontainers.image.kimi_code_version":"0.28.0", "org.opencontainers.image.opencode_version":"1.18.14", "org.opencontainers.image.pi_coding_agent_version":"0.84.0", + "org.opencontainers.image.dsh_version":"0.1.0-rc.5", "org.opencontainers.image.ccx_version":"v0.7.0", "org.opencontainers.image.copilot_api_version":"0ea08febdd7e3e055b03dd298bf57e669500b5c1", "org.opencontainers.image.playwright_version":"1.59.0" @@ -109,6 +110,7 @@ case "$url" in */-/package/@moonshot-ai/kimi-code/dist-tags) echo '{"latest":"0.28.0"}' ;; */-/package/opencode-ai/dist-tags) echo '{"latest":"1.18.14"}' ;; */-/package/@earendil-works/pi-coding-agent/dist-tags) echo '{"latest":"0.84.1"}' ;; +*/-/package/@deepseek-ai/dsh/dist-tags) echo '{"latest":"0.1.0-rc.6"}' ;; */-/package/playwright/dist-tags) echo '{"latest":"1.60.0"}' ;; */-/package/cloakbrowser/dist-tags) echo '{"latest":"0.6.0"}' ;; *cdn.kimi.com/webbridge/latest/version.json) echo '{"version":"v1.12.0","binaries":{}}' ;; @@ -120,6 +122,7 @@ case "$url" in *registry.npmjs.org/@moonshot-ai%2fkimi-code) echo '{"time":{"0.28.0":"2026-07-10T00:00:00Z"}}' ;; *registry.npmjs.org/opencode-ai) echo '{"time":{"1.18.14":"2026-08-01T00:00:00Z"}}' ;; *registry.npmjs.org/@earendil-works%2fpi-coding-agent) echo '{"time":{"0.84.1":"2026-08-07T00:00:00Z"}}' ;; +*registry.npmjs.org/@deepseek-ai%2fdsh) echo '{"time":{"0.1.0-rc.6":"2026-08-13T12:36:00Z"}}' ;; *registry.npmjs.org/playwright) echo '{"time":{"1.60.0":"2026-05-14T08:00:00Z"}}' ;; *) echo "unexpected curl url: $url" >&2 @@ -230,6 +233,7 @@ for expected in \ "GROK_CLI_VERSION=0.2.93" \ "KIMI_CODE_VERSION=0.28.0" \ "PI_CODING_AGENT_VERSION=0.84.1" \ + "DSH_VERSION=0.1.0-rc.6" \ "CCX_VERSION=v0.7.0" \ "COPILOT_API_VERSION=0ea08febdd7e3e055b03dd298bf57e669500b5c1" \ "PLAYWRIGHT_VERSION=1.60.0" \ @@ -374,6 +378,7 @@ case "$url" in */-/package/@moonshot-ai/kimi-code/dist-tags) echo '{"latest":"0.28.0"}' ;; */-/package/opencode-ai/dist-tags) echo '{"latest":"1.18.14"}' ;; */-/package/@earendil-works/pi-coding-agent/dist-tags) echo '{"latest":"0.84.1"}' ;; +*/-/package/@deepseek-ai/dsh/dist-tags) echo '{"latest":"0.1.0-rc.6"}' ;; */-/package/playwright/dist-tags) echo '{"latest":"1.60.0"}' ;; */-/package/cloakbrowser/dist-tags) echo '{"latest":"0.6.0"}' ;; *cdn.kimi.com/webbridge/latest/version.json) echo '{"version":"v1.12.0","binaries":{}}' ;;