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.2",
"version": "1.0.3",
"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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ jobs:
with:
python-version: "3.9"
- run: python3 -m compileall -q skills/envrelay/scripts
# git_classify.py must run nothing a scanned repository's config names.
- run: python3 tests/git_classify.py -v

scripts:
runs-on: ubuntu-latest
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.2"
version = "1.0.3"
edition = "2024"
rust-version = "1.97.0"
license = "MIT OR Apache-2.0"
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ No sudo. envrelay.com sends you to the installer in the [latest release](https:/

- puts `envrelay` in `~/.local/bin`, and adds that directory to `PATH` in your shell's rc file if it is not there yet;
- puts the skill in `~/.agents/skills/envrelay`, where Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and most other agents look for skills, and links it into `~/.claude/skills` for Claude Code (and into `~/.kiro/skills` and `~/.cline/skills` if you use Kiro or Cline);
- checks for python3 3.9+ and git, which the skill's scripts use.
- checks for python3 3.9+ and git 2.31+, which the skill's scripts use.

Then start a new session of your coding agent and say:

Expand Down Expand Up @@ -62,7 +62,7 @@ Each download of `install.sh` from envrelay.com is recorded: the time, the IP ad
### What it needs

- **macOS 11 or later, or Linux**, on x86_64 or arm64. The Linux binaries are static, so any distribution works. On Windows, use WSL.
- **python3 3.9+ and git**, for the skill's scripts. A Mac gets both from Apple's Command Line Tools: if they are missing, the installer opens Apple's installer and you click Install. On Linux, it tells you the package-manager command to run.
- **python3 3.9+ and git 2.31+**, for the skill's scripts. A Mac gets both from Apple's Command Line Tools: if they are missing, the installer opens Apple's installer and you click Install. On Linux, it tells you the package-manager command to run.
- **A coding agent that reads Agent Skills**: Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and many more.

That is all. The package managers you already use (Homebrew, npm, cargo and so on) matter only when you want the agent to reinstall your software on the new machine, and `age` and `zstd` only for the [escape hatch](#the-escape-hatch).
Expand Down Expand Up @@ -222,10 +222,11 @@ cargo clippy --all-targets -- -D warnings
cargo test
cargo build --release
sh tests/installer.sh
python3 tests/git_classify.py
sh .github/scripts/check-versions.sh
```

`tests/installer.sh` runs the installer against a release packaged from this checkout, with a throwaway `HOME` per scenario, so it needs the release build first. The installer downloads only from GitHub, so a stand-in for curl, [`tests/stubs/curl`](tests/stubs/curl), serves that release in GitHub's place. `SH=dash sh tests/installer.sh` runs the installer under another shell; CI runs it under sh, dash, bash and zsh. `check-versions.sh` checks that the version is the same everywhere it is written.
`tests/installer.sh` runs the installer against a release packaged from this checkout, with a throwaway `HOME` per scenario, so it needs the release build first. The installer downloads only from GitHub, so a stand-in for curl, [`tests/stubs/curl`](tests/stubs/curl), serves that release in GitHub's place. `SH=dash sh tests/installer.sh` runs the installer under another shell; CI runs it under sh, dash, bash and zsh. `tests/git_classify.py` checks that the repository inventory starts none of the programs a scanned repository's git config names. `check-versions.sh` checks that the version is the same everywhere it is written.

## Releasing

Expand Down
7 changes: 4 additions & 3 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ curl -fsSL https://envrelay.com/install.sh | sh

- 把 `envrelay` 放进 `~/.local/bin`,如果这个目录还不在 `PATH` 里,就在你的 shell 配置文件里加上;
- 把 skill 放进 `~/.agents/skills/envrelay`——Codex、Cursor、Gemini CLI、GitHub Copilot、OpenCode 等大多数 agent 都从这里找 skill——再为 Claude Code 链接一份到 `~/.claude/skills`(用 Kiro 或 Cline 的话,也链接到 `~/.kiro/skills`、`~/.cline/skills`);
- 检查 skill 的脚本要用的 python3 3.9+ 和 git 在不在。
- 检查 skill 的脚本要用的 python3 3.9+ 和 git 2.31+ 在不在。

然后开一个新的 coding agent 会话,对它说:

Expand Down Expand Up @@ -62,7 +62,7 @@ curl -fsSL https://envrelay.com/install.sh | sh -s -- --dry-run
### 需要什么

- **macOS 11 及以上,或 Linux**,x86_64 或 arm64。Linux 版是静态链接的,任何发行版都能跑。Windows 请用 WSL。
- **python3 3.9+ 和 git**,skill 的脚本要用。Mac 上这两样都来自苹果的 Command Line Tools:缺的话,安装器会打开苹果的安装程序,你点一下“安装”即可。Linux 上它会告诉你该跑哪条包管理器命令。
- **python3 3.9+ 和 git 2.31+**,skill 的脚本要用。Mac 上这两样都来自苹果的 Command Line Tools:缺的话,安装器会打开苹果的安装程序,你点一下“安装”即可。Linux 上它会告诉你该跑哪条包管理器命令。
- **一个能读 Agent Skills 的 coding agent**:Claude Code、Codex、Cursor、Gemini CLI、GitHub Copilot、OpenCode 等等。

就这些。你平时用的包管理器(Homebrew、npm、cargo 等)只在你想让 agent 在新机器上把软件装回来时才用得上;`age` 和 `zstd` 只有走[逃生通道](#逃生通道)时才需要。
Expand Down Expand Up @@ -222,10 +222,11 @@ cargo clippy --all-targets -- -D warnings
cargo test
cargo build --release
sh tests/installer.sh
python3 tests/git_classify.py
sh .github/scripts/check-versions.sh
```

`tests/installer.sh` 用当前 checkout 打出来的 release 跑安装器,每个场景一个一次性的 `HOME`,所以要先 build release。安装器只从 GitHub 下载,所以由一个替身 curl([`tests/stubs/curl`](tests/stubs/curl))代替 GitHub 提供这个 release。`SH=dash sh tests/installer.sh` 换一个 shell 跑安装器;CI 在 sh、dash、bash、zsh 下各跑一遍。`check-versions.sh` 检查所有写了版本号的地方是否一致。
`tests/installer.sh` 用当前 checkout 打出来的 release 跑安装器,每个场景一个一次性的 `HOME`,所以要先 build release。安装器只从 GitHub 下载,所以由一个替身 curl([`tests/stubs/curl`](tests/stubs/curl))代替 GitHub 提供这个 release。`SH=dash sh tests/installer.sh` 换一个 shell 跑安装器;CI 在 sh、dash、bash、zsh 下各跑一遍。`tests/git_classify.py` 检查仓库清点不会启动被扫描仓库的 git 配置里写的任何程序。`check-versions.sh` 检查所有写了版本号的地方是否一致。

## 发版

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# ADR-028: The git inventory runs nothing a repository's config names

Date: 2026-09-26
Status: accepted
Amends: ADR-022 (deterministic mechanics in scripts)

## Context

`git_classify.py` walks the home directory and runs read-only git commands in
every repository it finds: `status`, `remote`, `stash list`, `rev-parse`,
`symbolic-ref`, `rev-list`. Read-only is not the same as running nothing. A
repository's own `.git/config` can name programs that git starts during those
reads:

- `core.fsmonitor`, which `status` asks which files changed;
- a filter driver's `clean` or `process` command, which `status` pipes a file
through when its timestamp changed but its size did not;
- `gpg.program`, which `stash list` calls for every signed stash once
`log.showSignature` is on;
- the same settings in a checked-out submodule, where `status` runs a second
`git status`.

A repository that came from someone else — a cloned exercise, an unpacked
archive, a directory another tool wrote — is data the scan finds, not something
the user chose to trust. The skill runs the scan before the user has looked at
any of them.

ClawHub re-scanned v1.0.2 on 2026-09-25 at 15:43 UTC and moved its audit from
Pass to Review. Its one substantive finding (A.I.G T09, high) is this: the
inventory can run code a scanned repository configures.

## Decision

- **Every git command in `git_classify.py` runs with the repository's helpers
switched off**: `core.fsmonitor` empty (not `false`, which git before 2.36
runs as a command), `core.hooksPath` set to `/dev/null`, and
`log.showSignature` off.
- **Filter drivers that only the repository defines are switched off for
`status`**: `clean`, `smudge` and `process` empty, `required` false. The
script lists the repository's config with its scopes, and those of its
checked-out submodules, and switches off each driver defined outside the
system, global and command scopes. A driver the user installed — git lfs
puts itself in the global config — keeps running.
- **The settings travel in `GIT_CONFIG_COUNT`/`GIT_CONFIG_KEY_n`/
`GIT_CONFIG_VALUE_n`, not `-c`.** `-c` splits at the first `=`, and a
driver's name may contain one. The environment also reaches the `git status`
that runs inside each submodule.
- **The script needs git 2.31 or newer**, the first with `GIT_CONFIG_COUNT`.
With an older git every repository is `unknown`, with the version in the
detail; no git command runs in the repository first. The skill's
`compatibility` field says so, and `install.sh`, which checks for what the
scripts need, names an older git and the version it found.
- **`tests/git_classify.py` proves each case both ways**: the script leaves the
program unrun, and plain git making the same read runs it. CI runs it on
Linux under Python 3.9 and on macOS.
- This ships as v1.0.3.

## Consequences

- **A file a repository-only filter would have cleaned can count as
uncommitted.** That errs towards carrying the repository whole, the safe
direction: at worst a clean repository gets `files` instead of `clone`.
- **Users with git older than 2.31 lose the inventory.** macOS's command line
tools shipped 2.24 and 2.30 in the Xcode 11 and 12 years; Debian 11 has
2.30. The installer tells them at install time, not at the first backup.
- **The `git` on `PATH` is trusted.** It is the user's own environment, not
something the scanned repository controls.

## Alternatives rejected

- **Only switch off `core.fsmonitor`.** It is the obvious one, but filters and
signature checks run from the same commands.
- **Switch off every filter driver, the user's too.** Simpler, but every git
lfs repository would read as fully uncommitted and be carried as files.
- **Stop running `git status` and read the index directly.** It would take a
reimplementation of git's change detection, and still have to decide what a
filter would have done.
28 changes: 22 additions & 6 deletions docs/publishing.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,11 +274,11 @@ To publish:
```

```bash
clawhub skill publish ./skills/envrelay --owner futrixdev --name EnvRelay --version 1.0.0 --changelog "First release." --categories operations,development --topics backup,restore,migration,dotfiles,developer-environment --source-repo FutrixDev/envrelay-skill --source-commit "$(git rev-parse HEAD)" --source-ref v1.0.0 --source-path skills/envrelay --dry-run
clawhub skill publish ./skills/envrelay --owner futrixdev --name "EnvRelay: Backup, Restore & Migrate Dev Environments" --version 1.0.0 --changelog "First release." --categories operations,development,productivity --topics backup,migration,dotfiles,developer-environment,new-machine-setup --source-repo FutrixDev/envrelay-skill --source-commit "$(git rev-parse HEAD)" --source-ref v1.0.0 --source-path skills/envrelay --dry-run
```

```bash
clawhub skill publish ./skills/envrelay --owner futrixdev --name EnvRelay --version 1.0.0 --changelog "First release." --categories operations,development --topics backup,restore,migration,dotfiles,developer-environment --source-repo FutrixDev/envrelay-skill --source-commit "$(git rev-parse HEAD)" --source-ref v1.0.0 --source-path skills/envrelay
clawhub skill publish ./skills/envrelay --owner futrixdev --name "EnvRelay: Backup, Restore & Migrate Dev Environments" --version 1.0.0 --changelog "First release." --categories operations,development,productivity --topics backup,migration,dotfiles,developer-environment,new-machine-setup --source-repo FutrixDev/envrelay-skill --source-commit "$(git rev-parse HEAD)" --source-ref v1.0.0 --source-path skills/envrelay
```

For a later release, change the tag (in `git checkout` and `--source-ref`),
Expand All @@ -288,6 +288,17 @@ To publish:
"Version not found" and `latest` stayed at 1.0.1. v1.0.2 was audited and
public within seven minutes of the upload.

ClawHub's search ranks by the name, categories and topics, which only a
new version can change. A query ranks a skill highest when every word is a
word of its slug or name, then when every word begins a word of the name,
then when every word begins a category or topic, and only then when every
word begins a word of the summary (SKILL.md's `description`). Matching is
by prefix, not by stem: "migrating" does not find "migrate". Within a rank,
closeness in meaning comes first, then installs. So the name carries
backup, restore and migrate, and the topics carry what people type that the
name does not. ClawHub takes at most five topics, and at most three
categories from its own list.

4. Check the listing, then the version's security audit. They are separate
verdicts: moderation decides whether the listing is public, and it can be
public (`clean`) while the audit on its page says Review, which asks users
Expand All @@ -302,10 +313,15 @@ To publish:
```

The audit is `version.security`. v1.0.0 and v1.0.1 read `suspicious`,
shown as Review, for the download override that ADR-027 removed; v1.0.2
reads `clean`, shown as Pass. The latest version's audit, with any
findings, is on
<https://clawhub.ai/futrixdev/skills/envrelay/security-audit>.
shown as Review, for the download override that ADR-027 removed. v1.0.2
first read `clean`, shown as Pass; a re-scan on 2026-09-25 at 15:43 UTC
moved it to Review, for the git config that ADR-028 switches off. A
verdict can change after publishing, so check it again before calling a
release clean. The latest version's audit, with any findings, is on
<https://clawhub.ai/futrixdev/skills/envrelay/security-audit>. Most of its
static findings are the skill's subject, not a flaw in it: it names
credential and agent-state paths because it backs them up, and `install.sh`
downloads a binary because that is how it installs one.

```bash
openclaw skills verify @futrixdev/envrelay
Expand Down
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "envrelay",
"version": "1.0.2",
"version": "1.0.3",
"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
8 changes: 4 additions & 4 deletions skills/envrelay/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
name: envrelay
description: Use when backing up, restoring, or migrating a development environment between machines - dotfiles, config, SSH/cloud credentials, git repositories, AI coding agent state (Claude Code, Codex, Cursor and the rest), and installed software - or when working with an .envrelay backup file. Covers what to carry, what to leave behind and rebuild, how to record it in a manifest, and how to replay it on the new machine (macOS and Linux).
compatibility: Needs macOS or Linux with a terminal the user can type into, python3 3.9 or newer, git, and the envrelay binary, which the skill's own installer adds once the user agrees.
description: Use when backing up or restoring a development environment, or when asked to migrate one to a new machine - a new Mac, laptop or Linux box. Carries dotfiles, config, SSH keys and cloud credentials, git repositories, AI coding agent state (Claude Code, Codex, Cursor and the rest), and installed software; also use when working with an .envrelay backup file. Covers what to carry, what to leave behind and rebuild, how to record it in a manifest, and how to replay it on the new machine (macOS and Linux). Once the user agrees, it installs the envrelay binary, which encrypts the backup, into ~/.local/bin.
compatibility: Needs macOS or Linux with a terminal the user can type into, python3 3.9 or newer, git 2.31 or newer, and the envrelay binary, which the skill's own installer adds once the user agrees.
allowed-tools: Bash(python3 ${CLAUDE_SKILL_DIR}/scripts/*)
metadata:
version: "1.0.2"
version: "1.0.3"
clawdis:
requires:
bins:
Expand Down Expand Up @@ -60,7 +60,7 @@ yours.
| Script | Does | Never does |
|---|---|---|
| `stage_copy.py SRC DEST [--exclude N…] [--hash] [--hash-prefix P]` | Copies one entry into staging: permissions and symlinks (dangling included) preserved, excludes applied, unreadable and special files *skipped and recorded* instead of sinking the copy, per-file SHA-256 with `--hash`, keys prefixed with `--hash-prefix`. Cleans up its own partial copy on failure | Follow symlinks, read file contents, write outside DEST |
| `git_classify.py PATH… \| --scan ROOT` | Finds repositories and reports facts: the five-state classification, remotes, branch, head, uncommitted/unpushed/stash counts | Choose a strategy, run any mutating git command |
| `git_classify.py PATH… \| --scan ROOT` | Finds repositories and reports facts: the five-state classification, remotes, branch, head, uncommitted/unpushed/stash counts | Choose a strategy, run any mutating git command, let a repository's config start a program |
| `sw_inventory.py [--apps] [--diff MANIFEST]` | One-shot batch enumeration of every present package manager, GUI apps by bundle id, and the manifest-vs-machine diff | Install, uninstall, resolve names against registries |
| `restore_ledger.py LEDGER CMD…` | Per-item restore state: `init` seeds one `pending` line per manifest entry, then `set`, `list`, `report`. Survives an interrupted restore so you resume instead of re-deriving | Change anything outside its own ledger file |
| `agent_inventory.py [--agents …] [--no-sizes] [--recent-days N] [--diff MANIFEST]` | One pass over every AI coding agent on the machine: which are installed and at what version, their config/extension/session/credential paths with sizes, their extensions by name, their MCP servers with **key names only**, and what no rule accounts for | Read a session transcript, read a credential file, print any secret value |
Expand Down
Loading
Loading