Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "envrelay",
"version": "1.0.0",
"version": "1.0.1",
"description": "Move a development environment to a new machine: back up dotfiles, credentials, git repositories, AI coding agent state and installed software into one passphrase-encrypted file, then restore it step by step.",
"author": {
"name": "FutrixDev",
Expand Down
22 changes: 20 additions & 2 deletions .github/scripts/check-versions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
# (metadata.version in SKILL.md) picks the release its install.sh fetches, so a
# skill installed from anywhere gets the binary it was written for. The Claude
# Code plugin's (.claude-plugin/plugin.json) is what /plugin compares to decide
# whether there is an update. With TAG, the tag must be v<version> as well.
# envrelay.com names the version too, in its own repository (ADR-025).
# whether there is an update. plugin.json at the root is the same manifest for
# Copilot CLI, VS Code and awesome-copilot, in the Agent Plugins format
# (ADR-026), so it must match the Claude Code one field for field. With TAG, the
# tag must be v<version> as well. envrelay.com names the version too, in its
# own repository (ADR-025).
set -eu

root=$(git -C "$(dirname "$0")" rev-parse --show-toplevel)
Expand Down Expand Up @@ -44,6 +47,21 @@ esac
plugin=$(python3 -c 'import json, sys; print(json.load(open(sys.argv[1]))["version"])' .claude-plugin/plugin.json)
[ "$plugin" = "$version" ] || fail ".claude-plugin/plugin.json has version $plugin, Cargo.toml has $version"

# The Agent Plugins manifest is the Claude Code one plus the $schema that opts
# it into that format.
agent=$(python3 - plugin.json .claude-plugin/plugin.json <<'EOF'
import json, sys
schema = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json"
agent, claude = (json.load(open(path)) for path in sys.argv[1:])
if agent.pop("$schema", None) != schema:
print(f'plugin.json must have "$schema": "{schema}"')
differ = sorted(k for k in agent.keys() | claude.keys() if agent.get(k) != claude.get(k))
if differ:
print("plugin.json and .claude-plugin/plugin.json differ in " + ", ".join(differ))
EOF
)
[ -z "$agent" ] || fail "$agent"

if [ $# -gt 0 ] && [ "$1" != "v$version" ]; then
fail "tag $1 does not match Cargo.toml's version: expected v$version"
fi
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

[package]
name = "envrelay"
version = "1.0.0"
version = "1.0.1"
edition = "2024"
rust-version = "1.97.0"
license = "MIT OR Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ sh .github/scripts/check-versions.sh

## Releasing

1. Set the new version in `Cargo.toml`, in `metadata.version` in [`skills/envrelay/SKILL.md`](skills/envrelay/SKILL.md) and in [`.claude-plugin/plugin.json`](.claude-plugin/plugin.json), and run `cargo build` so `Cargo.lock` follows. CI fails until the three agree.
1. Set the new version in `Cargo.toml`, in `metadata.version` in [`skills/envrelay/SKILL.md`](skills/envrelay/SKILL.md), in [`.claude-plugin/plugin.json`](.claude-plugin/plugin.json) and in [`plugin.json`](plugin.json), and run `cargo build` so `Cargo.lock` follows. CI fails until the four agree.
2. Once that is merged, tag the merge commit on GitHub's main and push the tag right away (until the release exists, a skill installed from main asks for a release that is not there yet), for example:

```bash
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ sh .github/scripts/check-versions.sh

## 发版

1. 在 `Cargo.toml`、[`skills/envrelay/SKILL.md`](skills/envrelay/SKILL.md) 的 `metadata.version`、[`.claude-plugin/plugin.json`](.claude-plugin/plugin.json) 三处改成新版本号,再跑一次 `cargo build` 让 `Cargo.lock` 跟上。三处不一致时 CI 会失败。
1. 在 `Cargo.toml`、[`skills/envrelay/SKILL.md`](skills/envrelay/SKILL.md) 的 `metadata.version`、[`.claude-plugin/plugin.json`](.claude-plugin/plugin.json)、[`plugin.json`](plugin.json) 四处改成新版本号,再跑一次 `cargo build` 让 `Cargo.lock` 跟上。四处不一致时 CI 会失败。
2. 合并之后,马上给 GitHub 上 main 的合并提交打 tag 并推送(release 出来之前,从 main 装的 skill 会去找一个还不存在的 release),例如:

```bash
Expand Down
38 changes: 21 additions & 17 deletions docs/decisions/024-one-command-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
Date: 2026-09-24
Status: accepted
Extends: ADR-022 (deterministic mechanics scripts)
Amended by: ADR-025 (the site moves to a repository of its own)
Amended by: ADR-025 (the site moves to a repository of its own), ADR-026
(frontmatter and manifests that awesome-copilot accepts)

## Context

Expand Down Expand Up @@ -101,15 +102,16 @@ step: it shows the user the `--dry-run` plan, says what `envrelay` is, and runs
`install.sh --bin-only` only on a yes. Installing the passphrase layer is the
user's decision, like every other install the skill proposes.

**One version, written in three places.** `Cargo.toml` (what `envrelay
**One version, written in four places.** `Cargo.toml` (what `envrelay
--version` prints), `metadata.version` in SKILL.md (which release the skill's
installer fetches) and `.claude-plugin/plugin.json` (what Claude Code compares
to offer an update). `.github/scripts/check-versions.sh` fails CI and the
release preflight unless they agree. It asks the installer which release it
would fetch rather than reading SKILL.md a second way, so the check and the
user's path share one parser. The homepage's badge and demo name the version
too, from a meta tag that the site's repository sets after each release
(ADR-025).
installer fetches), `.claude-plugin/plugin.json` (what Claude Code compares to
offer an update) and `plugin.json` at the root (the same manifest for Copilot
CLI, VS Code and awesome-copilot, ADR-026). `.github/scripts/check-versions.sh`
fails CI and the release preflight unless they agree. It asks the installer
which release it would fetch rather than reading SKILL.md a second way, so the
check and the user's path share one parser. The homepage's badge and demo name
the version too, from a meta tag that the site's repository sets after each
release (ADR-025).

That is what makes a skill installed from anywhere safe. Run from inside an
installed skill, `install.sh` fetches the release matching the `SKILL.md`
Expand All @@ -125,13 +127,14 @@ the latest release.
- `allowed-tools: Bash(python3 ${CLAUDE_SKILL_DIR}/scripts/*)` pre-approves the
scripts and nothing else. The installer and `envrelay` stay behind a prompt.
- `compatibility` names what the skill needs to run.
- `metadata.openclaw` declares the required binaries (python3, git), the
operating systems (darwin, linux), the two optional environment variables
the installer reads, and the homepage. OpenClaw decides from these whether
the skill can load, and ClawHub's review compares them with what the code
does. The Agent Skills spec expects `metadata` to map strings to strings;
OpenClaw documents this nested shape, and no validator we have found
rejects it.
- A top-level `clawdis` block declares the required binaries (python3, git),
the operating systems (darwin, linux), the two optional environment
variables the installer reads, and the homepage, and ClawHub's review
compares them with what the code does. v1.0.0 had them in
`metadata.openclaw`, where OpenClaw itself also read them to decide whether
the skill can load. They moved because awesome-copilot's lint refuses a
`metadata` value that is not a string. OpenClaw does not read the new block
(ADR-026).
- There is no `license` field. ClawHub releases every skill it publishes under
MIT-0 and asks for no conflicting license terms in SKILL.md, so the field
would be wrong there. The repository's MIT OR Apache-2.0 covers the source,
Expand Down Expand Up @@ -216,7 +219,8 @@ Smithery, GitHub's awesome-copilot, the directories that crawl GitHub, and the
awesome lists. `docs/publishing.md` is the checklist, with what each one asks
for and why some are left out. The repository is also a plugin marketplace of
its own (`.claude-plugin/`), which Claude Code, GitHub Copilot CLI and VS Code
read.
read; the last two take the plugin's manifest from the root `plugin.json`
(ADR-026).

`gh skill publish` is used only as `--dry-run`, plus the `agent-skills` repo
topic it would otherwise add. Without `--dry-run` it creates a GitHub release
Expand Down
5 changes: 3 additions & 2 deletions docs/decisions/025-site-in-its-own-repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Date: 2026-09-25
Status: accepted
Amends: ADR-024 (one command installs the binary and the skill)
Amended by: ADR-026 (frontmatter and manifests that awesome-copilot accepts)

## Context

Expand Down Expand Up @@ -37,8 +38,8 @@ installed pointed at it.
follows this repository's release workflow. It needs nothing from the site's
repository: it compares what envrelay.com serves with the release's
`install.sh`, byte for byte, then installs through the one-liner.
- **The version is written in three places here**: `Cargo.toml`, SKILL.md's
`metadata.version` and `.claude-plugin/plugin.json`, which
- **The version is written in four places here**: `Cargo.toml`, SKILL.md's
`metadata.version`, and the plugin's two manifests since ADR-026, which
`check-versions.sh` compares. The homepage keeps its `envrelay-version` meta,
which a maintainer sets in the site's repository once a release is out, and
deploys.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# ADR-026: Frontmatter and manifests that awesome-copilot accepts

Date: 2026-09-25
Status: accepted
Amends: ADR-024 (one command installs the binary and the skill), ADR-025 (the
site moves to a repository of its own)

## Context

github/awesome-copilot lists a plugin that lives in its own repository once an
issue form names a release tag, its commit and a version. Their intake
(`eng/external-plugin-quality-gates.mjs`, which pins `@microsoft/vally` 0.12.0
and Ajv 8.20.0) checks out that commit, then:

- reads the plugin's manifest with `git show` from `.github/plugin/plugin.json`,
`.plugin/plugin.json` or `plugin.json` at the root, the first that exists,
and stops if there is none;
- requires the manifest's `version` to be the one submitted;
- runs `vally lint` over the skills directories the manifest names, or over the
whole repository when it names none;
- installs the plugin with Copilot CLI from a marketplace it makes on the spot,
and checks that the manifest arrived;
- checks the manifest against the Agent Plugins 1.0 specification, whose top
level allows `$schema`, `name`, `version`, `description`, `author`,
`homepage`, `repository`, `license`, `keywords` and `extensions`.

v1.0.0 fails at the first step: its one manifest is
`.claude-plugin/plugin.json`, which Claude Code reads and the intake does not.
It would fail the lint too. vally refuses a SKILL.md `metadata` value that is
not a string ("Metadata values must be strings. Non-string values found for
key(s): openclaw"), and ADR-024 put the declarations for OpenClaw and ClawHub
in a nested `metadata.openclaw`.

Each reader of those declarations looks in a place of its own:

- ClawHub (`convex/lib/skills/index.ts`) takes `metadata.clawdbot`,
`metadata.clawdis` or `metadata.openclaw` if it is an object, and otherwise a
top-level `clawdis` block.
- OpenClaw (`packages/markdown-core/src/frontmatter.ts` and
`src/shared/frontmatter.ts`) turns `metadata` into JSON text, parses it back
and takes an `openclaw` or `clawdbot` object from it. It reads no other
field for these.
- vally, `gh skill` and `claude plugin validate --strict` accept extra
top-level fields. `skills-ref`, the Agent Skills reference validator,
refuses them.

No one frontmatter satisfies all of these.

## Decision

- **The declarations move to a top-level `clawdis` block**, unchanged: the
required binaries, the operating systems, the two optional environment
variables and the homepage. `metadata` keeps only `version`.
- **`plugin.json` at the root is the plugin's Agent Plugins manifest**: the
Claude Code one plus the `$schema` that opts into Agent Plugins 1.0. Copilot
CLI and VS Code read it with that format's semantics, and it is where
awesome-copilot finds it. Claude Code reads only
`.claude-plugin/plugin.json`, where it ignores `$schema`, so that file
stays.
- **`check-versions.sh` compares the two manifests** field for field, apart
from `$schema`. The version is now written in four places.
- **v1.0.1 is the release to submit.**

## Consequences

- vally passes over the whole repository, and the manifest has no field the
specification check warns about.
- ClawHub reads the same values from v1.0.1 as from v1.0.0.
- **OpenClaw no longer sees the declarations.** It used them to decide whether
the skill can load; now it loads the skill on Windows too, or where python3
or git is missing, and knows no homepage for it. The `compatibility` field
still names macOS or Linux, python3 and git, and the skill's first step,
"Before anything: the tools", checks for python3 and git before a backup or
a restore starts.
- `skills-ref validate` fails on `clawdis`. No channel we submit to is known to
run it.
- Another manifest in `.github/plugin/` or `.plugin/` would take over from the
root one at awesome-copilot's intake, and Copilot CLI's documented lookup
checks `.plugin/plugin.json` first as well (`docs/publishing.md`, section 2).

## Alternatives rejected

- **Keep `metadata.openclaw` and leave out awesome-copilot.** OpenClaw would
keep hiding the skill where it cannot run, but the owner chose the listing:
awesome-copilot is the marketplace that Copilot CLI and VS Code ship with.
- **Declare nothing.** ClawHub's review would see an installer that downloads
a binary, and nothing declared to compare it with.
- **`metadata.openclaw` as a JSON string.** vally would accept it, but ClawHub
and OpenClaw both take only an object there.
- **A symlink from the root to `.claude-plugin/plugin.json`.** `git show`
returns the link's target path, not the manifest.
- **The manifest in `.github/plugin/` or `.plugin/`.** Those are the older
locations; Agent Plugins 1.0, which Copilot CLI, VS Code and Cursor load,
puts the manifest at the plugin's root.
- **The root manifest alone.** Claude Code reads only
`.claude-plugin/plugin.json`.
Loading
Loading