174 lines
6.4 KiB
Bash
Executable File
174 lines
6.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# One-shot setup for the obsidian-vault-kb plugin.
|
|
#
|
|
# Order matters here, deliberately: the permission rules are established
|
|
# FIRST, against a stable path we control ourselves
|
|
# (~/.agent-skills/obsidian-vault-kb/bin/), not against Claude Code's
|
|
# internal plugin cache path (which changes on every plugin update).
|
|
# Configuration (vaults, mode) comes after. Re-running this script after a
|
|
# plugin update re-syncs the stable copy without the permission rules ever
|
|
# needing to change again.
|
|
#
|
|
# Usage:
|
|
# setup.sh --settings-scope (project|user) [--project-dir <path>]
|
|
# --mode (read-only|append|maintain)
|
|
# --vault <name>=<path> [--vault <name>=<path> ...]
|
|
#
|
|
# Prints a JSON summary of what changed to stdout at the end.
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PLUGIN_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
SKILL_DIR="$(realpath "$PLUGIN_DIR/skills/obsidian-vault-kb")"
|
|
|
|
if ! command -v python3 >/dev/null 2>&1; then
|
|
echo "Error: python3 is required." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# --- 1. Stable, self-controlled target location. Copy the three wrapper
|
|
# scripts and their shared helper there — this is the ONLY location
|
|
# permissions will ever point to. ---
|
|
STABLE_DIR="$HOME/.agent-skills/obsidian-vault-kb/bin"
|
|
mkdir -p "$STABLE_DIR"
|
|
for f in vault_index.sh vault_search.sh vault_backlinks.sh _lib.sh; do
|
|
cp "$SKILL_DIR/scripts/$f" "$STABLE_DIR/$f"
|
|
done
|
|
chmod +x "$STABLE_DIR/vault_index.sh" "$STABLE_DIR/vault_search.sh" "$STABLE_DIR/vault_backlinks.sh"
|
|
|
|
# Tilde-form path for permission rules and SKILL.md's example commands —
|
|
# Claude Code matches Bash permission rules against the literal,
|
|
# unexpanded command text, so a rule written with the resolved absolute
|
|
# $HOME path (e.g. /home/hmk/...) does NOT match a command Claude typed as
|
|
# "~/...", even though both point at the same file. Keep both sides in
|
|
# tilde form so they actually match.
|
|
TILDE_STABLE_DIR="~/.agent-skills/obsidian-vault-kb/bin"
|
|
TILDE_CONFIG_FILE="~/.agent-skills/obsidian-vault-kb/config.json"
|
|
|
|
# --- 2. Parse arguments ---
|
|
SETTINGS_SCOPE=""
|
|
PROJECT_DIR="$PWD"
|
|
MODE=""
|
|
VAULTS=()
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--settings-scope) SETTINGS_SCOPE="$2"; shift 2 ;;
|
|
--project-dir) PROJECT_DIR="$2"; shift 2 ;;
|
|
--mode) MODE="$2"; shift 2 ;;
|
|
--vault) VAULTS+=("$2"); shift 2 ;;
|
|
*) echo "Error: unknown argument '$1'" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
if [ "$SETTINGS_SCOPE" != "project" ] && [ "$SETTINGS_SCOPE" != "user" ]; then
|
|
echo "Error: --settings-scope must be 'project' or 'user'" >&2
|
|
exit 1
|
|
fi
|
|
case "$MODE" in
|
|
read-only|append|maintain) ;;
|
|
*) echo "Error: --mode must be read-only, append, or maintain" >&2; exit 1 ;;
|
|
esac
|
|
if [ "${#VAULTS[@]}" -eq 0 ]; then
|
|
echo "Error: at least one --vault <name>=<path> is required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$SETTINGS_SCOPE" = "project" ]; then
|
|
SETTINGS_FILE="$PROJECT_DIR/.claude/settings.json"
|
|
else
|
|
SETTINGS_FILE="$HOME/.claude/settings.json"
|
|
fi
|
|
|
|
CONFIG_DIR="$HOME/.agent-skills/obsidian-vault-kb"
|
|
CONFIG_FILE="$CONFIG_DIR/config.json"
|
|
mkdir -p "$CONFIG_DIR"
|
|
|
|
# --- 3. Write permissions FIRST, against the stable path from step 1 ---
|
|
mkdir -p "$(dirname "$SETTINGS_FILE")"
|
|
|
|
python3 - "$SETTINGS_FILE" "$TILDE_STABLE_DIR" "$TILDE_CONFIG_FILE" << 'PYEOF'
|
|
import json, os, sys
|
|
|
|
settings_file, tilde_stable_dir, tilde_config_file = sys.argv[1:4]
|
|
|
|
settings = {}
|
|
if os.path.exists(settings_file):
|
|
with open(settings_file) as f:
|
|
settings = json.load(f)
|
|
|
|
perms = settings.setdefault("permissions", {})
|
|
allow = perms.setdefault("allow", [])
|
|
|
|
new_rules = [
|
|
f"Bash({tilde_stable_dir}/vault_index.sh:*)",
|
|
f"Bash({tilde_stable_dir}/vault_search.sh:*)",
|
|
f"Bash({tilde_stable_dir}/vault_backlinks.sh:*)",
|
|
f"Read({tilde_config_file})",
|
|
]
|
|
for rule in new_rules:
|
|
if rule not in allow:
|
|
allow.append(rule)
|
|
|
|
with open(settings_file, "w") as f:
|
|
json.dump(settings, f, indent=2)
|
|
f.write("\n")
|
|
PYEOF
|
|
|
|
# --- 4. THEN configuration: validate + write/merge vaults and mode ---
|
|
VAULTS_JSON=$(python3 -c '
|
|
import json, os, sys
|
|
pairs = sys.argv[1:]
|
|
vaults = []
|
|
for p in pairs:
|
|
name, path = p.split("=", 1)
|
|
real = os.path.realpath(path)
|
|
if not os.path.isdir(real):
|
|
sys.stderr.write(f"Error: vault path {path!r} (for {name!r}) is not a directory.\n")
|
|
sys.exit(1)
|
|
vaults.append({"name": name, "path": real})
|
|
print(json.dumps(vaults))
|
|
' "${VAULTS[@]}")
|
|
|
|
python3 - "$CONFIG_FILE" "$VAULTS_JSON" "$MODE" << 'PYEOF'
|
|
import json, os, sys
|
|
config_file, new_vaults_json, mode = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
new_vaults = json.loads(new_vaults_json)
|
|
|
|
existing = {"vaults": [], "mode": mode}
|
|
if os.path.exists(config_file):
|
|
with open(config_file) as f:
|
|
existing = json.load(f)
|
|
existing["mode"] = mode
|
|
|
|
existing_by_name = {v["name"]: v for v in existing.get("vaults", [])}
|
|
for v in new_vaults:
|
|
existing_by_name[v["name"]] = v # upsert: new/updated path always wins
|
|
existing["vaults"] = list(existing_by_name.values())
|
|
|
|
with open(config_file, "w") as f:
|
|
json.dump(existing, f, indent=2)
|
|
f.write("\n")
|
|
PYEOF
|
|
|
|
# --- 5. Summary ---
|
|
python3 - "$STABLE_DIR" "$SETTINGS_FILE" "$CONFIG_FILE" "$MODE" "$VAULTS_JSON" "$TILDE_STABLE_DIR" "$TILDE_CONFIG_FILE" << 'PYEOF'
|
|
import json, sys
|
|
stable_dir, settings_file, config_file, mode, vaults_json, tilde_stable_dir, tilde_config_file = sys.argv[1:8]
|
|
print(json.dumps({
|
|
"stable_script_location": stable_dir,
|
|
"settings_file": settings_file,
|
|
"config_file": config_file,
|
|
"mode": mode,
|
|
"vaults_registered": json.loads(vaults_json),
|
|
"permission_rules_added": [
|
|
f"Bash({tilde_stable_dir}/vault_index.sh:*)",
|
|
f"Bash({tilde_stable_dir}/vault_search.sh:*)",
|
|
f"Bash({tilde_stable_dir}/vault_backlinks.sh:*)",
|
|
f"Read({tilde_config_file})"
|
|
],
|
|
"note": "The permission rules point at a stable location this script controls (~/.agent-skills/obsidian-vault-kb/bin/), not at Claude Code's internal plugin cache — so they survive future plugin updates without changing. They're written in tilde form (~/...) to match the literal, unexpanded command text Claude Code matches against, not the resolved absolute $HOME path. Permissions are written to disk now, but Claude Code loads permissions at session start and does not always pick up changes made by its own file edits within the same session — if a vault script call still prompts after this, start a fresh session rather than re-running setup."
|
|
}, indent=2))
|
|
PYEOF
|