Files
nac 777607dc29 chore(repo): establish project baseline
Track the source-of-truth code, skills, bootstrap rules, docs, and packaging files before adding new features.

Exclude local vault config, generated skill mirrors, temporary exports, virtual environments, and business data files from version control.

Validation: inspected staged file list and git ignored status before commit.
2026-07-05 11:03:40 +00:00

246 lines
11 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
#
# obsidian-wiki setup — configures skill discovery for all supported AI agents.
#
# Usage: bash setup.sh
#
# What it does:
# 1. Creates .env from .env.example (if not present)
# 2. Writes ~/.obsidian-wiki/config so skills work from any project
# 3. Symlinks .skills/* into each agent's expected skills directory:
# Project-local:
# - .claude/skills/ (Claude Code)
# - .cursor/skills/ (Cursor)
# - .windsurf/skills/ (Windsurf)
# - .agents/skills/ (AGENTS.md-aware agents, generic)
# - .kiro/skills/ (Kiro IDE/CLI)
# Global:
# - ~/.claude/skills/ (Claude Code, portable skills only)
# - ~/.gemini/skills/ (Gemini CLI)
# - ~/.codex/skills/ (Codex)
# - ~/.hermes/skills/ (Hermes)
# - ~/.openclaw/skills/ (OpenClaw)
# - ~/.copilot/skills/ (GitHub Copilot CLI)
# - ~/.trae/skills/ (Trae)
# - ~/.trae-cn/skills/ (Trae CN)
# - ~/.kiro/skills/ (Kiro CLI)
# - ~/.agents/skills/ (OpenCode, Aider, Factory Droid, generic)
# 4. Bootstraps AGENTS.md aliases (CLAUDE.md, GEMINI.md, .hermes.md)
# 5. Prints a summary of what's ready
#
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILLS_DIR="$SCRIPT_DIR/.skills"
# install_skills <target_dir> <label> [relative|absolute] [skill-subset...]
# "relative" requires target_dir under $SCRIPT_DIR and emits ../-prefixed
# targets matching the committed symlinks. Extra args restrict the install
# to a named subset of skills (e.g. portable-only into ~/.claude/skills).
install_skills() {
local target_dir="$1"
local label="$2"
local mode="${3:-absolute}"
shift 3 || shift $#
local subset=("$@") # empty = install all
case "$mode" in
relative|absolute) ;;
*) echo "install_skills: bad mode '$mode' (want relative|absolute)" >&2; exit 1 ;;
esac
local rel_prefix=""
if [ "$mode" = "relative" ]; then
# Strip $SCRIPT_DIR prefix; if it doesn't match, target is outside the
# repo and "relative" isn't meaningful — bail rather than emit a wrong link.
local rel="${target_dir#"$SCRIPT_DIR"/}"
if [ "$rel" = "$target_dir" ]; then
echo "install_skills: relative mode requires target under \$SCRIPT_DIR ($target_dir)" >&2
exit 1
fi
# One ../ per path component in $rel; e.g. .claude/skills → 2 components → ../../
local slashes="${rel//[^\/]/}"
local depth=$(( ${#slashes} + 1 )) i
for (( i=0; i<depth; i++ )); do rel_prefix="../$rel_prefix"; done
fi
mkdir -p "$target_dir"
for skill in "$SKILLS_DIR"/*/; do
local skill_name link_path link_target
skill_name="$(basename "$skill")"
if [ ${#subset[@]} -gt 0 ]; then
local match=0 want
for want in "${subset[@]}"; do [ "$want" = "$skill_name" ] && match=1 && break; done
[ "$match" = 1 ] || continue
fi
link_path="$target_dir/$skill_name"
if [ "$mode" = "relative" ]; then
link_target="${rel_prefix}.skills/$skill_name"
else
link_target="${skill%/}"
fi
if [ -L "$link_path" ]; then
rm "$link_path"
elif [ -d "$link_path" ]; then
echo "⚠️ $link_path is a real directory, skipping symlink"
continue
elif [ -f "$link_path" ]; then
# Git on Windows without core.symlinks=true writes committed symlinks
# as regular files containing the target path. Replace with a real symlink.
rm "$link_path"
fi
ln -s "$link_target" "$link_path"
# Sanity check: every skill ships a SKILL.md, so a working symlink resolves it.
[ -e "$link_path/SKILL.md" ] || { echo "install_skills: broken link $link_path → $link_target" >&2; exit 1; }
done
echo "✅ Installed skills → $label"
}
echo ""
echo "╔══════════════════════════════════════════════════╗"
echo "║ obsidian-wiki — Agent Setup ║"
echo "╚══════════════════════════════════════════════════╝"
echo ""
# ── Step 1: .env ──────────────────────────────────────────────
if [ ! -f "$SCRIPT_DIR/.env" ]; then
cp "$SCRIPT_DIR/.env.example" "$SCRIPT_DIR/.env"
echo "✅ Created .env from .env.example"
echo " → Edit .env and set OBSIDIAN_VAULT_PATH before using skills."
else
echo "✅ .env already exists"
fi
# ── Step 1b: ~/.obsidian-wiki/config ─────────────────────────
GLOBAL_CONFIG_DIR="$HOME/.obsidian-wiki"
GLOBAL_CONFIG="$GLOBAL_CONFIG_DIR/config"
mkdir -p "$GLOBAL_CONFIG_DIR"
# Read vault path from .env if it's already set
VAULT_PATH=""
if [ -f "$SCRIPT_DIR/.env" ]; then
# Strip quotes if present, but preserve the path (spaces or not)
VAULT_PATH=$(grep -E '^OBSIDIAN_VAULT_PATH=' "$SCRIPT_DIR/.env" | cut -d'=' -f2- | sed 's/^"//;s/"$//')
fi
# If vault path is empty or placeholder, ask the user
if [ -z "$VAULT_PATH" ] || [ "$VAULT_PATH" = "/path/to/your/vault" ]; then
echo ""
read -p " Where is your Obsidian vault? (absolute path): " VAULT_PATH || true
if [ -n "$VAULT_PATH" ]; then
# Escape the path for sed: replace '/' with '\/' and '"' with '\"'
ESCAPED_PATH=$(printf '%s\n' "$VAULT_PATH" | sed -e 's/[\/&]/\\&/g' -e 's/"/\\"/g')
# Update .env with quoted path to preserve spaces
sed -i.bak "s|^OBSIDIAN_VAULT_PATH=.*|OBSIDIAN_VAULT_PATH=\"$ESCAPED_PATH\"|" "$SCRIPT_DIR/.env"
rm -f "$SCRIPT_DIR/.env.bak"
fi
fi
# Write global config with quoted path (preserves spaces)
cat > "$GLOBAL_CONFIG" <<EOF
OBSIDIAN_VAULT_PATH="$VAULT_PATH"
OBSIDIAN_WIKI_REPO="$SCRIPT_DIR"
EOF
echo "✅ Global config written to ~/.obsidian-wiki/config"
# ── Step 1c: Bootstrap symlinks ──────────────────────────────
# .hermes.md → AGENTS.md (Hermes resolves .hermes.md before AGENTS.md;
# a symlink keeps a single source of truth)
HERMES_BOOTSTRAP="$SCRIPT_DIR/.hermes.md"
if [ -L "$HERMES_BOOTSTRAP" ]; then
rm "$HERMES_BOOTSTRAP"
elif [ -f "$HERMES_BOOTSTRAP" ]; then
echo "⚠️ .hermes.md is a regular file, replacing with symlink"
rm "$HERMES_BOOTSTRAP"
fi
ln -s AGENTS.md "$HERMES_BOOTSTRAP"
echo "✅ .hermes.md → AGENTS.md"
# ── Step 2: Symlink skills into agent directories ─────────────
# Project-local skill dirs. Each of these is where the matching agent looks
# for skills scoped to this repo.
AGENT_DIRS=(
".claude/skills"
".cursor/skills"
".windsurf/skills"
".agents/skills"
".pi/skills" # Pi coding agent
".kiro/skills" # Kiro IDE/CLI (paired with .kiro/steering/obsidian-wiki.md)
)
for agent_dir in "${AGENT_DIRS[@]}"; do
install_skills "$SCRIPT_DIR/$agent_dir" "$agent_dir/" relative
done
# ── Step 3: Install global skills ────────────────────────────
# ~/.claude/skills gets only the two portable skills (usable from any project).
install_skills "$HOME/.claude/skills" "~/.claude/skills/ (wiki-update, wiki-query)" absolute wiki-update wiki-query
# Steps 3b–3j: Install all skills for every supported agent.
# OpenClaw discovers skills from ~/.agents/skills/ (per docs.openclaw.ai/skills);
# that path also covers OpenCode, Factory Droid, Aider, and any AGENTS.md-aware
# agent. Platforms that have a dedicated skills dir get their own symlink tree
# so discovery works regardless of whether the user relies on AGENTS.md.
install_skills "$HOME/.gemini/skills" "~/.gemini/skills/ (Gemini CLI)"
install_skills "$HOME/.gemini/antigravity/skills" "~/.gemini/antigravity/skills/ (Antigravity, legacy)"
install_skills "$HOME/.codex/skills" "~/.codex/skills/"
install_skills "$HOME/.hermes/skills" "~/.hermes/skills/ (Hermes default)"
# Hermes: active named profile (if $HERMES_HOME points to a non-default location)
if [ -n "$HERMES_HOME" ] && [ "$HERMES_HOME" != "$HOME/.hermes" ]; then
install_skills "$HERMES_HOME/skills" "$HERMES_HOME/skills/ (Hermes active profile: $(basename "$HERMES_HOME"))"
fi
# Hermes: all named profiles under ~/.hermes/profiles/
if [ -d "$HOME/.hermes/profiles" ]; then
for _hermes_profile_dir in "$HOME/.hermes/profiles"/*/; do
[ -d "$_hermes_profile_dir" ] || continue
_hermes_profile_name="$(basename "$_hermes_profile_dir")"
# Skip if already handled via $HERMES_HOME above
if [ -n "$HERMES_HOME" ] && [ "$HERMES_HOME" = "${_hermes_profile_dir%/}" ]; then
continue
fi
install_skills "${_hermes_profile_dir}skills" \
"~/.hermes/profiles/${_hermes_profile_name}/skills/ (Hermes profile: ${_hermes_profile_name})"
done
fi
install_skills "$HOME/.openclaw/skills" "~/.openclaw/skills/ (OpenClaw managed)"
install_skills "$HOME/.copilot/skills" "~/.copilot/skills/ (GitHub Copilot CLI)"
install_skills "$HOME/.trae/skills" "~/.trae/skills/ (Trae)"
install_skills "$HOME/.trae-cn/skills" "~/.trae-cn/skills/ (Trae CN)"
install_skills "$HOME/.kiro/skills" "~/.kiro/skills/ (Kiro CLI)"
install_skills "$HOME/.pi/agent/skills" "~/.pi/agent/skills/ (Pi)"
install_skills "$HOME/.agents/skills" "~/.agents/skills/ (OpenCode, Aider, Droid, generic)"
# ── Step 4: Summary ──────────────────────────────────────────
SKILL_COUNT=$(echo "$SKILLS_DIR"/*/ | tr ' ' '\n' | grep -c /)
echo ""
echo "───────────────────────────────────────────────────"
echo " Setup complete!"
echo ""
echo " Skills found: $SKILL_COUNT"
echo " Agents ready: Claude Code, Cursor, Windsurf, Gemini CLI, Antigravity,"
echo " Codex, Hermes, OpenClaw, OpenCode, Aider, Factory Droid,"
echo " Trae, Trae CN, Kiro, Pi, GitHub Copilot (CLI + VS Code Chat)"
echo ""
echo " Bootstrap files:"
echo " CLAUDE.md → Claude Code"
echo " GEMINI.md → Gemini / Antigravity"
echo " AGENTS.md → Codex, OpenClaw, OpenCode, Aider, Droid, Trae, Hermes, Pi"
echo " .hermes.md → Hermes (symlink → AGENTS.md)"
echo " .cursor/rules/obsidian-wiki.mdc → Cursor (alwaysApply)"
echo " .windsurf/rules/obsidian-wiki.md → Windsurf (always-on)"
echo " .kiro/steering/obsidian-wiki.md → Kiro (inclusion: always)"
echo " .agent/rules/obsidian-wiki.md → Google Antigravity (alwaysApply)"
echo " .agent/workflows/obsidian-wiki.md → Google Antigravity (slash commands)"
echo " .github/copilot-instructions.md → GitHub Copilot (VS Code Chat)"
echo ""
echo " Next steps:"
echo " 1. Open this project in your agent"
echo " 2. Say: \"Set up my wiki\""
echo ""
echo " From any other project:"
echo " /wiki-update → sync knowledge into your vault"
echo " /wiki-query → ask questions against your wiki"
echo "───────────────────────────────────────────────────"
echo ""