Files
claude-archeflow-plugin/lib/archeflow-init.sh

565 lines
18 KiB
Bash
Executable File

#!/usr/bin/env bash
# archeflow-init.sh — Initialize an ArcheFlow project from a template bundle, clone from
# another project, save the current setup as a template, or list available templates.
#
# Usage:
# archeflow-init.sh <bundle-name> [--set key=value ...] Init from named bundle
# archeflow-init.sh --from <project-path> Clone from another project
# archeflow-init.sh --list List available templates
# archeflow-init.sh --save <name> Save current setup as template
# archeflow-init.sh --share <name> <path> Export template to directory
#
# Examples:
# ./lib/archeflow-init.sh writing-short-story
# ./lib/archeflow-init.sh writing-short-story --set target_words=8000
# ./lib/archeflow-init.sh --from ../book.giesing-gschichten
# ./lib/archeflow-init.sh --save my-story-setup
# ./lib/archeflow-init.sh --list
set -euo pipefail
GLOBAL_TEMPLATES="${HOME}/.archeflow/templates"
LOCAL_TEMPLATES=".archeflow/templates"
# --- Helpers ----------------------------------------------------------------
die() { echo "ERROR: $*" >&2; exit 1; }
warn() { echo "WARNING: $*" >&2; }
info() { echo " $*"; }
# Parse YAML value (simple single-level extraction — no nested support).
# Falls back to grep+sed when yq is unavailable.
yaml_value() {
local file="$1" key="$2"
if command -v yq &>/dev/null; then
yq -r ".$key // empty" "$file" 2>/dev/null
else
grep -E "^${key}:" "$file" 2>/dev/null | sed 's/^[^:]*:[[:space:]]*//' | sed 's/^["'"'"']\(.*\)["'"'"']$/\1/'
fi
}
# Parse YAML list (simple — one item per "- " line under key).
yaml_list() {
local file="$1" key="$2"
if command -v yq &>/dev/null; then
yq -r ".$key[]? // empty" "$file" 2>/dev/null
else
sed -n "/^${key}:/,/^[^ -]/{ /^ *- /{ s/^ *- *//; s/^[\"']\(.*\)[\"']$/\1/; p; } }" "$file" 2>/dev/null
fi
}
# Check if a directory has files matching a glob (safe for empty results).
has_files() {
local dir="$1" pattern="${2:-*}"
# shellcheck disable=SC2086
compgen -G "${dir}/${pattern}" &>/dev/null
}
# Confirm overwrite if target exists and has files.
confirm_overwrite() {
local dir="$1" desc="$2"
if [[ -d "$dir" ]] && has_files "$dir"; then
warn "$desc already has files in $dir"
if [[ -t 0 ]]; then
read -r -p " Overwrite? [y/N] " answer
[[ "$answer" =~ ^[Yy]$ ]] || die "Aborted — will not overwrite existing files."
else
die "Non-interactive mode — will not overwrite existing files in $dir. Remove them first."
fi
fi
}
# --- Commands ---------------------------------------------------------------
cmd_list() {
echo "ArcheFlow Templates"
echo "===================="
echo ""
# Bundles
local found_bundle=false
echo "Bundles:"
for base in "$LOCAL_TEMPLATES" "$GLOBAL_TEMPLATES"; do
local scope
[[ "$base" == "$LOCAL_TEMPLATES" ]] && scope="local" || scope="global"
if [[ -d "$base/bundles" ]]; then
for manifest in "$base"/bundles/*/manifest.yaml; do
[[ -f "$manifest" ]] || continue
found_bundle=true
local bname bdir desc
bdir="$(dirname "$manifest")"
bname="$(basename "$bdir")"
desc="$(yaml_value "$manifest" "description")"
printf " %-25s %-45s [%s]\n" "$bname" "${desc:-(no description)}" "$scope"
done
fi
done
$found_bundle || echo " (none)"
echo ""
# Individual templates
echo "Individual Templates:"
for category in workflows teams archetypes domains; do
local found=false
local label
label="$(echo "$category" | sed 's/^./\U&/')" # Capitalize
echo " ${label}:"
for base in "$LOCAL_TEMPLATES" "$GLOBAL_TEMPLATES"; do
local scope
[[ "$base" == "$LOCAL_TEMPLATES" ]] && scope="local" || scope="global"
if [[ -d "$base/$category" ]]; then
for f in "$base/$category"/*; do
[[ -f "$f" ]] || continue
found=true
printf " %-35s [%s]\n" "$(basename "$f")" "$scope"
done
fi
done
$found || echo " (none)"
done
}
cmd_init_bundle() {
local bundle_name="$1"
shift
local -A overrides=()
# Parse --set key=value arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--set)
shift
[[ $# -gt 0 ]] || die "--set requires a key=value argument"
local k="${1%%=*}" v="${1#*=}"
overrides["$k"]="$v"
shift
;;
*)
die "Unknown argument: $1"
;;
esac
done
# Find the bundle
local bundle_dir=""
for base in "$LOCAL_TEMPLATES" "$GLOBAL_TEMPLATES"; do
if [[ -f "$base/bundles/${bundle_name}/manifest.yaml" ]]; then
bundle_dir="$base/bundles/${bundle_name}"
break
fi
done
[[ -n "$bundle_dir" ]] || die "Bundle not found: $bundle_name. Run '$0 --list' to see available templates."
local manifest="$bundle_dir/manifest.yaml"
echo "Initializing from bundle: $bundle_name"
echo " Source: $bundle_dir"
echo ""
# Check requires
local req
while IFS= read -r req; do
[[ -z "$req" ]] && continue
if [[ ! -e "$req" ]]; then
die "Required file not found: $req. This bundle requires it in the project root."
fi
info "Requirement satisfied: $req"
done < <(yaml_list "$manifest" "requires")
# Create target directories
mkdir -p .archeflow/teams .archeflow/workflows .archeflow/archetypes .archeflow/domains
# Copy team
local team_file
team_file="$(yaml_value "$manifest" "includes.team" 2>/dev/null || true)"
# Fallback for flat YAML parsing
if [[ -z "$team_file" ]] && command -v yq &>/dev/null; then
team_file="$(yq -r '.includes.team // empty' "$manifest" 2>/dev/null)"
fi
if [[ -n "$team_file" && -f "$bundle_dir/$team_file" ]]; then
confirm_overwrite ".archeflow/teams" "Teams directory"
cp "$bundle_dir/$team_file" ".archeflow/teams/$team_file"
info "Team: $team_file -> .archeflow/teams/"
elif [[ -n "$team_file" ]]; then
# team_file might just be the name, check without path
if [[ -f "$bundle_dir/team.yaml" ]]; then
confirm_overwrite ".archeflow/teams" "Teams directory"
cp "$bundle_dir/team.yaml" ".archeflow/teams/$team_file"
info "Team: $team_file -> .archeflow/teams/"
else
warn "Team file not found in bundle: $team_file"
fi
fi
# Copy workflow
local wf_file
wf_file="$(yaml_value "$manifest" "includes.workflow" 2>/dev/null || true)"
if [[ -z "$wf_file" ]] && command -v yq &>/dev/null; then
wf_file="$(yq -r '.includes.workflow // empty' "$manifest" 2>/dev/null)"
fi
if [[ -n "$wf_file" && -f "$bundle_dir/$wf_file" ]]; then
confirm_overwrite ".archeflow/workflows" "Workflows directory"
cp "$bundle_dir/$wf_file" ".archeflow/workflows/$wf_file"
info "Workflow: $wf_file -> .archeflow/workflows/"
elif [[ -n "$wf_file" && -f "$bundle_dir/workflow.yaml" ]]; then
confirm_overwrite ".archeflow/workflows" "Workflows directory"
cp "$bundle_dir/workflow.yaml" ".archeflow/workflows/$wf_file"
info "Workflow: $wf_file -> .archeflow/workflows/"
elif [[ -n "$wf_file" ]]; then
warn "Workflow file not found in bundle: $wf_file"
fi
# Copy archetypes
local arch_count=0
if [[ -d "$bundle_dir/archetypes" ]] && has_files "$bundle_dir/archetypes" "*.md"; then
confirm_overwrite ".archeflow/archetypes" "Archetypes directory"
for f in "$bundle_dir"/archetypes/*.md; do
[[ -f "$f" ]] || continue
cp "$f" ".archeflow/archetypes/$(basename "$f")"
arch_count=$((arch_count + 1))
done
info "Archetypes: $arch_count files -> .archeflow/archetypes/"
fi
# Copy domain
local domain_file
domain_file="$(yaml_value "$manifest" "includes.domain" 2>/dev/null || true)"
if [[ -z "$domain_file" ]] && command -v yq &>/dev/null; then
domain_file="$(yq -r '.includes.domain // empty' "$manifest" 2>/dev/null)"
fi
if [[ -n "$domain_file" && -f "$bundle_dir/$domain_file" ]]; then
confirm_overwrite ".archeflow/domains" "Domains directory"
cp "$bundle_dir/$domain_file" ".archeflow/domains/$domain_file"
info "Domain: $domain_file -> .archeflow/domains/"
elif [[ -n "$domain_file" && -f "$bundle_dir/domain.yaml" ]]; then
confirm_overwrite ".archeflow/domains" "Domains directory"
cp "$bundle_dir/domain.yaml" ".archeflow/domains/$domain_file"
info "Domain: $domain_file -> .archeflow/domains/"
elif [[ -n "$domain_file" ]]; then
warn "Domain file not found in bundle: $domain_file"
fi
# Copy hooks if present
if [[ -f "$bundle_dir/hooks.yaml" ]]; then
cp "$bundle_dir/hooks.yaml" ".archeflow/hooks.yaml"
info "Hooks: hooks.yaml -> .archeflow/"
fi
# Generate config.yaml with variables
local config_file=".archeflow/config.yaml"
{
echo "# Generated by archeflow init from bundle: $bundle_name"
echo "bundle: $bundle_name"
local version
version="$(yaml_value "$manifest" "version")"
echo "bundle_version: ${version:-1}"
echo "initialized: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "variables:"
# Read default variables from manifest
local -A vars=()
if command -v yq &>/dev/null; then
while IFS='=' read -r k v; do
[[ -n "$k" ]] && vars["$k"]="$v"
done < <(yq -r '.variables // {} | to_entries[] | "\(.key)=\(.value)"' "$manifest" 2>/dev/null)
else
# Simple fallback: parse variables section
local in_vars=false
while IFS= read -r line; do
if [[ "$line" =~ ^variables: ]]; then
in_vars=true; continue
fi
if $in_vars; then
if [[ "$line" =~ ^[[:space:]]+(.*):\ (.*) ]]; then
local vk="${BASH_REMATCH[1]}" vv="${BASH_REMATCH[2]}"
vk="$(echo "$vk" | xargs)"
vv="$(echo "$vv" | sed 's/#.*//' | xargs)"
[[ -n "$vk" ]] && vars["$vk"]="$vv"
elif [[ "$line" =~ ^[^[:space:]] ]]; then
break
fi
fi
done < "$manifest"
fi
# Apply overrides
for k in "${!overrides[@]}"; do
vars["$k"]="${overrides[$k]}"
done
# Write variables
if [[ ${#vars[@]} -eq 0 ]]; then
echo " # (no variables defined)"
else
for k in $(echo "${!vars[@]}" | tr ' ' '\n' | sort); do
echo " $k: ${vars[$k]}"
done
fi
} > "$config_file"
info "Config: $config_file"
echo ""
echo "ArcheFlow initialized from bundle: $bundle_name"
# Print variable summary
if [[ ${#vars[@]} -gt 0 ]]; then
local var_summary=""
for k in $(echo "${!vars[@]}" | tr ' ' '\n' | sort); do
[[ -n "$var_summary" ]] && var_summary+=", "
var_summary+="${k}=${vars[$k]}"
done
echo " Variables: $var_summary"
fi
echo ""
echo "Ready to run: archeflow:run"
}
cmd_init_from() {
local source_path="$1"
[[ -d "$source_path/.archeflow" ]] || die "No .archeflow/ directory found in $source_path"
echo "Cloning ArcheFlow setup from: $source_path"
echo ""
mkdir -p .archeflow
local copied=0
for subdir in teams workflows archetypes domains; do
if [[ -d "$source_path/.archeflow/$subdir" ]] && has_files "$source_path/.archeflow/$subdir"; then
confirm_overwrite ".archeflow/$subdir" "$subdir directory"
mkdir -p ".archeflow/$subdir"
cp "$source_path/.archeflow/$subdir"/* ".archeflow/$subdir/"
local count
count=$(find ".archeflow/$subdir" -maxdepth 1 -type f | wc -l)
info "$subdir/: $count files copied"
copied=$((copied + count))
fi
done
# Copy config.yaml if present
if [[ -f "$source_path/.archeflow/config.yaml" ]]; then
cp "$source_path/.archeflow/config.yaml" ".archeflow/config.yaml"
info "config.yaml copied"
copied=$((copied + 1))
fi
# Copy hooks.yaml if present
if [[ -f "$source_path/.archeflow/hooks.yaml" ]]; then
cp "$source_path/.archeflow/hooks.yaml" ".archeflow/hooks.yaml"
info "hooks.yaml copied"
copied=$((copied + 1))
fi
# Explicitly skip run-specific directories
for skip in events artifacts context templates; do
if [[ -d "$source_path/.archeflow/$skip" ]]; then
info "(skipped $skip/ — run-specific data)"
fi
done
echo ""
echo "Cloned $copied files from $source_path"
echo "Ready to run: archeflow:run"
}
cmd_save() {
local name="$1"
[[ -d ".archeflow" ]] || die "No .archeflow/ directory in current project. Nothing to save."
local bundle_dir="$GLOBAL_TEMPLATES/bundles/$name"
if [[ -d "$bundle_dir" ]]; then
warn "Template bundle already exists: $bundle_dir"
if [[ -t 0 ]]; then
read -r -p " Overwrite? [y/N] " answer
[[ "$answer" =~ ^[Yy]$ ]] || die "Aborted."
else
die "Non-interactive mode — will not overwrite existing bundle $name."
fi
rm -rf "$bundle_dir"
fi
mkdir -p "$bundle_dir"
echo "Saving current setup as template: $name"
echo ""
local team_file="" wf_file="" domain_file=""
local -a arch_files=()
local file_count=0
# Copy teams (take first .yaml file)
if [[ -d ".archeflow/teams" ]] && has_files ".archeflow/teams" "*.yaml"; then
team_file="$(ls .archeflow/teams/*.yaml 2>/dev/null | head -1)"
if [[ -n "$team_file" ]]; then
cp "$team_file" "$bundle_dir/$(basename "$team_file")"
team_file="$(basename "$team_file")"
info "Team: $team_file"
file_count=$((file_count + 1))
fi
fi
# Copy workflows (take first .yaml file)
if [[ -d ".archeflow/workflows" ]] && has_files ".archeflow/workflows" "*.yaml"; then
wf_file="$(ls .archeflow/workflows/*.yaml 2>/dev/null | head -1)"
if [[ -n "$wf_file" ]]; then
cp "$wf_file" "$bundle_dir/$(basename "$wf_file")"
wf_file="$(basename "$wf_file")"
info "Workflow: $wf_file"
file_count=$((file_count + 1))
fi
fi
# Copy archetypes
if [[ -d ".archeflow/archetypes" ]] && has_files ".archeflow/archetypes" "*.md"; then
mkdir -p "$bundle_dir/archetypes"
for f in .archeflow/archetypes/*.md; do
[[ -f "$f" ]] || continue
cp "$f" "$bundle_dir/archetypes/"
arch_files+=("$(basename "$f")")
file_count=$((file_count + 1))
done
info "Archetypes: ${#arch_files[@]} files"
fi
# Copy domain (take first .yaml file)
if [[ -d ".archeflow/domains" ]] && has_files ".archeflow/domains" "*.yaml"; then
domain_file="$(ls .archeflow/domains/*.yaml 2>/dev/null | head -1)"
if [[ -n "$domain_file" ]]; then
cp "$domain_file" "$bundle_dir/$(basename "$domain_file")"
domain_file="$(basename "$domain_file")"
info "Domain: $domain_file"
file_count=$((file_count + 1))
fi
fi
# Copy hooks if present
if [[ -f ".archeflow/hooks.yaml" ]]; then
cp ".archeflow/hooks.yaml" "$bundle_dir/hooks.yaml"
info "Hooks: hooks.yaml"
file_count=$((file_count + 1))
fi
# Detect domain name from domain file
local domain_name=""
if [[ -n "$domain_file" && -f "$bundle_dir/$domain_file" ]]; then
domain_name="$(yaml_value "$bundle_dir/$domain_file" "name")"
fi
# Read variables from config.yaml if present
local has_vars=false
local vars_yaml=""
if [[ -f ".archeflow/config.yaml" ]]; then
if command -v yq &>/dev/null; then
vars_yaml="$(yq -r '.variables // {} | to_entries[] | " \(.key): \(.value)"' ".archeflow/config.yaml" 2>/dev/null)"
[[ -n "$vars_yaml" ]] && has_vars=true
else
local in_vars=false
while IFS= read -r line; do
if [[ "$line" =~ ^variables: ]]; then
in_vars=true; continue
fi
if $in_vars; then
if [[ "$line" =~ ^[[:space:]] ]]; then
vars_yaml+="$line"$'\n'
has_vars=true
else
break
fi
fi
done < ".archeflow/config.yaml"
fi
fi
# Generate manifest
local project_dir
project_dir="$(basename "$(pwd)")"
{
echo "name: $name"
echo "description: \"Saved from $project_dir\""
echo "version: 1"
[[ -n "$domain_name" ]] && echo "domain: $domain_name"
echo "includes:"
[[ -n "$team_file" ]] && echo " team: $team_file"
[[ -n "$wf_file" ]] && echo " workflow: $wf_file"
if [[ ${#arch_files[@]} -gt 0 ]]; then
echo " archetypes:"
for a in "${arch_files[@]}"; do
echo " - $a"
done
fi
[[ -n "$domain_file" ]] && echo " domain: $domain_file"
echo "requires: []"
if $has_vars; then
echo "variables:"
echo "$vars_yaml"
else
echo "variables: {}"
fi
} > "$bundle_dir/manifest.yaml"
file_count=$((file_count + 1)) # manifest itself
echo ""
echo "Template saved: $name"
echo " Location: $bundle_dir/"
echo " Files: $file_count"
echo " Use with: archeflow init $name"
}
cmd_share() {
local name="$1" target="$2"
local bundle_dir=""
for base in "$LOCAL_TEMPLATES" "$GLOBAL_TEMPLATES"; do
if [[ -d "$base/bundles/$name" ]]; then
bundle_dir="$base/bundles/$name"
break
fi
done
[[ -n "$bundle_dir" ]] || die "Bundle not found: $name. Run '$0 --list' to see available templates."
mkdir -p "$target"
cp -r "$bundle_dir" "$target/$name"
echo "Exported: $target/$name/"
echo "To import: cp -r $target/$name ~/.archeflow/templates/bundles/"
}
# --- Main -------------------------------------------------------------------
if [[ $# -eq 0 ]]; then
echo "Usage:"
echo " $0 <bundle-name> [--set key=value ...] Init from named bundle"
echo " $0 --from <project-path> Clone from another project"
echo " $0 --list List available templates"
echo " $0 --save <name> Save current setup as template"
echo " $0 --share <name> <path> Export template to directory"
exit 0
fi
case "$1" in
--list)
cmd_list
;;
--from)
[[ $# -ge 2 ]] || die "--from requires a project path"
cmd_init_from "$2"
;;
--save)
[[ $# -ge 2 ]] || die "--save requires a template name"
cmd_save "$2"
;;
--share)
[[ $# -ge 3 ]] || die "--share requires a name and a target path"
cmd_share "$2" "$3"
;;
-*)
die "Unknown option: $1"
;;
*)
cmd_init_bundle "$@"
;;
esac