feat(templates): add import/export system for agent systems
This commit is contained in:
parent
efbdbd82ed
commit
ea3ff53d2c
4 changed files with 372 additions and 0 deletions
29
scripts/templates/transfer/MANIFEST.md
Normal file
29
scripts/templates/transfer/MANIFEST.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Agent System Manifest
|
||||||
|
Export name: {{EXPORT_NAME}}
|
||||||
|
Export date: {{EXPORT_DATE}}
|
||||||
|
Generated by: Agent Factory export-system.sh
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
| Type | File | Checksum (SHA256, first 16) |
|
||||||
|
|------|------|---------------------------|
|
||||||
|
| agent | .claude/agents/pipeline-runner.md | a1b2c3d4e5f6a7b8 |
|
||||||
|
| skill | .claude/skills/my-pipeline/SKILL.md | b2c3d4e5f6a7b8c9 |
|
||||||
|
| hook | .claude/hooks/pre-tool-use.sh | c3d4e5f6a7b8c9d0 |
|
||||||
|
| settings | .claude/settings.json | d4e5f6a7b8c9d0e1 |
|
||||||
|
| automation | automation/run-pipeline.sh | e5f6a7b8c9d0e1f2 |
|
||||||
|
| context | CLAUDE.md | f6a7b8c9d0e1f2a3 |
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
| Requirement | Value |
|
||||||
|
|-------------|-------|
|
||||||
|
| Claude Code version | 1.x or later |
|
||||||
|
| MCP servers | (list any required MCP servers, e.g. GitHub, Slack) |
|
||||||
|
| Tools | Bash, Read, Write, Glob, Grep |
|
||||||
|
| Environment | ANTHROPIC_API_KEY |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Replace all `{{PLACEHOLDER}}` variables after import.
|
||||||
|
See transfer/README.md for import instructions.
|
||||||
64
scripts/templates/transfer/README.md
Normal file
64
scripts/templates/transfer/README.md
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Import / Export System
|
||||||
|
|
||||||
|
Portable packaging for agent systems. Export a working agent system from one
|
||||||
|
project and import it into another, with automatic placeholder substitution.
|
||||||
|
|
||||||
|
## Export
|
||||||
|
|
||||||
|
Pack your current agent system into a tarball:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/templates/transfer/export-system.sh my-project-name
|
||||||
|
# Creates: agent-system-my-project-name-2026-04-11.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
### What is included
|
||||||
|
|
||||||
|
| Included | Excluded |
|
||||||
|
|---------|---------|
|
||||||
|
| `.claude/agents/` | `.env` (secrets) |
|
||||||
|
| `.claude/skills/` | `*.local.*` files |
|
||||||
|
| `.claude/hooks/` | `audit.log` |
|
||||||
|
| `.claude/settings.json` | `cost-events.jsonl` |
|
||||||
|
| `hooks/` | `memory/` (machine-specific state) |
|
||||||
|
| `automation/` | `.git/` |
|
||||||
|
| `scripts/` | |
|
||||||
|
| `CLAUDE.md` | |
|
||||||
|
|
||||||
|
## Import
|
||||||
|
|
||||||
|
Extract a tarball into a new project directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Dry run first: inspect without extracting
|
||||||
|
tar -tzf agent-system-my-project-name-2026-04-11.tar.gz
|
||||||
|
|
||||||
|
# Import (will stop if files conflict)
|
||||||
|
bash scripts/templates/transfer/import-system.sh agent-system-my-project-name-2026-04-11.tar.gz
|
||||||
|
|
||||||
|
# Import and overwrite existing files
|
||||||
|
bash scripts/templates/transfer/import-system.sh agent-system-my-project-name-2026-04-11.tar.gz --force
|
||||||
|
```
|
||||||
|
|
||||||
|
The import script will:
|
||||||
|
1. Read `MANIFEST.md` and verify the archive
|
||||||
|
2. Check for conflicts with existing files (stops unless `--force`)
|
||||||
|
3. Extract all files
|
||||||
|
4. Replace `{{PROJECT_DIR}}` and `{{PROJECT_NAME}}` placeholders
|
||||||
|
5. Make `.sh` files executable
|
||||||
|
6. Validate agent frontmatter and hook syntax
|
||||||
|
|
||||||
|
## Customization after import
|
||||||
|
|
||||||
|
1. **Update `CLAUDE.md`** — replace project-specific context, goals, and constraints
|
||||||
|
2. **Add secrets** — create `.env` with `ANTHROPIC_API_KEY=sk-ant-...`
|
||||||
|
3. **Review hooks** — check that paths in hook scripts match your environment
|
||||||
|
4. **Run `/agent-factory:status`** — verify all components load correctly
|
||||||
|
5. **Run `/agent-factory:evaluate`** — check capability coverage
|
||||||
|
|
||||||
|
## Manifest
|
||||||
|
|
||||||
|
Each export includes `MANIFEST.md` listing every file with a SHA256 checksum
|
||||||
|
(first 16 hex characters). Use this to verify file integrity after transfer.
|
||||||
|
|
||||||
|
Template manifest: `scripts/templates/transfer/MANIFEST.md`
|
||||||
132
scripts/templates/transfer/export-system.sh
Normal file
132
scripts/templates/transfer/export-system.sh
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Agent Factory — export-system.sh
|
||||||
|
# Packages an agent system into a portable tarball with checksums.
|
||||||
|
# Usage: bash export-system.sh <export-name>
|
||||||
|
# Bash 3.2 compatible.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
EXPORT_NAME="${1:-agent-system}"
|
||||||
|
DATE=$(python3 -c "import datetime; print(datetime.date.today().strftime('%Y-%m-%d'))")
|
||||||
|
OUTPUT_FILE="agent-system-${EXPORT_NAME}-${DATE}.tar.gz"
|
||||||
|
STAGING_DIR="/tmp/agent-export-$$"
|
||||||
|
MANIFEST_FILE="${STAGING_DIR}/MANIFEST.md"
|
||||||
|
|
||||||
|
# Determine project root (directory containing .claude/)
|
||||||
|
PROJECT_ROOT="$(pwd)"
|
||||||
|
if [ ! -d "${PROJECT_ROOT}/.claude" ]; then
|
||||||
|
echo "ERROR: No .claude/ directory found in $(pwd)" >&2
|
||||||
|
echo "Run this script from your project root." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[export] Starting export: ${EXPORT_NAME}"
|
||||||
|
echo "[export] Date: ${DATE}"
|
||||||
|
echo "[export] Output: ${OUTPUT_FILE}"
|
||||||
|
|
||||||
|
mkdir -p "${STAGING_DIR}"
|
||||||
|
|
||||||
|
# Collect files — explicit inclusions only
|
||||||
|
collect_files() {
|
||||||
|
local dir="$1"
|
||||||
|
local dest="$2"
|
||||||
|
if [ -d "${PROJECT_ROOT}/${dir}" ]; then
|
||||||
|
mkdir -p "${STAGING_DIR}/${dest}"
|
||||||
|
cp -r "${PROJECT_ROOT}/${dir}/." "${STAGING_DIR}/${dest}/" 2>/dev/null || true
|
||||||
|
echo "[export] Collected: ${dir}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_files ".claude/agents" ".claude/agents"
|
||||||
|
collect_files ".claude/skills" ".claude/skills"
|
||||||
|
collect_files ".claude/hooks" ".claude/hooks"
|
||||||
|
collect_files "hooks" "hooks"
|
||||||
|
collect_files "automation" "automation"
|
||||||
|
collect_files "scripts" "scripts"
|
||||||
|
|
||||||
|
# Copy settings.json if it exists
|
||||||
|
if [ -f "${PROJECT_ROOT}/.claude/settings.json" ]; then
|
||||||
|
mkdir -p "${STAGING_DIR}/.claude"
|
||||||
|
cp "${PROJECT_ROOT}/.claude/settings.json" "${STAGING_DIR}/.claude/settings.json"
|
||||||
|
echo "[export] Collected: .claude/settings.json"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy CLAUDE.md if it exists
|
||||||
|
if [ -f "${PROJECT_ROOT}/CLAUDE.md" ]; then
|
||||||
|
cp "${PROJECT_ROOT}/CLAUDE.md" "${STAGING_DIR}/CLAUDE.md"
|
||||||
|
echo "[export] Collected: CLAUDE.md"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove excluded files from staging
|
||||||
|
echo "[export] Removing excluded files"
|
||||||
|
rm -f "${STAGING_DIR}/.env" 2>/dev/null || true
|
||||||
|
find "${STAGING_DIR}" -name "*.local.*" -delete 2>/dev/null || true
|
||||||
|
find "${STAGING_DIR}" -name "audit.log" -delete 2>/dev/null || true
|
||||||
|
find "${STAGING_DIR}" -name "cost-events.jsonl" -delete 2>/dev/null || true
|
||||||
|
find "${STAGING_DIR}" -name ".git" -prune -o -print | grep "\.git$" | xargs rm -rf 2>/dev/null || true
|
||||||
|
rm -rf "${STAGING_DIR}/memory" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Generate MANIFEST.md with checksums
|
||||||
|
echo "[export] Generating manifest"
|
||||||
|
|
||||||
|
COMPONENT_ROWS=""
|
||||||
|
find "${STAGING_DIR}" -type f | sort | while read -r fpath; do
|
||||||
|
rel="${fpath#${STAGING_DIR}/}"
|
||||||
|
checksum=$(python3 -c "import hashlib; print(hashlib.sha256(open('${fpath}','rb').read()).hexdigest()[:16])")
|
||||||
|
filetype="other"
|
||||||
|
case "$rel" in
|
||||||
|
.claude/agents/*) filetype="agent" ;;
|
||||||
|
.claude/skills/*) filetype="skill" ;;
|
||||||
|
.claude/hooks/*|hooks/*) filetype="hook" ;;
|
||||||
|
.claude/settings.json) filetype="settings" ;;
|
||||||
|
automation/*) filetype="automation" ;;
|
||||||
|
CLAUDE.md) filetype="context" ;;
|
||||||
|
esac
|
||||||
|
echo "| ${filetype} | ${rel} | ${checksum} |" >> "${MANIFEST_FILE}.rows"
|
||||||
|
done
|
||||||
|
|
||||||
|
cat > "${MANIFEST_FILE}" << MANIFEST
|
||||||
|
# Agent System Manifest
|
||||||
|
Export name: ${EXPORT_NAME}
|
||||||
|
Export date: ${DATE}
|
||||||
|
Generated by: Agent Factory export-system.sh
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
| Type | File | Checksum (SHA256, first 16) |
|
||||||
|
|------|------|---------------------------|
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
if [ -f "${MANIFEST_FILE}.rows" ]; then
|
||||||
|
cat "${MANIFEST_FILE}.rows" >> "${MANIFEST_FILE}"
|
||||||
|
rm "${MANIFEST_FILE}.rows"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat >> "${MANIFEST_FILE}" << MANIFEST
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
| Requirement | Value |
|
||||||
|
|-------------|-------|
|
||||||
|
| Claude Code version | 1.x or later |
|
||||||
|
| MCP servers | List any required MCP servers here |
|
||||||
|
| Tools | List any required tools (Bash, WebSearch, etc.) |
|
||||||
|
| Environment | List required env vars (ANTHROPIC_API_KEY, etc.) |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Add any project-specific setup notes here.
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
echo "[export] Manifest written"
|
||||||
|
|
||||||
|
# Create tarball
|
||||||
|
cd "${STAGING_DIR}"
|
||||||
|
tar -czf "${PROJECT_ROOT}/${OUTPUT_FILE}" .
|
||||||
|
cd "${PROJECT_ROOT}"
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -rf "${STAGING_DIR}"
|
||||||
|
|
||||||
|
echo "[export] Done: ${OUTPUT_FILE}"
|
||||||
|
echo "[export] Size: $(du -sh "${OUTPUT_FILE}" | cut -f1)"
|
||||||
147
scripts/templates/transfer/import-system.sh
Normal file
147
scripts/templates/transfer/import-system.sh
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Agent Factory — import-system.sh
|
||||||
|
# Imports an exported agent system tarball into the current project.
|
||||||
|
# Usage: bash import-system.sh <tarball> [--force]
|
||||||
|
# Bash 3.2 compatible.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
TARBALL="$1"
|
||||||
|
FORCE="${2:-}"
|
||||||
|
STAGING_DIR="/tmp/agent-import-$$"
|
||||||
|
|
||||||
|
if [ -z "$TARBALL" ]; then
|
||||||
|
echo "Usage: bash import-system.sh <agent-system-*.tar.gz> [--force]" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$TARBALL" ]; then
|
||||||
|
echo "ERROR: File not found: $TARBALL" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[import] Importing: $TARBALL"
|
||||||
|
|
||||||
|
mkdir -p "$STAGING_DIR"
|
||||||
|
tar -xzf "$TARBALL" -C "$STAGING_DIR"
|
||||||
|
|
||||||
|
# Read MANIFEST.md
|
||||||
|
MANIFEST="${STAGING_DIR}/MANIFEST.md"
|
||||||
|
if [ ! -f "$MANIFEST" ]; then
|
||||||
|
echo "ERROR: No MANIFEST.md found in tarball — may not be a valid Agent Factory export" >&2
|
||||||
|
rm -rf "$STAGING_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[import] Manifest found:"
|
||||||
|
head -5 "$MANIFEST"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for conflicts (existing files in destination)
|
||||||
|
CONFLICTS=""
|
||||||
|
find "$STAGING_DIR" -type f | sort | while read -r fpath; do
|
||||||
|
rel="${fpath#${STAGING_DIR}/}"
|
||||||
|
dest="${PWD}/${rel}"
|
||||||
|
if [ -f "$dest" ] && [ "$FORCE" != "--force" ]; then
|
||||||
|
echo "[import] CONFLICT: $rel already exists"
|
||||||
|
CONFLICTS="yes"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$CONFLICTS" ] && [ "$FORCE" != "--force" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Conflicts detected. Re-run with --force to overwrite, or remove conflicting files first." >&2
|
||||||
|
rm -rf "$STAGING_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract files to project
|
||||||
|
echo "[import] Extracting files"
|
||||||
|
find "$STAGING_DIR" -type f | sort | while read -r fpath; do
|
||||||
|
rel="${fpath#${STAGING_DIR}/}"
|
||||||
|
dest="${PWD}/${rel}"
|
||||||
|
destdir="$(dirname "$dest")"
|
||||||
|
mkdir -p "$destdir"
|
||||||
|
cp "$fpath" "$dest"
|
||||||
|
echo "[import] Wrote: $rel"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Replace {{PLACEHOLDER}} variables
|
||||||
|
echo "[import] Replacing placeholders"
|
||||||
|
PROJECT_DIR="$(pwd)"
|
||||||
|
PROJECT_NAME="$(basename "$PROJECT_DIR")"
|
||||||
|
|
||||||
|
find "$PROJECT_DIR/.claude" "$PROJECT_DIR/automation" "$PROJECT_DIR/hooks" \
|
||||||
|
"$PROJECT_DIR/CLAUDE.md" -type f 2>/dev/null | while read -r fpath; do
|
||||||
|
case "$fpath" in
|
||||||
|
*.sh|*.md|*.json|*.yml|*.yaml)
|
||||||
|
python3 - "$fpath" "$PROJECT_DIR" "$PROJECT_NAME" << 'PYEOF'
|
||||||
|
import sys, re
|
||||||
|
path, project_dir, project_name = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||||
|
try:
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
content = content.replace('{{PROJECT_DIR}}', project_dir)
|
||||||
|
content = content.replace('{{PROJECT_NAME}}', project_name)
|
||||||
|
with open(path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(content)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
PYEOF
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Make .sh files executable
|
||||||
|
echo "[import] Setting executable permissions"
|
||||||
|
find "$PROJECT_DIR/.claude/hooks" "$PROJECT_DIR/hooks" "$PROJECT_DIR/automation" \
|
||||||
|
-name "*.sh" -type f 2>/dev/null | while read -r fpath; do
|
||||||
|
chmod +x "$fpath"
|
||||||
|
echo "[import] chmod +x: $fpath"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Validate frontmatter in agent files
|
||||||
|
echo "[import] Validating agent frontmatter"
|
||||||
|
INVALID_AGENTS=""
|
||||||
|
if [ -d "$PROJECT_DIR/.claude/agents" ]; then
|
||||||
|
for agent in "$PROJECT_DIR/.claude/agents"/*.md; do
|
||||||
|
[ -f "$agent" ] || continue
|
||||||
|
python3 - "$agent" << 'PYEOF'
|
||||||
|
import sys, re
|
||||||
|
path = sys.argv[1]
|
||||||
|
try:
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
if not content.startswith('---'):
|
||||||
|
print(f"WARN: {path} missing YAML frontmatter")
|
||||||
|
sys.exit(0)
|
||||||
|
parts = content.split('---', 2)
|
||||||
|
if len(parts) < 3:
|
||||||
|
print(f"WARN: {path} malformed frontmatter")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"WARN: {path} could not be validated: {e}")
|
||||||
|
PYEOF
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate bash syntax on hook scripts
|
||||||
|
echo "[import] Validating hook syntax"
|
||||||
|
if [ -d "$PROJECT_DIR/.claude/hooks" ]; then
|
||||||
|
for script in "$PROJECT_DIR/.claude/hooks"/*.sh; do
|
||||||
|
[ -f "$script" ] || continue
|
||||||
|
if bash -n "$script" 2>/dev/null; then
|
||||||
|
echo "[import] OK: $script"
|
||||||
|
else
|
||||||
|
echo "[import] WARN: $script failed bash -n check"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$STAGING_DIR"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[import] Import complete."
|
||||||
|
echo "[import] Next steps:"
|
||||||
|
echo " 1. Review CLAUDE.md and update project-specific sections"
|
||||||
|
echo " 2. Add your ANTHROPIC_API_KEY to .env"
|
||||||
|
echo " 3. Run /agent-factory:status to verify the system"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue