Fixed setups
This commit is contained in:
+68
-107
@@ -3,124 +3,85 @@ description: Set up the obsidian-vault-kb skill (vault path, mode, minimal permi
|
||||
argument-hint: [vault-path] [mode]
|
||||
---
|
||||
|
||||
Set up the `obsidian-vault-kb` skill end to end. As part of the
|
||||
`obsidian-vault-kb` plugin, this command is automatically namespaced by
|
||||
Claude Code and invoked as `/obsidian-vault-kb:setup` — no manual filename
|
||||
prefixing needed. Follow these steps in order.
|
||||
Set up the `obsidian-vault-kb` skill. As part of the `obsidian-vault-kb`
|
||||
plugin, this command is automatically namespaced by Claude Code and invoked
|
||||
as `/obsidian-vault-kb:setup`.
|
||||
|
||||
## 1. Determine the skill's install directory
|
||||
Almost everything about this setup — validating vault paths, writing the
|
||||
skill's config, merging permissions into settings.json, setting executable
|
||||
bits — happens inside one bundled script, `scripts/setup.sh`, which you run
|
||||
as a **single** Bash call. That script also copies the wrapper scripts it
|
||||
needs to a stable, self-controlled location
|
||||
(`~/.agent-skills/obsidian-vault-kb/bin/`) before writing any permissions,
|
||||
so the permission rules never have to change again across future plugin
|
||||
updates — only this first run (and, optionally, a re-run after a plugin
|
||||
update) needs to locate the plugin itself.
|
||||
|
||||
This plugin's `skills/obsidian-vault-kb/` is a symlink into the repo's
|
||||
tool-neutral `skills/obsidian-vault-kb/` directory (the single source of
|
||||
truth for `SKILL.md`, `scripts/`, and `references/`, shared across all
|
||||
vendor adapters in this repo). Resolve it to its real, absolute,
|
||||
symlink-free path:
|
||||
## 1. Ask the user — selectable questions first, vault path last
|
||||
|
||||
**1a. First turn: two questions via the selection UI, fixed options for
|
||||
both:**
|
||||
- Mode: `read-only`, `append`, or `maintain` (as defined in SKILL.md —
|
||||
explain each briefly if unsure).
|
||||
- Settings scope: `project` (`.claude/settings.json` in the current
|
||||
project) or `user` (`~/.claude/settings.json`).
|
||||
|
||||
**1b. Second turn, plain chat message, no tool call.** Ask exactly:
|
||||
"What's the path to your vault? Give it a short name too if you're
|
||||
registering more than one." End your turn right after asking this, with
|
||||
nothing else queued up, so the user's reply is what actually gets
|
||||
collected before you continue. (Only a tool call reliably pauses for a
|
||||
reply in this environment; plain text followed immediately by a tool call
|
||||
in the same turn does not wait — that's what caused this question to
|
||||
appear skipped in an earlier version of this command.) At least one vault
|
||||
is required — `setup.sh` errors out without one — so this question is
|
||||
always asked, unlike the optional repos question in the git-manager
|
||||
plugin's setup.
|
||||
|
||||
## 2. Run the setup script — one command
|
||||
|
||||
Claude Code installs plugins added from a local marketplace at
|
||||
`~/.claude/plugins/cache/<marketplace-name>/<plugin-name>/<plugin-version>/`.
|
||||
For this repo's marketplace (`skill-repo`) and this plugin's current
|
||||
version (`1.0.0` — check this plugin's own `.claude-plugin/plugin.json` if
|
||||
this has since changed), that's:
|
||||
|
||||
```bash
|
||||
realpath <plugin-dir>/skills/obsidian-vault-kb
|
||||
~/.claude/plugins/cache/skill-repo/obsidian-vault-kb/1.0.0/scripts/setup.sh \
|
||||
--settings-scope <project|user> \
|
||||
[--project-dir <path>] \
|
||||
--mode <read-only|append|maintain> \
|
||||
--vault <name>=<path> [--vault <name>=<path> ...]
|
||||
```
|
||||
|
||||
This resolved path is `<SKILL_DIR>` for the rest of this setup — you'll
|
||||
need it exactly for step 4. Use the real path (not the symlink path) so the
|
||||
permissions whitelisted in step 4 keep working even if this plugin
|
||||
directory is relocated relative to the shared `skills/` directory.
|
||||
The script itself validates each vault path exists and is a directory, and
|
||||
reports an error if not — no separate validation call needed beforehand.
|
||||
Try this path directly first — don't `find`/`ls` preemptively.
|
||||
|
||||
## 2. Gather vault path and mode
|
||||
|
||||
If `$ARGUMENTS` contains a vault path and/or mode, use those directly.
|
||||
Otherwise ask the user:
|
||||
|
||||
1. Absolute path to the Obsidian vault (if there's more than one vault, ask
|
||||
for a short name per vault too).
|
||||
2. Mode: `read-only`, `append`, or `maintain` (as defined in SKILL.md —
|
||||
explain briefly if the user is unsure which to pick).
|
||||
|
||||
Validate the path exists and is a directory before continuing:
|
||||
If the exact cache path doesn't exist (installed version differs from
|
||||
`1.0.0`, differently named marketplace, or a future cache layout change),
|
||||
fall back to exactly one bounded lookup instead of guessing further:
|
||||
|
||||
```bash
|
||||
test -d "<vault-path>" && echo OK || echo "MISSING"
|
||||
find ~/.claude/plugins/cache/skill-repo/obsidian-vault-kb -maxdepth 2 -type d 2>/dev/null
|
||||
```
|
||||
|
||||
If missing, ask the user to correct it before proceeding.
|
||||
and construct the same `scripts/setup.sh` call using whatever version
|
||||
directory that reveals. This is expected to prompt for approval the first
|
||||
time — there's no way to pre-whitelist it before the setup that establishes
|
||||
the whitelist has run.
|
||||
|
||||
## 3. Write the skill configuration
|
||||
## 3. Relay the result
|
||||
|
||||
The config path is tool-neutral (not Claude-specific), so this same file
|
||||
would be reused if another Agent-Skills-compatible tool ran this skill:
|
||||
The script prints a JSON summary (stable script location, settings file
|
||||
written, config file, mode, vaults registered, the exact permission rules
|
||||
added, and a note about session reload). Present that summary to the user
|
||||
in plain language — don't re-read any of those files yourself to "double
|
||||
check"; the script's own output already reflects exactly what's on disk.
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.agent-skills/obsidian-vault-kb
|
||||
cat > ~/.agent-skills/obsidian-vault-kb/config.json << 'EOF'
|
||||
{
|
||||
"vaults": [
|
||||
{"name": "<short-name>", "path": "<absolute-vault-path>"}
|
||||
],
|
||||
"mode": "<read-only|append|maintain>"
|
||||
}
|
||||
EOF
|
||||
cat ~/.agent-skills/obsidian-vault-kb/config.json
|
||||
```
|
||||
|
||||
Confirm the written content with the user.
|
||||
|
||||
## 4. Generate and merge the minimal permissions whitelist
|
||||
|
||||
Read `permissions-whitelist.template.json` (next to this plugin's
|
||||
`plugin.json`, i.e. `<plugin-dir>/permissions-whitelist.template.json`). It
|
||||
whitelists exactly three commands, each pinned to its full absolute path —
|
||||
no generic `Bash(find:*)`, `Bash(rg:*)`, `Bash(cat:*)`, etc., since those
|
||||
would allow reading anywhere on the filesystem rather than just the vault:
|
||||
|
||||
- `Bash(<SKILL_DIR>/scripts/vault_index.sh:*)`
|
||||
- `Bash(<SKILL_DIR>/scripts/vault_search.sh:*)`
|
||||
- `Bash(<SKILL_DIR>/scripts/vault_backlinks.sh:*)`
|
||||
- `Read(~/.agent-skills/obsidian-vault-kb/config.json)`
|
||||
|
||||
These three scripts resolve the vault path only from
|
||||
`~/.agent-skills/obsidian-vault-kb/config.json` and refuse to operate on any
|
||||
path outside it (see their source), so whitelisting them by exact path does
|
||||
not grant broader filesystem access — not even to other files in the same
|
||||
`scripts/` directory, since the wildcard is per-script-file, not per-folder.
|
||||
|
||||
Steps:
|
||||
|
||||
1. Replace every `<SKILL_DIR>` placeholder in the template with the real,
|
||||
symlink-resolved absolute path from step 1.
|
||||
2. Locate the target settings file: prefer the project-level
|
||||
`.claude/settings.json` if a project is open, otherwise the user-level
|
||||
`~/.claude/settings.json`. Ask the user which one they want if unclear.
|
||||
3. If the target file doesn't exist yet, create it containing just the
|
||||
substituted `permissions.allow` array.
|
||||
4. If it exists, read it first and merge: add only entries from the
|
||||
substituted array that aren't already present in the target file's
|
||||
`permissions.allow` array. Don't duplicate entries, don't remove or
|
||||
overwrite unrelated existing permissions in the file.
|
||||
5. Show the user exactly what was added (the four entries above with the
|
||||
real path filled in) before writing, so they can see precisely what
|
||||
they're approving.
|
||||
|
||||
Do **not** add anything beyond these four entries. In particular, do not
|
||||
whitelist writing to the vault or to the config file — those keep prompting
|
||||
for confirmation every time, by design, in `append`/`maintain` mode.
|
||||
|
||||
## 5. Make the helper scripts executable
|
||||
|
||||
```bash
|
||||
chmod +x <SKILL_DIR>/scripts/vault_index.sh <SKILL_DIR>/scripts/vault_search.sh <SKILL_DIR>/scripts/vault_backlinks.sh
|
||||
```
|
||||
|
||||
Note this modifies the shared `skills/obsidian-vault-kb/scripts/` files in
|
||||
the repo (via the resolved real path), not a plugin-local copy — that's
|
||||
expected, since the symlink means there is only one copy on disk.
|
||||
|
||||
## 6. Confirm and offer a test run
|
||||
|
||||
Summarize to the user:
|
||||
- Vault path(s) and mode that were configured
|
||||
- The exact four permission entries that were added, and where (project vs.
|
||||
user settings)
|
||||
- That nothing else was whitelisted — every other command, and any write
|
||||
into the vault, still prompts for confirmation
|
||||
|
||||
Then offer to run `<SKILL_DIR>/scripts/vault_index.sh` as a quick sanity
|
||||
check, if the user wants to see it working right away.
|
||||
Do **not** follow this with a test invocation of `vault_index.sh` — Claude
|
||||
Code loads permissions at session start and doesn't always pick up changes
|
||||
made by its own file edits within the same session, so an immediate test
|
||||
can prompt (or not) in a way that doesn't reliably indicate success either
|
||||
way. If a real command later still prompts, the fix is a fresh session, not
|
||||
re-running this setup — the configuration is already correctly saved.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"_comment": "Template for this skill's Claude Code permissions. <SKILL_DIR> must be replaced with the absolute path where this plugin's skills/obsidian-vault-kb directory resolves to (i.e. the real, symlink-resolved path of skills/obsidian-vault-kb inside vendor/claude-code/plugins/obsidian-vault-kb/) before merging into settings.json. The /obsidian-vault-kb:setup command does this substitution automatically. Only the three read-only wrapper scripts are whitelisted, each pinned to its exact absolute path — no generic Bash(find:*), Bash(rg:*), Bash(cat:*), etc. The wrapper scripts themselves resolve the vault path only from ~/.agent-skills/obsidian-vault-kb/config.json and refuse to operate outside it, so whitelisting them does not grant filesystem access beyond the configured vault.",
|
||||
"_comment": "Reference only — setup.sh generates and writes these exact rules itself, so this file is no longer read during setup. Kept here for auditing/documentation: this is what /obsidian-vault-kb:setup will add to your permissions.allow, with <SKILL_DIR> substituted for the real, symlink-resolved skills/obsidian-vault-kb path. Only the three read-only wrapper scripts are whitelisted, each pinned to its exact absolute path — no generic Bash(find:*), Bash(rg:*), Bash(cat:*), etc. The wrapper scripts themselves resolve the vault path only from ~/.agent-skills/obsidian-vault-kb/config.json and refuse to operate outside it, so whitelisting them does not grant filesystem access beyond the configured vault.",
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(<SKILL_DIR>/scripts/vault_index.sh:*)",
|
||||
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user