#!/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 [--set key=value ...] Init from named bundle # archeflow-init.sh --from Clone from another project # archeflow-init.sh --list List available templates # archeflow-init.sh --save Save current setup as template # archeflow-init.sh --share 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 [--set key=value ...] Init from named bundle" echo " $0 --from Clone from another project" echo " $0 --list List available templates" echo " $0 --save Save current setup as template" echo " $0 --share 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