Skip to content

Refactor to add skill/ directory with bootstrap scripts#3

Merged
juntao merged 4 commits intomainfrom
feat/skill-directory
Jan 31, 2026
Merged

Refactor to add skill/ directory with bootstrap scripts#3
juntao merged 4 commits intomainfrom
feat/skill-directory

Conversation

@juntao
Copy link
Member

@juntao juntao commented Jan 31, 2026

Summary

  • Move skill.md to skill/skill.md
  • Add bootstrap.sh for lazy-loading binaries from GitHub releases
  • Add wrapper scripts for each tool (create-wallet, get-address, pay, x402curl, x402-config)
  • Add skill/scripts/ directory for downloaded binaries (gitignored)
  • Update install.md with new installation instructions

How Bootstrap Works

  1. Wrapper scripts in skill/ detect the platform (linux/darwin/windows + x86_64/aarch64)
  2. On first run, the appropriate zip is downloaded from GitHub releases
  3. Binaries are extracted to skill/scripts/
  4. The real binary is executed with the original arguments

New Directory Structure

skill/
├── skill.md              # Skill definition for Claude
├── bootstrap.sh          # Shared bootstrap logic
├── create-wallet         # Wrapper script
├── get-address           # Wrapper script
├── pay                   # Wrapper script
├── x402curl              # Wrapper script
├── x402-config           # Wrapper script
└── scripts/              # Downloaded binaries (auto-populated)
    ├── .gitignore
    └── .gitkeep

Test plan

  • Wrapper scripts are executable
  • bootstrap.sh contains correct platform detection logic
  • skill/scripts/.gitignore excludes downloaded binaries
  • CI builds pass

Generated with Claude Code

- Move skill.md to skill/skill.md
- Add bootstrap.sh for lazy-loading binaries from GitHub releases
- Add wrapper scripts for each tool (create-wallet, get-address, pay, x402curl, x402-config)
- Add skill/scripts/ directory for downloaded binaries (gitignored)
- Update install.md with new installation instructions

The bootstrap approach:
1. Wrapper scripts detect platform (OS + arch)
2. On first run, download appropriate zip from GitHub releases
3. Extract binaries to skill/scripts/
4. Execute the real binary with original arguments

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the x402 skill to use a lazy-loading approach for binary distribution. Instead of requiring users to manually download platform-specific binaries, wrapper scripts now automatically download and cache them from GitHub releases on first use.

Changes:

  • Moved skill.md into a skill/ directory with bootstrap infrastructure
  • Added bootstrap.sh containing platform detection, download, and extraction logic
  • Created wrapper scripts for all five tools that source bootstrap.sh and delegate to actual binaries
  • Updated installation documentation to reflect the new bootstrap approach

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
skill/bootstrap.sh Core bootstrap logic for platform detection, downloading binaries from GitHub releases, and executing tools
skill/create-wallet Wrapper script that bootstraps and delegates to the create-wallet binary
skill/get-address Wrapper script that bootstraps and delegates to the get-address binary
skill/pay Wrapper script that bootstraps and delegates to the pay binary
skill/x402curl Wrapper script that bootstraps and delegates to the x402curl binary
skill/x402-config Wrapper script that bootstraps and delegates to the x402-config binary
skill/scripts/.gitignore Ensures downloaded binaries are not committed to the repository
skill/scripts/.gitkeep Placeholder to maintain the scripts directory in git
skill/skill.md Updated with installation instructions explaining the bootstrap process
install.md Completely rewritten with new installation instructions for the bootstrap approach
Comments suppressed due to low confidence (2)

skill/skill.md:14

  • Inconsistent path reference. The documentation states binaries are extracted to skill/scripts/, but based on the actual installation location (~/.claude/skills/x402/) and the bootstrap.sh script (which uses SCRIPTS_DIR="${SCRIPT_DIR}/scripts"), the correct path relative to the script location would be just scripts/, not skill/scripts/.

The absolute path shown at line 24 also incorrectly includes an extra skill/ subdirectory.
skill/skill.md:24

  • Inconsistent path reference. The correct path after installation should be ~/.claude/skills/x402/scripts/ (without the extra skill/ subdirectory). This is consistent with how the bootstrap.sh script resolves paths and the directory structure shown in install.md.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

fi

# Make binaries executable (not needed on Windows)
if [ "$(uname -s)" != "MINGW"* ] && [ "$(uname -s)" != "MSYS"* ] && [ "$(uname -s)" != "CYGWIN"* ]; then
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string comparison syntax is incorrect. The [ ] test command doesn't support glob pattern matching with !=. The current condition will always evaluate to false because it's doing a literal string comparison.

Use a case statement or [[ ]] instead. For example:

case "$(uname -s)" in
    MINGW*|MSYS*|CYGWIN*) ;;  # Skip chmod on Windows
    *) chmod +x "${SCRIPTS_DIR}"/* ;;
esac

Or use double brackets:

if [[ "$(uname -s)" != MINGW* ]] && [[ "$(uname -s)" != MSYS* ]] && [[ "$(uname -s)" != CYGWIN* ]]; then
Suggested change
if [ "$(uname -s)" != "MINGW"* ] && [ "$(uname -s)" != "MSYS"* ] && [ "$(uname -s)" != "CYGWIN"* ]; then
if [[ "$(uname -s)" != MINGW* ]] && [[ "$(uname -s)" != MSYS* ]] && [[ "$(uname -s)" != CYGWIN* ]]; then

Copilot uses AI. Check for mistakes.

# Make binaries executable (not needed on Windows)
if [ "$(uname -s)" != "MINGW"* ] && [ "$(uname -s)" != "MSYS"* ] && [ "$(uname -s)" != "CYGWIN"* ]; then
chmod +x "${SCRIPTS_DIR}"/*
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wildcard glob "${SCRIPTS_DIR}"/* will match all files including .gitignore and .gitkeep, making them executable unnecessarily. Consider being more selective by only making actual binary files executable, or excluding dotfiles from the chmod operation.

For example:

find "${SCRIPTS_DIR}" -maxdepth 1 -type f ! -name '.*' -exec chmod +x {} +

Or if the binaries have specific names:

for tool in create-wallet get-address pay x402curl x402-config; do
    [ -f "${SCRIPTS_DIR}/${tool}" ] && chmod +x "${SCRIPTS_DIR}/${tool}"
done
Suggested change
chmod +x "${SCRIPTS_DIR}"/*
find "${SCRIPTS_DIR}" -maxdepth 1 -type f ! -name '.*' -exec chmod +x {} +

Copilot uses AI. Check for mistakes.
echo "Installed tools:"
ls "${SCRIPTS_DIR}"
```
1. Wrapper scripts in `skill/` detect your platform
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent path reference. The documentation references skill/scripts/ subdirectory, but based on the installation command at line 15 (cp -r /tmp/x402-repo/skill "$SKILL_DIR"), the actual path after installation would be ~/.claude/skills/x402/scripts/ (without the skill/ subdirectory).

This inconsistency is also present at lines 31 and 74-75. The path should be just scripts/ to match the actual directory structure shown at lines 39-50.

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +75
3. Extract to `~/.claude/skills/x402/skill/scripts/`
4. Make executable: `chmod +x ~/.claude/skills/x402/skill/scripts/*`
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent path reference. Based on the installation command at line 15, the actual path after installation should be ~/.claude/skills/x402/scripts/ (without the skill/ subdirectory in between). This should be changed to match the correct directory structure.

Suggested change
3. Extract to `~/.claude/skills/x402/skill/scripts/`
4. Make executable: `chmod +x ~/.claude/skills/x402/skill/scripts/*`
3. Extract to `~/.claude/skills/x402/scripts/`
4. Make executable: `chmod +x ~/.claude/skills/x402/scripts/*`

Copilot uses AI. Check for mistakes.
```
1. Wrapper scripts in `skill/` detect your platform
2. On first run, binaries are downloaded from GitHub releases
3. Binaries are cached in `skill/scripts/`
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent path reference. Based on the installation command at line 15, the correct path should be just scripts/ (not skill/scripts/). After installation, the binaries are cached directly in ~/.claude/skills/x402/scripts/ as shown in the directory structure at lines 39-50.

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +144
# Download
if command -v curl &>/dev/null; then
curl -sL -o "$zip_file" "$url"
else
wget -q -O "$zip_file" "$url"
fi

# Extract
if command -v unzip &>/dev/null; then
unzip -q -o "$zip_file" -d "${SCRIPTS_DIR}"
else
echo "Error: unzip not found. Please install unzip." >&2
rm -rf "$temp_dir"
exit 1
fi

# Make binaries executable (not needed on Windows)
if [ "$(uname -s)" != "MINGW"* ] && [ "$(uname -s)" != "MSYS"* ] && [ "$(uname -s)" != "CYGWIN"* ]; then
chmod +x "${SCRIPTS_DIR}"/*
fi

# Cleanup
rm -rf "$temp_dir"

echo "x402 tools installed successfully." >&2
}

# Main bootstrap function
# Usage: bootstrap_and_run <tool_name> [args...]
bootstrap_and_run() {
local tool_name="$1"
shift

local platform
local binary_path
local binary_name

platform=$(detect_platform)

# Determine binary name (add .exe for Windows)
if [[ "$platform" == windows-* ]]; then
binary_name="${tool_name}.exe"
else
binary_name="${tool_name}"
fi

binary_path="${SCRIPTS_DIR}/${binary_name}"

# Check if binary exists, download if not
if [ ! -x "$binary_path" ]; then
local download_url
download_url=$(get_download_url "$platform")
download_binaries "$platform" "$download_url"
fi

# Verify binary exists after download
if [ ! -x "$binary_path" ]; then
echo "Error: Binary ${binary_name} not found after download." >&2
exit 1
fi

# Execute the tool
exec "$binary_path" "$@"
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bootstrap logic downloads and executes prebuilt binaries from https://github.com/second-state/x402-agent-tools using the mutable releases/latest endpoint without any integrity verification or version pinning, which creates a supply-chain risk of arbitrary code execution if that repository, its releases, or the network path are compromised. An attacker who can tamper with the GitHub release artifacts (or the latest tag) could cause clients to transparently fetch and run a malicious binary whenever a wrapper script is executed. To mitigate this, pin downloads to immutable identifiers (e.g., specific release/version or checksummed artifacts) and verify the binary’s integrity (checksum/signature) before unzipping and exec-ing it.

Copilot uses AI. Check for mistakes.
- Remove individual wrapper scripts (create-wallet, get-address, pay, x402curl, x402-config)
- Keep only bootstrap.sh for downloading binaries
- Update skill.md with instructions for agent to:
  1. Check if binaries exist in skill/scripts/
  2. Run bootstrap.sh if not installed
  3. Run tools directly from skill/scripts/
- Update all command examples with full paths

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 12 comments.

Comments suppressed due to low confidence (1)

skill/skill.md:27

  • The PR description states "Add wrapper scripts for each tool (create-wallet, get-address, pay, x402curl, x402-config)" and describes that "Wrapper scripts in skill/ detect the platform" and "On first run, the appropriate zip is downloaded from GitHub releases". However, the actual implementation differs significantly:
  1. No wrapper scripts are included in this PR
  2. The documentation instructs users to manually run bootstrap.sh first, then call binaries directly from scripts/ directory
  3. There's no automatic lazy-loading on first tool invocation

This represents a discrepancy between the described approach (lazy-loading via wrapper scripts) and the implemented approach (manual bootstrap, then direct binary execution). Either the wrapper scripts need to be added, or the PR description needs to be updated to accurately describe the manual bootstrap approach.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +19 to +20
chmod +x "${SKILL_DIR}"/*.sh "${SKILL_DIR}/create-wallet" "${SKILL_DIR}/get-address" \
"${SKILL_DIR}/pay" "${SKILL_DIR}/x402curl" "${SKILL_DIR}/x402-config"
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The installation instructions reference wrapper scripts (create-wallet, get-address, pay, x402curl, x402-config) that should exist in the skill/ directory according to the PR description, but these files are not included in this pull request. The chmod command will fail because these wrapper scripts don't exist.

This appears to be a critical omission - either the wrapper scripts need to be added to this PR, or the installation instructions and documentation need to be updated to reflect a different approach (e.g., using bootstrap.sh directly or calling binaries from scripts/ directory).

Copilot uses AI. Check for mistakes.
Comment on lines +42 to 50
├── create-wallet # Wrapper script
├── get-address # Wrapper script
├── pay # Wrapper script
├── x402curl # Wrapper script
├── x402-config # Wrapper script
└── scripts/ # Downloaded binaries (auto-populated)
├── .gitignore
└── .gitkeep
```
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The directory structure diagram shows wrapper scripts (create-wallet, get-address, pay, x402curl, x402-config) that are not included in this pull request. These files are referenced throughout the documentation but don't exist in the actual changes. This creates a discrepancy between documentation and implementation.

Copilot uses AI. Check for mistakes.
Comment on lines 39 to 50
~/.claude/skills/x402/
├── skill.md # Skill definition for Claude
└── scripts/
├── create-wallet # Create new Ethereum wallet
├── get-address # Get wallet public address
├── pay # Transfer tokens
├── x402curl # HTTP client with 402 handling
└── x402-config # Configuration management
├── bootstrap.sh # Shared bootstrap logic
├── create-wallet # Wrapper script
├── get-address # Wrapper script
├── pay # Wrapper script
├── x402curl # Wrapper script
├── x402-config # Wrapper script
└── scripts/ # Downloaded binaries (auto-populated)
├── .gitignore
└── .gitkeep
```
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The directory structure diagram is misleading because it shows ~/.claude/skills/x402/ as the root directory containing skill.md, bootstrap.sh, etc. However, based on the installation command at line 15 (cp -r /tmp/x402-repo/skill "$SKILL_DIR"), the actual structure would be:

~/.claude/skills/x402/skill/
├── skill.md              # Skill definition for Claude
├── bootstrap.sh          # Shared bootstrap logic
├── create-wallet         # Wrapper script
├── get-address           # Wrapper script
├── pay                   # Wrapper script
├── x402curl              # Wrapper script
├── x402-config           # Wrapper script
└── scripts/              # Downloaded binaries (auto-populated)
    ├── .gitignore
    └── .gitkeep

Note the additional /skill/ subdirectory level that's missing from the current diagram. This is consistent with how skill.md references all paths (e.g., ~/.claude/skills/x402/skill/scripts/create-wallet).

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +20
cp -r /tmp/x402-repo/skill "$SKILL_DIR"
rm -rf /tmp/x402-repo

### Step 6: Verify Installation

```bash
# List installed tools
ls -la "${SCRIPTS_DIR}"

# Verify executables work
"${SCRIPTS_DIR}/get-address" --help
# Make scripts executable
chmod +x "${SKILL_DIR}"/*.sh "${SKILL_DIR}/create-wallet" "${SKILL_DIR}/get-address" \
"${SKILL_DIR}/pay" "${SKILL_DIR}/x402curl" "${SKILL_DIR}/x402-config"
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a path inconsistency in the installation instructions. Line 15 copies the entire skill directory to SKILL_DIR with cp -r /tmp/x402-repo/skill "$SKILL_DIR", which creates the structure ~/.claude/skills/x402/skill/.... However, line 19 references wrapper scripts as if they're directly in SKILL_DIR: "${SKILL_DIR}/create-wallet" which would be ~/.claude/skills/x402/create-wallet.

For consistency, line 19 should either:

  1. Reference "${SKILL_DIR}/skill/create-wallet" (to match the nested structure from line 15), or
  2. Line 15 should copy the contents with cp -r /tmp/x402-repo/skill/* "$SKILL_DIR" to flatten the structure

This affects lines 19-20, 88, and 105-107 which all use the wrong path pattern.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +51
download_url=$(curl -sL "$api_url" | grep -o "https://github.com/${REPO}/releases/download/[^\"]*${artifact_name}" | head -1)
elif command -v wget &>/dev/null; then
download_url=$(wget -qO- "$api_url" | grep -o "https://github.com/${REPO}/releases/download/[^\"]*${artifact_name}" | head -1)
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The grep pattern used to extract the download URL from the GitHub API response may not be reliable across different platforms or if the API response format changes. The pattern grep -o "https://github.com/${REPO}/releases/download/[^\"]*${artifact_name}" relies on specific JSON formatting and assumes no quotes within the URL path.

Consider using a more robust JSON parser like jq if available, with a fallback to the current grep approach. For example:

if command -v jq &>/dev/null; then
    download_url=$(curl -sL "$api_url" | jq -r ".assets[] | select(.name == \"${artifact_name}\") | .browser_download_url")
else
    download_url=$(curl -sL "$api_url" | grep -o "https://github.com/${REPO}/releases/download/[^\"]*${artifact_name}" | head -1)
fi

This would make the script more robust, though the current approach may work adequately for the expected use case.

Suggested change
download_url=$(curl -sL "$api_url" | grep -o "https://github.com/${REPO}/releases/download/[^\"]*${artifact_name}" | head -1)
elif command -v wget &>/dev/null; then
download_url=$(wget -qO- "$api_url" | grep -o "https://github.com/${REPO}/releases/download/[^\"]*${artifact_name}" | head -1)
if command -v jq &>/dev/null; then
download_url=$(curl -sL "$api_url" | jq -r ".assets[] | select(.name == \"${artifact_name}\") | .browser_download_url")
else
download_url=$(curl -sL "$api_url" | grep -o "https://github.com/${REPO}/releases/download/[^\"]*${artifact_name}" | head -1)
fi
elif command -v wget &>/dev/null; then
if command -v jq &>/dev/null; then
download_url=$(wget -qO- "$api_url" | jq -r ".assets[] | select(.name == \"${artifact_name}\") | .browser_download_url")
else
download_url=$(wget -qO- "$api_url" | grep -o "https://github.com/${REPO}/releases/download/[^\"]*${artifact_name}" | head -1)
fi

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +89
curl -sL -o "$zip_file" "$url"
else
wget -q -O "$zip_file" "$url"
fi

Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The download commands (curl and wget) don't check for HTTP errors or download failures. If the download fails (e.g., 404, network error, server error), the script will still attempt to unzip an incomplete or non-existent file, leading to confusing error messages.

Consider adding error checking after the download:

if command -v curl &>/dev/null; then
    if ! curl -sL -o "$zip_file" "$url"; then
        echo "Error: Failed to download from ${url}" >&2
        rm -rf "$temp_dir"
        exit 1
    fi
else
    if ! wget -q -O "$zip_file" "$url"; then
        echo "Error: Failed to download from ${url}" >&2
        rm -rf "$temp_dir"
        exit 1
    fi
fi

# Verify the file was downloaded and is not empty
if [ ! -s "$zip_file" ]; then
    echo "Error: Downloaded file is empty or missing" >&2
    rm -rf "$temp_dir"
    exit 1
fi
Suggested change
curl -sL -o "$zip_file" "$url"
else
wget -q -O "$zip_file" "$url"
fi
if ! curl -sL -o "$zip_file" "$url"; then
echo "Error: Failed to download from ${url}" >&2
rm -rf "$temp_dir"
exit 1
fi
else
if ! wget -q -O "$zip_file" "$url"; then
echo "Error: Failed to download from ${url}" >&2
rm -rf "$temp_dir"
exit 1
fi
fi
# Verify the file was downloaded and is not empty
if [ ! -s "$zip_file" ]; then
echo "Error: Downloaded file is empty or missing" >&2
rm -rf "$temp_dir"
exit 1
fi

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +93
# Download
echo "Fetching from: ${url}" >&2
if command -v curl &>/dev/null; then
curl -sL -o "$zip_file" "$url"
else
wget -q -O "$zip_file" "$url"
fi

# Extract
echo "Extracting binaries..." >&2
if command -v unzip &>/dev/null; then
unzip -q -o "$zip_file" -d "${SCRIPTS_DIR}"
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The download_binaries function downloads a zip from the third-party repo second-state/x402-agent-tools and immediately extracts it into SCRIPTS_DIR using a floating releases/latest URL, with no checksum or signature verification. If that GitHub repository or its release artifacts are compromised, this script will transparently install and execute attacker-controlled binaries on the host with the user’s privileges. To reduce supply-chain risk, pin downloads to an immutable version (tag/commit or asset checksum) and verify integrity before extraction and execution.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +16
git clone --depth 1 https://github.com/second-state/x402-agent-tools.git /tmp/x402-repo
cp -r /tmp/x402-repo/skill "$SKILL_DIR"
rm -rf /tmp/x402-repo
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Quick Install instructions clone https://github.com/second-state/x402-agent-tools.git with --depth 1 and copy the skill/ directory directly into the user’s skills path, without pinning to a specific tag/commit or verifying the contents. Because this pulls code from an unpinned third-party repository, a compromise or malicious change to that repo could cause new installs to run attacker-controlled scripts with the user’s privileges. To harden the supply chain, pin the clone to an immutable revision and/or validate expected checksums or signatures before using the downloaded files.

Copilot uses AI. Check for mistakes.
juntao and others added 2 commits January 31, 2026 12:11
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@juntao juntao merged commit 9dc1b18 into main Jan 31, 2026
11 checks passed
@juntao juntao deleted the feat/skill-directory branch January 31, 2026 04:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants