diff --git a/.zeroclaw/INSTALL.md b/.zeroclaw/INSTALL.md new file mode 100644 index 000000000..4a07d7e8c --- /dev/null +++ b/.zeroclaw/INSTALL.md @@ -0,0 +1,92 @@ +# Installing Compound Engineering for ZeroClaw + +[ZeroClaw](https://github.com/zeroclaw-labs/zeroclaw) loads CE through native **skills** discovery — the same `SKILL.md` directories shipped in this repository's `skills/` folder. No Bun converter or generated copy step is required. + +## Prerequisites + +1. Install ZeroClaw ([install guide](https://github.com/zeroclaw-labs/zeroclaw#install)). +2. Run `zeroclaw quickstart` so you have at least one agent (typically `default`) under `~/.zeroclaw/agents/`. +3. If you use a non-default profile, the installer follows ZeroClaw runtime precedence for the install root: `ZEROCLAW_CONFIG_DIR`, then `ZEROCLAW_DATA_DIR`, then legacy `ZEROCLAW_WORKSPACE`. Per-agent destinations honor `[agents..workspace.path]` when set in `config.toml`. +4. Enable bundled scripts in your ZeroClaw config. Many CE skills ship `scripts/*.sh` and `scripts/*.py`; ZeroClaw's skill audit blocks script files unless you opt in: + +```toml +# ~/.zeroclaw/config.toml +[skills] +allow_scripts = true +``` + +## Install skills + +ZeroClaw v0.8+ loads skills from **per-agent workspace** paths (`~/.zeroclaw/agents//workspace/skills/`), not the legacy `~/.zeroclaw/workspace/skills/` tree. The installer copies skill directories into the paths agents actually read. + +From a clone of this repository: + +```bash +# Default agent (recommended after quickstart) +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global + +# Explicit agent alias +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --agent my-agent + +# Every configured agent +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --agent all +``` + +### Shared skill bundle (multi-agent hosts) + +To install once under `~/.zeroclaw/shared/skills/compound_engineering/` and reference it from agent config: + +```bash +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --shared +``` + +Then add to `~/.zeroclaw/config.toml`: + +```toml +[skill_bundles.compound_engineering] + +[agents.default] +skill_bundles = ["compound_engineering"] +``` + +The script **copies** skill directories (ZeroClaw rejects symlinks at audit time). It does **not** call `zeroclaw skills install` — that CLI writes to `config.data_dir/skills`, which agent sessions do not load. The installer honors `ZEROCLAW_CONFIG_DIR` when set, and refuses unknown agent aliases (run `zeroclaw quickstart` before `--global` or `--agent`). + +Re-run the script after `git pull` to refresh installed copies when skill content changes. + +Skills marked `disable-model-invocation: true` (for example `lfg`, `ce-dogfood`, `ce-polish`) are **not** installed by default. ZeroClaw does not honor that frontmatter field. Opt in when you need those workflows: + +```bash +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global --include-manual +``` + +## Pin a release + +Clone the tag you want, then run the install script against that checkout: + +```bash +git clone --branch compound-engineering-vX.Y.Z --depth 1 \ + https://github.com/EveryInc/compound-engineering-plugin.git +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global +``` + +Replace `X.Y.Z` with a tag from the [releases page](https://github.com/EveryInc/compound-engineering-plugin/releases). + +## Local development + +From your working copy: + +```bash +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global +``` + +Edit skills under `skills/` and re-run the install script to refresh copies. Restart the agent session or gateway if skills do not reload immediately. + +## Uninstall + +Remove CE skill directories from the install target (for example `~/.zeroclaw/agents/default/workspace/skills/ce-brainstorm`). Names match folders under `skills/`. + +For `--shared` installs, remove skills from `~/.zeroclaw/shared/skills/compound_engineering/` and drop the bundle reference from agent config. + +## Project context + +ZeroClaw reads workspace context from standard instruction files. CE skills reference "the project's active instructions and conventions already in your context" rather than hardcoding harness-specific filenames. Root `AGENTS.md` in your project is the conventional target. diff --git a/.zeroclaw/scripts/install-skills.sh b/.zeroclaw/scripts/install-skills.sh new file mode 100755 index 000000000..5f33859ed --- /dev/null +++ b/.zeroclaw/scripts/install-skills.sh @@ -0,0 +1,380 @@ +#!/usr/bin/env bash +# Copy Compound Engineering skills/ into ZeroClaw agent workspace skills directories. +# ZeroClaw rejects symlinked skill directories at audit time — copies only. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +SKILLS_SRC="$REPO_ROOT/skills" +SHARED_BUNDLE="compound_engineering" + +expand_path() { + local path="$1" + case "$path" in + "~") + printf '%s\n' "$HOME" + ;; + "~/"*) + printf '%s\n' "$HOME/${path:2}" + ;; + *) + printf '%s\n' "$path" + ;; + esac +} + +resolve_config_dir_for_data() { + local data_dir + data_dir="$(expand_path "$1")" + + if [[ -f "$data_dir/config.toml" ]]; then + printf '%s\n' "$data_dir" + return + fi + + local legacy_dir="${data_dir%/}/../.zeroclaw" + legacy_dir="$(cd "$(dirname "$data_dir")" && pwd)/.zeroclaw" + + if [[ -f "$legacy_dir/config.toml" ]]; then + printf '%s\n' "$legacy_dir" + return + fi + + local base + base="$(basename "$data_dir")" + if [[ "$base" == "data" || "$base" == "workspace" ]]; then + printf '%s\n' "$legacy_dir" + return + fi + + printf '%s\n' "$data_dir" +} + +resolve_install_root() { + if [[ -n "${ZEROCLAW_INSTALL_ROOT:-}" ]]; then + expand_path "$ZEROCLAW_INSTALL_ROOT" + return + fi + if [[ -n "${ZEROCLAW_CONFIG_DIR:-}" ]]; then + expand_path "$ZEROCLAW_CONFIG_DIR" + return + fi + if [[ -n "${ZEROCLAW_DATA_DIR:-}" ]]; then + resolve_config_dir_for_data "$ZEROCLAW_DATA_DIR" + return + fi + if [[ -n "${ZEROCLAW_WORKSPACE:-}" ]]; then + resolve_config_dir_for_data "$ZEROCLAW_WORKSPACE" + return + fi + printf '%s\n' "$HOME/.zeroclaw" +} + +INSTALL_ROOT="$(resolve_install_root)" + +usage() { + cat <<'EOF' +Usage: install-skills.sh [--global | --agent ALIAS | --shared | --dir PATH] [--include-manual] + + --global Install into the default agent workspace (same as --agent default) + --agent ALIAS Install into the agent's workspace skills directory + --agent all Install into every configured agent workspace + --shared Install bundle at /shared/skills/compound_engineering/ + --dir PATH Install into an explicit skills directory + --include-manual Also install manual-only skills (disable-model-invocation: true) + +Set ZEROCLAW_INSTALL_ROOT to override the install root explicitly. +When unset, install root follows ZeroClaw runtime precedence: + ZEROCLAW_CONFIG_DIR > ZEROCLAW_DATA_DIR > ZEROCLAW_WORKSPACE > ~/.zeroclaw + +Per-agent destinations honor [agents..workspace.path] when set in config.toml. + +ZeroClaw v0.8+ loads agent skills from per-agent workspace paths, not the legacy +~/.zeroclaw/workspace/skills tree. This script does not call zeroclaw skills install +(that CLI writes to config.data_dir/skills, which agents do not read). + +For --shared, add to /config.toml: + + [skill_bundles.compound_engineering] + + [agents.default] + skill_bundles = ["compound_engineering"] + +CE skills ship bundled shell/Python scripts. Enable allow_scripts before use: + + [skills] + allow_scripts = true +EOF + exit 1 +} + +SCOPE="--global" +AGENT_ALIAS="default" +DEST="" +INCLUDE_MANUAL=false +DESTS=() + +while [[ $# -gt 0 ]]; do + case "$1" in + --global) + SCOPE="--global" + shift + ;; + --agent) + [[ $# -ge 2 ]] || usage + SCOPE="--agent" + AGENT_ALIAS="$2" + shift 2 + ;; + --shared) + SCOPE="--shared" + shift + ;; + --dir) + [[ $# -ge 2 ]] || usage + SCOPE="--dir" + DEST="$2" + shift 2 + ;; + --include-manual) + INCLUDE_MANUAL=true + shift + ;; + --use-zeroclaw-cli) + echo "warn: --use-zeroclaw-cli is deprecated and ignored (zeroclaw skills install targets data_dir, not agent workspaces)" >&2 + shift + ;; + *) + usage + ;; + esac +done + +read_agent_workspace_path() { + local alias="$1" + local config="$INSTALL_ROOT/config.toml" + + [[ -f "$config" ]] || return 0 + + awk -v alias="$alias" ' + function trim(s) { + sub(/^[ \t]+/, "", s) + sub(/[ \t]+$/, "", s) + return s + } + function unquote(s) { + s = trim(s) + if (s ~ /^".*"$/) { + sub(/^"/, "", s) + sub(/"$/, "", s) + } else if (s ~ /^'\''.*'\''$/) { + sub(/^'\''/, "", s) + sub(/'\''$/, "", s) + } + return s + } + /^\[agents\./ { + in_agent = ($0 == "[agents." alias "]" || $0 == "[agents.\"" alias "\"]") + in_workspace = ($0 == "[agents." alias ".workspace]" || $0 == "[agents.\"" alias "\".workspace]") + next + } + in_workspace && /^[ \t]*path[ \t]*=/ { + sub(/^[ \t]*path[ \t]*=[ \t]*/, "") + print unquote($0) + exit + } + in_agent && /^[ \t]*workspace[ \t]*=[ \t]*\{/ { + line = $0 + sub(/^[ \t]*workspace[ \t]*=[ \t]*\{[ \t]*/, "", line) + sub(/\}[ \t]*$/, "", line) + split(line, parts, /,[ \t]*/) + for (i in parts) { + if (parts[i] ~ /^path[ \t]*=/) { + sub(/^path[ \t]*=[ \t]*/, "", parts[i]) + print unquote(parts[i]) + exit + } + } + } + ' "$config" +} + +agent_configured() { + local alias="$1" + local config="$INSTALL_ROOT/config.toml" + + if [[ -f "$config" ]] && grep -qE "^\[agents\.(${alias}|\"${alias}\")\]" "$config"; then + return 0 + fi + + if [[ -d "$INSTALL_ROOT/agents/$alias" && ! -f "$config" ]]; then + return 0 + fi + + return 1 +} + +require_agent() { + local alias="$1" + if agent_configured "$alias"; then + return 0 + fi + + echo "error: agent '$alias' not configured under $INSTALL_ROOT — run zeroclaw quickstart or pass --agent with a valid alias" >&2 + exit 1 +} + +agent_skills_dir() { + local alias="$1" + local custom_path + + custom_path="$(read_agent_workspace_path "$alias")" + if [[ -n "$custom_path" ]]; then + printf '%s\n' "$(expand_path "$custom_path")/skills" + return + fi + + printf '%s\n' "$INSTALL_ROOT/agents/$alias/workspace/skills" +} + +list_configured_agent_aliases() { + local config="$INSTALL_ROOT/config.toml" + [[ -f "$config" ]] || return 1 + + awk ' + /^\[agents\.([a-zA-Z0-9_-]+|\"[^\"]+\")\]$/ { + line = $0 + sub(/^\[agents\./, "", line) + sub(/\]$/, "", line) + gsub(/^"|"$/, "", line) + print line + } + ' "$config" +} + +resolve_destinations() { + case "$SCOPE" in + --global | --agent) + if [[ "$AGENT_ALIAS" == "all" ]]; then + local aliases=() + local alias_name + + while IFS= read -r alias_name; do + [[ -n "$alias_name" ]] || continue + aliases+=("$alias_name") + done < <(list_configured_agent_aliases || true) + + if [[ ${#aliases[@]} -eq 0 ]]; then + if [[ ! -d "$INSTALL_ROOT/agents" ]]; then + echo "error: no agents directory at $INSTALL_ROOT/agents" >&2 + exit 1 + fi + local agent_dir + for agent_dir in "$INSTALL_ROOT/agents"/*/; do + [[ -d "$agent_dir" ]] || continue + alias_name="$(basename "$agent_dir")" + if agent_configured "$alias_name"; then + aliases+=("$alias_name") + fi + done + fi + + if [[ ${#aliases[@]} -eq 0 ]]; then + echo "error: no configured agents found under $INSTALL_ROOT" >&2 + exit 1 + fi + + for alias_name in "${aliases[@]}"; do + DESTS+=("$(agent_skills_dir "$alias_name")") + done + else + require_agent "$AGENT_ALIAS" + DESTS+=("$(agent_skills_dir "$AGENT_ALIAS")") + fi + ;; + --shared) + DESTS+=("$INSTALL_ROOT/shared/skills/$SHARED_BUNDLE") + ;; + --dir) + [[ -n "$DEST" ]] || usage + DESTS+=("$DEST") + ;; + *) + usage + ;; + esac +} + +if [[ ! -d "$SKILLS_SRC" ]]; then + echo "error: skills directory not found at $SKILLS_SRC" >&2 + exit 1 +fi + +resolve_destinations + +copy_skill() { + local src="$1" + local dest="$2" + rm -rf "$dest" + cp -R "$src" "$dest" +} + +install_to_dest() { + local dest="$1" + local installed=0 + local skipped=0 + local manual_omitted=0 + local manual_included=0 + local manual_removed=0 + + mkdir -p "$dest" + + for skill_dir in "$SKILLS_SRC"/*/; do + [[ -f "${skill_dir}SKILL.md" ]] || continue + local name + name="$(basename "$skill_dir")" + local is_manual=false + + if grep -qE '^disable-model-invocation:[[:space:]]*true[[:space:]]*$' "${skill_dir}SKILL.md"; then + is_manual=true + if [[ "$INCLUDE_MANUAL" != "true" ]]; then + local target="$dest/$name" + if [[ -e "$target" ]]; then + rm -rf "$target" + echo "removed $name: manual-only skill" >&2 + manual_removed=$((manual_removed + 1)) + fi + echo "skip $name: manual-only (disable-model-invocation)" >&2 + manual_omitted=$((manual_omitted + 1)) + continue + fi + echo "warn $name: manual-only skill installed — ZeroClaw ignores disable-model-invocation" >&2 + manual_included=$((manual_included + 1)) + fi + + local target="$dest/$name" + if [[ -e "$target" && ! -d "$target" ]]; then + echo "skip $name: $target exists and is not a directory" >&2 + skipped=$((skipped + 1)) + continue + fi + + copy_skill "$skill_dir" "$target" + echo "installed $name -> $target" + installed=$((installed + 1)) + done + + if [[ "$INCLUDE_MANUAL" == "true" ]]; then + echo "done: $installed installed, $skipped skipped, $manual_included manual-only included (destination: $dest)" + else + echo "done: $installed installed, $skipped skipped, $manual_omitted manual-only omitted, $manual_removed manual-only removed (destination: $dest)" + fi +} + +for dest in "${DESTS[@]}"; do + install_to_dest "$dest" +done + +if [[ ${#DESTS[@]} -gt 1 ]]; then + echo "completed installs for ${#DESTS[@]} destinations under $INSTALL_ROOT" +fi diff --git a/README.md b/README.md index 6c0aad736..5871ba445 100644 --- a/README.md +++ b/README.md @@ -326,6 +326,17 @@ The bundled `.agy/` directory remains a compatibility entry point (`agy plugin i See [`.agy/INSTALL.md`](.agy/INSTALL.md) for pinning, local development, uninstall, and legacy Gemini import. +### ZeroClaw + +[ZeroClaw](https://github.com/zeroclaw-labs/zeroclaw) loads CE skills from per-agent workspace paths (`~/.zeroclaw/agents//workspace/skills/`). Run `zeroclaw quickstart` first, enable bundled scripts in `~/.zeroclaw/config.toml` (`[skills] allow_scripts = true`), then install from a checkout: + +```bash +git clone https://github.com/EveryInc/compound-engineering-plugin +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global +``` + +Re-run the install script after updating the checkout. See [`.zeroclaw/INSTALL.md`](.zeroclaw/INSTALL.md) for per-agent paths, pinning, and uninstall steps. + ### Existing Installs Compound Engineering moved to a root-native, skills-only layout. An existing marketplace install keeps a **cached** marketplace snapshot that still points at the old `plugins/compound-engineering` path, so updating the plugin on its own reads that stale snapshot and leaves you on the previous version. Refresh the cached marketplace **first**, then update the plugin — order matters. @@ -445,6 +456,14 @@ agy plugin install "$PWD/.agy" See [`.agy/INSTALL.md`](.agy/INSTALL.md) for remote install and pinning examples. +**ZeroClaw** + +```bash +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global +``` + +Set `[skills] allow_scripts = true` in `~/.zeroclaw/config.toml` before installing. + ## Limitations OpenCode and Pi use native package/plugin loading from this repository. The Bun CLI remains for repository development and converter maintenance, not normal installation. diff --git a/docs/solutions/integrations/native-plugin-install-strategy.md b/docs/solutions/integrations/native-plugin-install-strategy.md index 039376996..798bdb912 100644 --- a/docs/solutions/integrations/native-plugin-install-strategy.md +++ b/docs/solutions/integrations/native-plugin-install-strategy.md @@ -1,7 +1,7 @@ --- title: "Native plugin install strategy for supported harnesses" date: 2026-06-19 -last_updated: 2026-06-23 +last_updated: 2026-06-30 category: integrations module: installer problem_type: integration_decision @@ -25,6 +25,7 @@ tags: - antigravity - opencode - pi + - zeroclaw --- # Native Plugin Install Strategy @@ -49,6 +50,7 @@ The install strategy follows from that: prefer each harness's native plugin/pack | OpenCode | Git-backed OpenCode plugin entry in `opencode.json` | No | `.opencode/plugins/compound-engineering.js` registers the CE skills directory directly. | | Pi | Git-backed Pi package install from this repository | No | Root `package.json` exposes `.pi/extensions/compound-engineering.ts` and the CE skills directory. `pi-ask-user` is a recommended companion for richer prompts. | | Antigravity CLI | Native plugin install from root `plugin.json` + `skills/`, or bundled `.agy/` entry point | No | `agy plugin install https://github.com/EveryInc/compound-engineering-plugin` for one-command remote install. `.agy/plugin.json` symlinks to the root manifest; `.agy/skills` symlinks to `skills/`. | +| ZeroClaw | Native skills install via `.zeroclaw/scripts/install-skills.sh` | No | Copies CE skills into agent workspace skills dirs (honors `[agents..workspace.path]`). Install root follows `ZEROCLAW_CONFIG_DIR` > `ZEROCLAW_DATA_DIR` > `ZEROCLAW_WORKSPACE`. Set `[skills] allow_scripts = true` for script-bearing CE skills. | Kiro is no longer a documented CE install target. Historical converter and cleanup code may remain for regression coverage or old artifact handling, but user-facing install docs should not advertise Kiro. @@ -129,6 +131,26 @@ The committed `.agy/` bundle remains for explicit local installs (`agy plugin in `agy` still reads `GEMINI.md` as workspace context. See `.agy/INSTALL.md` for pinning, validation, and uninstall. +## ZeroClaw + +ZeroClaw v0.8+ loads agent skills from per-agent workspace paths at `~/.zeroclaw/agents//workspace/skills/`, or from shared bundles under `~/.zeroclaw/shared/skills//` when referenced in agent config. CE ships `.zeroclaw/scripts/install-skills.sh`, which copies each directory under this repository's `skills/` into the chosen destination. The legacy `~/.zeroclaw/workspace/skills/` tree is not used by the current agent loader. + +Recommended install (default agent after `zeroclaw quickstart`): + +```bash +git clone https://github.com/EveryInc/compound-engineering-plugin +./compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global +``` + +Enable bundled scripts in `~/.zeroclaw/config.toml` before installing — many CE skills ship `scripts/*.sh` and `scripts/*.py`: + +```toml +[skills] +allow_scripts = true +``` + +For multi-agent hosts, use `--shared` plus a `[skill_bundles.compound_engineering]` entry (see `.zeroclaw/INSTALL.md`). Re-run the install script after pulling a newer CE release. The script skips manual-only skills by default; pass `--include-manual` when needed. + ## Kimi Code CLI Kimi Code CLI has a native plugin surface, so CE should not maintain a Kimi converter target for normal installation. The root `.kimi-plugin/plugin.json` declares the CE skills directory with `skills: "./skills/"` and carries display metadata through Kimi's `interface` object. diff --git a/docs/specs/zeroclaw.md b/docs/specs/zeroclaw.md new file mode 100644 index 000000000..f95a4743f --- /dev/null +++ b/docs/specs/zeroclaw.md @@ -0,0 +1,88 @@ +# ZeroClaw Spec (Skills) + +Last verified: 2026-06-30 + +## Primary sources + +``` +https://github.com/zeroclaw-labs/zeroclaw +https://github.com/zeroclaw-labs/zeroclaw/blob/master/docs/book/src/tools/skills.md +https://github.com/zeroclaw-labs/zeroclaw/blob/master/docs/book/src/agents/filesystem.md +``` + +## Skills (primary CE install surface) + +ZeroClaw skills follow the open [Agent Skills](https://agentskills.io) standard. Each skill is a directory containing `SKILL.md` with YAML frontmatter (`name`, `description`, `version`, `author`, `tags`). ZeroClaw loads skills at agent boot from the per-agent workspace and from configured shared skill bundles. + +### Discovery paths (v0.8+) + +| Scope | Path | Loaded by | +| --- | --- | --- | +| Per-agent workspace (primary) | `~/.zeroclaw/agents//workspace/skills//` | `zeroclaw agent -a ` | +| Shared skill bundle | `~/.zeroclaw/shared/skills///` | Agents with `[agents.].skill_bundles` referencing the bundle | + +CE uses bundle alias `compound_engineering` (underscore, not hyphen — ZeroClaw aliases must match `[a-z0-9][a-z0-9_]{0,62}`). +| Legacy (pre-v0.8 migration) | `~/.zeroclaw/workspace/skills/` | Not used by current agent loader | + +CE ships skills at `./skills//SKILL.md` in this repository. Compound Engineering does **not** copy skills into a generated tree for ZeroClaw at release time; users install from a checkout with `.zeroclaw/scripts/install-skills.sh`. + +### Copy-only install (no symlinks) + +ZeroClaw's skill audit rejects symlinked skill directories and symlinked files inside a skill. The CE installer copies each skill directory into the target skills path. Re-run the installer after pulling a newer CE release to refresh copies. + +### Do not use `zeroclaw skills install` for CE bulk install + +The ZeroClaw CLI's `skills install` command writes under `config.data_dir/skills/`. Agent sessions load from `agent_workspace_dir(alias)/skills/` (and optional shared bundles), not from `data_dir`. The CE install script copies directly into agent workspace paths instead. Install root follows ZeroClaw runtime precedence (`ZEROCLAW_CONFIG_DIR` > `ZEROCLAW_DATA_DIR` > `ZEROCLAW_WORKSPACE` > `~/.zeroclaw`), honors `[agents..workspace.path]` overrides, and refuses unknown agent aliases when `config.toml` is present. + +### Bundled scripts + +Many CE skills include `scripts/*.sh` and `scripts/*.py`. ZeroClaw blocks script-like files unless `skills.allow_scripts = true` in `~/.zeroclaw/config.toml`. + +### Manual-only skills + +Some CE skills set `disable-model-invocation: true` so Claude and Codex do not auto-invoke them (for example `lfg`, `ce-dogfood`, `ce-polish`). ZeroClaw's frontmatter parser does not read that field. `.zeroclaw/scripts/install-skills.sh` skips manual-only skills by default; pass `--include-manual` to copy them anyway. + +## Instruction files + +ZeroClaw projects commonly use root `AGENTS.md` for workspace context. CE skills reference "the project's active instructions and conventions already in your context" rather than hardcoding harness-specific filenames. + +## Install commands + +Default agent workspace from a checkout: + +```bash +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global +``` + +Explicit agent or all agents: + +```bash +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --agent my-agent +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --agent all +``` + +Shared bundle (requires config — see `.zeroclaw/INSTALL.md`): + +```bash +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --shared +``` + +Manual-only skills require the opt-in flag: + +```bash +/path/to/compound-engineering-plugin/.zeroclaw/scripts/install-skills.sh --global --include-manual +``` + +After installing or updating skills, restart the agent session or gateway if the skill list does not refresh. + +## Update and removal + +Re-run the install script after pulling a newer CE release. The script removes prior copies before reinstalling. + +To remove CE skills, delete the skill directories from the target workspace or shared bundle path. + +## Subagent and tool notes + +CE skills dispatch generic subagents with skill-local prompt assets under `references/agents/` and `references/personas/`. ZeroClaw's subagent and MCP capabilities vary by deployment (CLI, gateway, zerocode). Skills degrade gracefully when a primitive is unavailable — the same cross-harness posture used for OpenCode and Pi. + +Bundled shell scripts in skills use the model-filled `SKILL_DIR` anchor documented in the repository's contributor instructions so paths resolve when the agent's working directory is the user's project, not the skill directory. diff --git a/src/release/components.ts b/src/release/components.ts index 9c999fba7..2c073046b 100644 --- a/src/release/components.ts +++ b/src/release/components.ts @@ -25,6 +25,7 @@ const FILE_COMPONENT_MAP: Array<{ component: ReleaseComponent; prefixes: string[ ".codex-plugin/", ".kimi-plugin/plugin.json", ".opencode/", + ".zeroclaw/", ".pi/", "AGENTS.md", "CLAUDE.md", diff --git a/tests/zeroclaw-install-skills.test.ts b/tests/zeroclaw-install-skills.test.ts new file mode 100644 index 000000000..b6800304a --- /dev/null +++ b/tests/zeroclaw-install-skills.test.ts @@ -0,0 +1,145 @@ +import { describe, expect, test } from "bun:test" +import { promises as fs } from "fs" +import os from "os" +import path from "path" + +const installScript = path.join( + import.meta.dir, + "..", + ".zeroclaw", + "scripts", + "install-skills.sh", +) + +const sampleSkill = "ce-brainstorm" +const defaultDerivedSkills = (root: string) => + path.join(root, "agents", "default", "workspace", "skills", sampleSkill) + +type RunResult = { + exitCode: number + stdout: string + stderr: string +} + +async function runInstall( + env: Record, + args: string[] = ["--global"], +): Promise { + const proc = Bun.spawn(["bash", installScript, ...args], { + cwd: path.join(import.meta.dir, ".."), + env: { ...process.env, ...env }, + stderr: "pipe", + stdout: "pipe", + }) + + const [exitCode, stdout, stderr] = await Promise.all([ + proc.exited, + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + ]) + + return { exitCode, stdout, stderr } +} + +async function pathExists(filePath: string): Promise { + try { + await fs.access(filePath) + return true + } catch { + return false + } +} + +describe("zeroclaw install-skills.sh", () => { + test("honors ZEROCLAW_DATA_DIR when config.toml lives at the data root", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "zc-data-root-")) + const dataRoot = path.join(root, "profile") + await fs.mkdir(path.join(dataRoot, "agents", "default"), { recursive: true }) + await fs.writeFile( + path.join(dataRoot, "config.toml"), + "[agents.default]\n", + ) + + const result = await runInstall({ + ZEROCLAW_INSTALL_ROOT: undefined, + ZEROCLAW_CONFIG_DIR: undefined, + ZEROCLAW_DATA_DIR: dataRoot, + ZEROCLAW_WORKSPACE: undefined, + }) + + expect(result.exitCode).toBe(0) + expect(await pathExists(path.join(dataRoot, "agents", "default", "workspace", "skills", sampleSkill))).toBe( + true, + ) + expect(await pathExists(defaultDerivedSkills(root))).toBe(false) + }) + + test("honors ZEROCLAW_DATA_DIR when config.toml lives under parent .zeroclaw", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "zc-data-nested-")) + const installRoot = path.join(root, "project", ".zeroclaw") + const dataDir = path.join(root, "project", "data") + await fs.mkdir(path.join(installRoot, "agents", "default"), { recursive: true }) + await fs.mkdir(dataDir, { recursive: true }) + await fs.writeFile(path.join(installRoot, "config.toml"), "[agents.default]\n") + + const result = await runInstall({ + ZEROCLAW_INSTALL_ROOT: undefined, + ZEROCLAW_CONFIG_DIR: undefined, + ZEROCLAW_DATA_DIR: dataDir, + ZEROCLAW_WORKSPACE: undefined, + }) + + expect(result.exitCode).toBe(0) + expect( + await pathExists(path.join(installRoot, "agents", "default", "workspace", "skills", sampleSkill)), + ).toBe(true) + }) + + test("prefers ZEROCLAW_CONFIG_DIR over ZEROCLAW_DATA_DIR", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "zc-config-wins-")) + const configRoot = path.join(root, "config-profile") + const dataRoot = path.join(root, "data-profile") + await fs.mkdir(path.join(configRoot, "agents", "default"), { recursive: true }) + await fs.mkdir(path.join(dataRoot, "agents", "default"), { recursive: true }) + await fs.writeFile(path.join(configRoot, "config.toml"), "[agents.default]\n") + await fs.writeFile(path.join(dataRoot, "config.toml"), "[agents.default]\n") + + const result = await runInstall({ + ZEROCLAW_CONFIG_DIR: configRoot, + ZEROCLAW_DATA_DIR: dataRoot, + ZEROCLAW_INSTALL_ROOT: undefined, + ZEROCLAW_WORKSPACE: undefined, + }) + + expect(result.exitCode).toBe(0) + expect( + await pathExists(path.join(configRoot, "agents", "default", "workspace", "skills", sampleSkill)), + ).toBe(true) + expect( + await pathExists(path.join(dataRoot, "agents", "default", "workspace", "skills", sampleSkill)), + ).toBe(false) + }) + + test("installs into [agents..workspace.path] when configured", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "zc-custom-workspace-")) + const installRoot = path.join(root, "install") + const customWorkspace = path.join(root, "custom-workspace") + await fs.mkdir(path.join(installRoot, "agents", "default"), { recursive: true }) + await fs.writeFile( + path.join(installRoot, "config.toml"), + `[agents.default]\n\n[agents.default.workspace]\npath = "${customWorkspace}"\n`, + ) + + const result = await runInstall({ + ZEROCLAW_INSTALL_ROOT: installRoot, + ZEROCLAW_CONFIG_DIR: undefined, + ZEROCLAW_DATA_DIR: undefined, + ZEROCLAW_WORKSPACE: undefined, + }) + + expect(result.exitCode).toBe(0) + expect(await pathExists(path.join(customWorkspace, "skills", sampleSkill))).toBe(true) + expect(await pathExists(defaultDerivedSkills(installRoot))).toBe(false) + expect(result.stdout).toContain(customWorkspace) + }) +})