From 213d4db452608a3c2865282b2696dfa0a6a9873e Mon Sep 17 00:00:00 2001 From: temeteke Date: Sun, 18 Oct 2026 05:35:15 +0900 Subject: [PATCH 1/5] feat: add Pi and DeepSeek Harness Dev Container Features --- README.md | 8 ++++- src/dsh/README.md | 55 +++++++++++++++++++++++++++++++ src/dsh/devcontainer-feature.json | 36 ++++++++++++++++++++ src/dsh/install.sh | 25 ++++++++++++++ src/dsh/setup-persistence.sh | 35 ++++++++++++++++++++ src/pi/README.md | 39 ++++++++++++++++++++++ src/pi/devcontainer-feature.json | 36 ++++++++++++++++++++ src/pi/install.sh | 25 ++++++++++++++ src/pi/setup-persistence.sh | 35 ++++++++++++++++++++ 9 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 src/dsh/README.md create mode 100644 src/dsh/devcontainer-feature.json create mode 100644 src/dsh/install.sh create mode 100644 src/dsh/setup-persistence.sh create mode 100644 src/pi/README.md create mode 100644 src/pi/devcontainer-feature.json create mode 100644 src/pi/install.sh create mode 100644 src/pi/setup-persistence.sh diff --git a/README.md b/README.md index 6a65ebb..22c4f0f 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ agent-specific repository. It provides the following features: - Claude Code - Codex - OpenCode +- Pi Coding Agent +- DeepSeek Harness (dsh) - Cline - Roo Code - Playwright Agent CLI @@ -34,6 +36,8 @@ This keeps the setting out of each project's `.devcontainer/devcontainer.json`. "ghcr.io/temeteke/devcontainer-features/claude-code:0": {}, "ghcr.io/temeteke/devcontainer-features/codex:0": {}, "ghcr.io/temeteke/devcontainer-features/opencode:0": {}, + "ghcr.io/temeteke/devcontainer-features/pi:0": {}, + "ghcr.io/temeteke/devcontainer-features/dsh:0": {}, "ghcr.io/temeteke/devcontainer-features/cline:0": {}, "ghcr.io/temeteke/devcontainer-features/roo-code:0": {}, "ghcr.io/temeteke/devcontainer-features/playwright:0": {}, @@ -86,6 +90,8 @@ other sensitive data. Since they are stored directly in your host home directory | `claude-code` | Coding tool via official Feature | `~/.claude` | | `codex` | Coding tool: `@openai/codex` via npm, VS Code extension `openai.chatgpt` | `~/.codex` | | `opencode` | Coding tool: `@opencode/cli` (OpenCode 2) via npm, VS Code extension `sst-dev.opencode` | `~/.config/opencode`, `~/.local/share/opencode`, `~/.agents` via `agents` | +| `pi` | Coding tool: `@earendil-works/pi-coding-agent` via npm | `~/.pi/agent` | +| `dsh` | Agent harness: `@deepseek-ai/dsh` via npm | `~/.dsh` | | `cline` | Coding tool: VS Code extension `saoudrizwan.claude-dev` | `~/Documents/Cline`, `~/.cline` | | `roo-code` | Coding tool: VS Code extension `rooveterinaryinc.roo-cline` | `~/.roo`, VS Code globalStorage | | `playwright` | AI agent tool: Playwright Agent CLI and browser runtime only | none | @@ -115,7 +121,7 @@ read-only bind mount properties. This supports standard dotfile layouts, including Vim configurations that install plugins or write undo data under `~/.vim/`. -Before starting a Dev Container, ensure the corresponding host paths exist. +Before starting a Dev Container, ensure the corresponding host paths exist (including `~/.pi/agent` and `~/.dsh` when enabled). A missing bind-mount source may prevent the container from starting. These Features do not mount the entire host HOME directory. diff --git a/src/dsh/README.md b/src/dsh/README.md new file mode 100644 index 0000000..9f870b5 --- /dev/null +++ b/src/dsh/README.md @@ -0,0 +1,55 @@ +# DeepSeek Harness (dsh) Feature + +Installs the official [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) +CLI (command: `dsh`) using npm. + +## Options + +- `install`: default `true` +- `persist`: default `true` +- `version`: npm version for `@deepseek-ai/dsh`, default `latest` + +## Persistence + +The default `~/.dsh` directory is bind-mounted from the host and linked into +the container home. It includes profiles, session state, and configuration. +The feature does not override `DSH_HOME`; a custom home must be managed +separately. + +**Before creating the container**, run `mkdir -p ~/.dsh` on the +Docker/Dev Container host so the bind-mount source exists. This directory is +writable inside the container; protect credentials and session data. + +The feature depends on the upstream Node.js feature (LTS). DSH is in +developer preview; new versions can introduce breaking changes. Pin +`version` when reproducible behavior is important. Native dependencies may +require a compiler toolchain if suitable prebuilt modules are unavailable. + +## Usage + +Examples inside a running Dev Container: + +```sh +dsh --help +dsh --profile headless "Summarize this repository" +dsh web --no-open +``` + +With the disposable `dcs` wrapper: + +```sh +dcs dsh --help +dcs dsh dsh --profile headless "Summarize this repository" +``` + +Official DSH is a launcher for profiles, including Web and headless modes, +not a Pi-style built-in terminal chat. The Web UI requires a reachable +container port and appropriate listening/forwarding configuration; `dcs` +does not publish Web UI ports automatically. Use a properly configured +persistent Dev Container for Web mode. + +Provider/model credentials are configured separately; installing the CLI +does not automatically configure an API provider. Third-party TUI profiles +are not installed by this Feature. + +This is an unofficial Feature, not a DeepSeek-maintained distribution. diff --git a/src/dsh/devcontainer-feature.json b/src/dsh/devcontainer-feature.json new file mode 100644 index 0000000..e42ccbc --- /dev/null +++ b/src/dsh/devcontainer-feature.json @@ -0,0 +1,36 @@ +{ + "id": "dsh", + "version": "0.1.0", + "name": "DeepSeek Harness", + "description": "Installs official DeepSeek Harness CLI and persists default ~/.dsh state.", + "options": { + "install": { + "type": "boolean", + "default": true, + "description": "Install the tool." + }, + "persist": { + "type": "boolean", + "default": true, + "description": "Link the default user-state directory to its bind-mounted host directory." + }, + "version": { + "type": "string", + "default": "latest", + "description": "npm version for @deepseek-ai/dsh; latest or a concrete version." + } + }, + "dependsOn": { + "ghcr.io/devcontainers/features/node:1": { + "version": "lts" + } + }, + "mounts": [ + { + "type": "bind", + "source": "${localEnv:HOME}/.dsh", + "target": "/mnt/devcontainer-features/dsh" + } + ], + "postCreateCommand": "/usr/local/share/devcontainer-features/dsh/setup-persistence.sh" +} diff --git a/src/dsh/install.sh b/src/dsh/install.sh new file mode 100644 index 0000000..4f5db5e --- /dev/null +++ b/src/dsh/install.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +FEATURE_ID="dsh" +FEATURE_DIR="/usr/local/share/devcontainer-features/${FEATURE_ID}" +BASE_DIR="/mnt/devcontainer-features/${FEATURE_ID}" + +mkdir -p "$FEATURE_DIR" "$BASE_DIR" +cp "${0%/*}/setup-persistence.sh" "$FEATURE_DIR/" +chmod +x "$FEATURE_DIR/setup-persistence.sh" + +cat > "${FEATURE_DIR}/feature.env" <&2 +fi + +if [ -L "$target_dir" ] && [ "$(readlink "$target_dir")" = "$source_dir" ]; then + exit 0 +fi + +if [ -L "$target_dir" ]; then + rm -f "$target_dir" +elif [ -e "$target_dir" ]; then + cp -a "$target_dir/." "$source_dir/" 2>/dev/null || true + rm -rf "$target_dir" +fi +ln -s "$source_dir" "$target_dir" +echo "[${FEATURE_ID}] Linked $target_dir -> $source_dir" diff --git a/src/pi/README.md b/src/pi/README.md new file mode 100644 index 0000000..ca65bc7 --- /dev/null +++ b/src/pi/README.md @@ -0,0 +1,39 @@ +# Pi Coding Agent Feature + +Installs the [Pi Coding Agent](https://pi.dev/) CLI with npm (command: `pi`). + +## Options + +- `install`: default `true` +- `persist`: default `true` +- `version`: npm version for `@earendil-works/pi-coding-agent`, default `latest` + +## Persistence + +The default `~/.pi/agent` directory is bind-mounted from the host and linked into +the container's user home. This includes authentication, settings, installed +extensions, and sessions. Project-local `.pi/` files remain in the workspace. +The feature uses the default agent directory; custom `PI_CODING_AGENT_DIR` +locations are not automatically mounted. + +**Before creating the container**, run `mkdir -p ~/.pi/agent` on the +Docker/Dev Container host so the bind-mount source exists. Data is writable +from within the container and survives deletion of a disposable container. + +The feature depends on the upstream Node.js feature (LTS). It uses the +upstream-recommended `npm install -g --ignore-scripts` form. + +## Usage + +Use `pi` inside a Dev Container. With the `dcs` sandbox wrapper: + +```sh +dcs pi +dcs pi,python +``` + +First configure your preferred provider with `/login` or supply an API key +through your chosen environment configuration. Host credentials in +`~/.pi/agent` are exposed to the container. + +The independent upstream project is not affiliated with this Feature. diff --git a/src/pi/devcontainer-feature.json b/src/pi/devcontainer-feature.json new file mode 100644 index 0000000..4ebed35 --- /dev/null +++ b/src/pi/devcontainer-feature.json @@ -0,0 +1,36 @@ +{ + "id": "pi", + "version": "0.1.0", + "name": "Pi Coding Agent", + "description": "Installs the Pi CLI and persists default ~/.pi/agent state.", + "options": { + "install": { + "type": "boolean", + "default": true, + "description": "Install the tool." + }, + "persist": { + "type": "boolean", + "default": true, + "description": "Link the default user-state directory to its bind-mounted host directory." + }, + "version": { + "type": "string", + "default": "latest", + "description": "npm version for @earendil-works/pi-coding-agent; latest or a concrete version." + } + }, + "dependsOn": { + "ghcr.io/devcontainers/features/node:1": { + "version": "lts" + } + }, + "mounts": [ + { + "type": "bind", + "source": "${localEnv:HOME}/.pi/agent", + "target": "/mnt/devcontainer-features/pi" + } + ], + "postCreateCommand": "/usr/local/share/devcontainer-features/pi/setup-persistence.sh" +} diff --git a/src/pi/install.sh b/src/pi/install.sh new file mode 100644 index 0000000..abb4027 --- /dev/null +++ b/src/pi/install.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +FEATURE_ID="pi" +FEATURE_DIR="/usr/local/share/devcontainer-features/${FEATURE_ID}" +BASE_DIR="/mnt/devcontainer-features/${FEATURE_ID}" + +mkdir -p "$FEATURE_DIR" "$BASE_DIR" +cp "${0%/*}/setup-persistence.sh" "$FEATURE_DIR/" +chmod +x "$FEATURE_DIR/setup-persistence.sh" + +cat > "${FEATURE_DIR}/feature.env" <&2 +fi + +if [ -L "$target_dir" ] && [ "$(readlink "$target_dir")" = "$source_dir" ]; then + exit 0 +fi + +if [ -L "$target_dir" ]; then + rm -f "$target_dir" +elif [ -e "$target_dir" ]; then + cp -a "$target_dir/." "$source_dir/" 2>/dev/null || true + rm -rf "$target_dir" +fi +ln -s "$source_dir" "$target_dir" +echo "[${FEATURE_ID}] Linked $target_dir -> $source_dir" From cd6d94d1581a6437925f07f95fb94e96eb13aca1 Mon Sep 17 00:00:00 2001 From: temeteke Date: Sun, 18 Oct 2026 05:36:57 +0900 Subject: [PATCH 2/5] ci: validate Feature metadata and shell scripts on pull requests --- .github/workflows/validate.yaml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/validate.yaml diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml new file mode 100644 index 0000000..8147204 --- /dev/null +++ b/.github/workflows/validate.yaml @@ -0,0 +1,22 @@ +name: Validate Dev Container Features + +on: + pull_request: + paths: + - "src/**" + - "test/**" + - ".github/workflows/validate.yaml" + push: + branches: [main] + paths: + - "src/**" + - "test/**" + - ".github/workflows/validate.yaml" + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Validate JSON and shell syntax + run: bash test/validate.sh From ccf359bede764a7c7a1c9940536c5e727abb2c60 Mon Sep 17 00:00:00 2001 From: temeteke Date: Sun, 18 Oct 2026 06:53:16 +0900 Subject: [PATCH 3/5] refactor: scope Feature PR to Pi Coding Agent --- README.md | 5 +-- src/dsh/README.md | 55 ------------------------------- src/dsh/devcontainer-feature.json | 36 -------------------- src/dsh/install.sh | 25 -------------- src/dsh/setup-persistence.sh | 35 -------------------- 5 files changed, 1 insertion(+), 155 deletions(-) delete mode 100644 src/dsh/README.md delete mode 100644 src/dsh/devcontainer-feature.json delete mode 100644 src/dsh/install.sh delete mode 100644 src/dsh/setup-persistence.sh diff --git a/README.md b/README.md index 22c4f0f..2c8c89f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ agent-specific repository. It provides the following features: - Codex - OpenCode - Pi Coding Agent -- DeepSeek Harness (dsh) - Cline - Roo Code - Playwright Agent CLI @@ -37,7 +36,6 @@ This keeps the setting out of each project's `.devcontainer/devcontainer.json`. "ghcr.io/temeteke/devcontainer-features/codex:0": {}, "ghcr.io/temeteke/devcontainer-features/opencode:0": {}, "ghcr.io/temeteke/devcontainer-features/pi:0": {}, - "ghcr.io/temeteke/devcontainer-features/dsh:0": {}, "ghcr.io/temeteke/devcontainer-features/cline:0": {}, "ghcr.io/temeteke/devcontainer-features/roo-code:0": {}, "ghcr.io/temeteke/devcontainer-features/playwright:0": {}, @@ -91,7 +89,6 @@ other sensitive data. Since they are stored directly in your host home directory | `codex` | Coding tool: `@openai/codex` via npm, VS Code extension `openai.chatgpt` | `~/.codex` | | `opencode` | Coding tool: `@opencode/cli` (OpenCode 2) via npm, VS Code extension `sst-dev.opencode` | `~/.config/opencode`, `~/.local/share/opencode`, `~/.agents` via `agents` | | `pi` | Coding tool: `@earendil-works/pi-coding-agent` via npm | `~/.pi/agent` | -| `dsh` | Agent harness: `@deepseek-ai/dsh` via npm | `~/.dsh` | | `cline` | Coding tool: VS Code extension `saoudrizwan.claude-dev` | `~/Documents/Cline`, `~/.cline` | | `roo-code` | Coding tool: VS Code extension `rooveterinaryinc.roo-cline` | `~/.roo`, VS Code globalStorage | | `playwright` | AI agent tool: Playwright Agent CLI and browser runtime only | none | @@ -121,7 +118,7 @@ read-only bind mount properties. This supports standard dotfile layouts, including Vim configurations that install plugins or write undo data under `~/.vim/`. -Before starting a Dev Container, ensure the corresponding host paths exist (including `~/.pi/agent` and `~/.dsh` when enabled). +Before starting a Dev Container, ensure the corresponding host paths exist (including `~/.pi/agent` when enabled). A missing bind-mount source may prevent the container from starting. These Features do not mount the entire host HOME directory. diff --git a/src/dsh/README.md b/src/dsh/README.md deleted file mode 100644 index 9f870b5..0000000 --- a/src/dsh/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# DeepSeek Harness (dsh) Feature - -Installs the official [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) -CLI (command: `dsh`) using npm. - -## Options - -- `install`: default `true` -- `persist`: default `true` -- `version`: npm version for `@deepseek-ai/dsh`, default `latest` - -## Persistence - -The default `~/.dsh` directory is bind-mounted from the host and linked into -the container home. It includes profiles, session state, and configuration. -The feature does not override `DSH_HOME`; a custom home must be managed -separately. - -**Before creating the container**, run `mkdir -p ~/.dsh` on the -Docker/Dev Container host so the bind-mount source exists. This directory is -writable inside the container; protect credentials and session data. - -The feature depends on the upstream Node.js feature (LTS). DSH is in -developer preview; new versions can introduce breaking changes. Pin -`version` when reproducible behavior is important. Native dependencies may -require a compiler toolchain if suitable prebuilt modules are unavailable. - -## Usage - -Examples inside a running Dev Container: - -```sh -dsh --help -dsh --profile headless "Summarize this repository" -dsh web --no-open -``` - -With the disposable `dcs` wrapper: - -```sh -dcs dsh --help -dcs dsh dsh --profile headless "Summarize this repository" -``` - -Official DSH is a launcher for profiles, including Web and headless modes, -not a Pi-style built-in terminal chat. The Web UI requires a reachable -container port and appropriate listening/forwarding configuration; `dcs` -does not publish Web UI ports automatically. Use a properly configured -persistent Dev Container for Web mode. - -Provider/model credentials are configured separately; installing the CLI -does not automatically configure an API provider. Third-party TUI profiles -are not installed by this Feature. - -This is an unofficial Feature, not a DeepSeek-maintained distribution. diff --git a/src/dsh/devcontainer-feature.json b/src/dsh/devcontainer-feature.json deleted file mode 100644 index e42ccbc..0000000 --- a/src/dsh/devcontainer-feature.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "id": "dsh", - "version": "0.1.0", - "name": "DeepSeek Harness", - "description": "Installs official DeepSeek Harness CLI and persists default ~/.dsh state.", - "options": { - "install": { - "type": "boolean", - "default": true, - "description": "Install the tool." - }, - "persist": { - "type": "boolean", - "default": true, - "description": "Link the default user-state directory to its bind-mounted host directory." - }, - "version": { - "type": "string", - "default": "latest", - "description": "npm version for @deepseek-ai/dsh; latest or a concrete version." - } - }, - "dependsOn": { - "ghcr.io/devcontainers/features/node:1": { - "version": "lts" - } - }, - "mounts": [ - { - "type": "bind", - "source": "${localEnv:HOME}/.dsh", - "target": "/mnt/devcontainer-features/dsh" - } - ], - "postCreateCommand": "/usr/local/share/devcontainer-features/dsh/setup-persistence.sh" -} diff --git a/src/dsh/install.sh b/src/dsh/install.sh deleted file mode 100644 index 4f5db5e..0000000 --- a/src/dsh/install.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -FEATURE_ID="dsh" -FEATURE_DIR="/usr/local/share/devcontainer-features/${FEATURE_ID}" -BASE_DIR="/mnt/devcontainer-features/${FEATURE_ID}" - -mkdir -p "$FEATURE_DIR" "$BASE_DIR" -cp "${0%/*}/setup-persistence.sh" "$FEATURE_DIR/" -chmod +x "$FEATURE_DIR/setup-persistence.sh" - -cat > "${FEATURE_DIR}/feature.env" <&2 -fi - -if [ -L "$target_dir" ] && [ "$(readlink "$target_dir")" = "$source_dir" ]; then - exit 0 -fi - -if [ -L "$target_dir" ]; then - rm -f "$target_dir" -elif [ -e "$target_dir" ]; then - cp -a "$target_dir/." "$source_dir/" 2>/dev/null || true - rm -rf "$target_dir" -fi -ln -s "$source_dir" "$target_dir" -echo "[${FEATURE_ID}] Linked $target_dir -> $source_dir" From 5520ed7687236d403726b429768bea8cc92bae81 Mon Sep 17 00:00:00 2001 From: temeteke Date: Thu, 5 Nov 2026 17:33:58 +0900 Subject: [PATCH 4/5] fix(pi): preserve existing container state safely during persistence setup --- src/pi/setup-persistence.sh | 144 ++++++++++++++++++++++++++++++------ 1 file changed, 123 insertions(+), 21 deletions(-) diff --git a/src/pi/setup-persistence.sh b/src/pi/setup-persistence.sh index 02319f0..a16f293 100644 --- a/src/pi/setup-persistence.sh +++ b/src/pi/setup-persistence.sh @@ -3,33 +3,135 @@ set -euo pipefail FEATURE_ID="pi" FEATURE_DIR="/usr/local/share/devcontainer-features/${FEATURE_ID}" -if [ -f "${FEATURE_DIR}/feature.env" ]; then +FEATURE_ENV="${FEATURE_DIR}/feature.env" +if [ -f "${FEATURE_ENV}" ]; then # shellcheck disable=SC1090 - . "${FEATURE_DIR}/feature.env" + . "${FEATURE_ENV}" fi -if [ "${PERSIST:-true}" != "true" ]; then - echo "[${FEATURE_ID}] Persistence disabled." - exit 0 -fi +PERSIST="${PERSIST:-true}" +BASE_DIR="/mnt/devcontainer-features/${FEATURE_ID}" -source_dir="/mnt/devcontainer-features/${FEATURE_ID}" -target_dir="$HOME/.pi/agent" +log() { + echo "[${FEATURE_ID}] $*" +} -mkdir -p "$source_dir" "$(dirname "$target_dir")" -if [ ! -w "$source_dir" ]; then - echo "[${FEATURE_ID}] Warning: $source_dir is not writable by $(id -un)." >&2 -fi +is_true() { + case "${1:-}" in + true|True|TRUE|1|yes|YES|y|Y) return 0 ;; + *) return 1 ;; + esac +} + +ensure_writable_dir() { + local dir="$1" + mkdir -p "$dir" + if [ ! -w "$dir" ]; then + log "Warning: $dir is not writable by $(id -un). Trying to continue." + fi +} + +nearest_existing() { + local path="$1" + while [ ! -e "$path" ] && [ "$path" != "/" ]; do + path="$(dirname "$path")" + done + printf '%s\n' "$path" +} + +mount_of() { + local path + local mnt + + path="$(nearest_existing "$1")" + + if command -v findmnt >/dev/null 2>&1; then + mnt="$(findmnt -n -o TARGET -T "$path" 2>/dev/null | head -n1 || true)" + if [ -n "$mnt" ]; then + printf '%s\n' "$mnt" + return 0 + fi + fi + + if command -v stat >/dev/null 2>&1; then + mnt="$(stat -c '%m' "$path" 2>/dev/null || true)" + if [ -n "$mnt" ]; then + printf '%s\n' "$mnt" + return 0 + fi + fi +} + +link_dir() { + local src="$1" + local dst="$2" + local backup + local mnt + local src_mnt -if [ -L "$target_dir" ] && [ "$(readlink "$target_dir")" = "$source_dir" ]; then + src_mnt="$(mount_of "$src")" + if [ -z "$src_mnt" ] || [ "$src_mnt" = "/" ]; then + log "Warning: $src is not on a mounted host path. Leaving $dst untouched." + return 0 + fi + + ensure_writable_dir "$src" + + if [ -L "$dst" ]; then + local current + current="$(readlink "$dst" || true)" + if [ "$current" = "$src" ]; then + log "Already linked: $dst -> $src" + return 0 + fi + fi + + mnt="$(mount_of "$dst")" + if [ -z "$mnt" ]; then + log "Warning: Cannot determine the mount for $dst. Leaving it untouched." + return 0 + fi + if [ "$mnt" != "/" ]; then + log "Warning: $dst is on mount $mnt. Leaving it untouched." + return 0 + fi + + mkdir -p "$(dirname "$dst")" + + if [ -e "$dst" ] || [ -L "$dst" ]; then + if [ -d "$dst" ] && rmdir "$dst" 2>/dev/null; then + log "Removed empty directory: $dst" + else + backup="${dst}.devcontainer-backup" + if [ -e "$backup" ] || [ -L "$backup" ]; then + local index=1 + while [ -e "${backup}.${index}" ] || [ -L "${backup}.${index}" ]; do + index=$((index + 1)) + done + backup="${backup}.${index}" + fi + if mv "$dst" "$backup"; then + log "Moved existing $dst to $backup" + else + log "Error: Failed to move $dst. Persistence was not configured." + return 1 + fi + fi + fi + + if ln -sfn "$src" "$dst"; then + log "Linked: $dst -> $src" + else + log "Error: Failed to create symlink $dst -> $src. Persistence was not configured." + return 1 + fi +} + +if ! is_true "$PERSIST"; then + log "Persistence disabled." exit 0 fi -if [ -L "$target_dir" ]; then - rm -f "$target_dir" -elif [ -e "$target_dir" ]; then - cp -a "$target_dir/." "$source_dir/" 2>/dev/null || true - rm -rf "$target_dir" -fi -ln -s "$source_dir" "$target_dir" -echo "[${FEATURE_ID}] Linked $target_dir -> $source_dir" +ensure_writable_dir "$BASE_DIR" + +link_dir "$BASE_DIR" "$HOME/.pi/agent" From 04939bf2aa7b659a64ea95b5ce34d997151a0e99 Mon Sep 17 00:00:00 2001 From: temeteke Date: Thu, 5 Nov 2026 23:12:51 +0900 Subject: [PATCH 5/5] docs(pi): document standalone Dev Container Feature usage --- src/pi/README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/pi/README.md b/src/pi/README.md index ca65bc7..87f707c 100644 --- a/src/pi/README.md +++ b/src/pi/README.md @@ -25,13 +25,18 @@ upstream-recommended `npm install -g --ignore-scripts` form. ## Usage -Use `pi` inside a Dev Container. With the `dcs` sandbox wrapper: - -```sh -dcs pi -dcs pi,python +Add this Feature to your `devcontainer.json`: + +```json +{ + "features": { + "ghcr.io/temeteke/devcontainer-features/pi:0": {} + } +} ``` +Then open the Dev Container and run `pi` in its terminal. + First configure your preferred provider with `/login` or supply an API key through your chosen environment configuration. Host credentials in `~/.pi/agent` are exposed to the container.