Added git-manager, updated READMEs
This commit is contained in:
@@ -12,6 +12,13 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Obsidian vault as a searchable knowledge base for lookup and note-taking.",
|
"description": "Obsidian vault as a searchable knowledge base for lookup and note-taking.",
|
||||||
"keywords": ["obsidian", "knowledge-base", "notes", "vault", "markdown"]
|
"keywords": ["obsidian", "knowledge-base", "notes", "vault", "markdown"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "git-manager",
|
||||||
|
"source": "./vendor/claude-code/plugins/git-manager",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Whitelisted git operations across one or more repositories via a single wrapper script, avoiding cd/chaining patterns that break Bash permission whitelisting.",
|
||||||
|
"keywords": ["git", "version-control", "workflow"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# skill-repo
|
||||||
|
|
||||||
|
Private collection of tool-neutral Agent Skills, plus optional vendor-specific
|
||||||
|
setup/integration adapters (currently: Claude Code).
|
||||||
|
|
||||||
|
## Why this structure
|
||||||
|
|
||||||
|
The `SKILL.md` format (YAML frontmatter + Markdown body + `scripts/` /
|
||||||
|
`references/` / `assets/`) is an open standard published at
|
||||||
|
[agentskills.io](https://agentskills.io), adopted by Claude Code, OpenAI
|
||||||
|
Codex, GitHub Copilot, Cursor, Gemini CLI, and others — the same skill files
|
||||||
|
work unmodified across these tools. What's *not* portable is host-specific
|
||||||
|
plumbing: slash commands, permission/allow-list syntax, plugin/marketplace
|
||||||
|
manifests. This repo keeps those two concerns physically separate.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
skill-repo/
|
||||||
|
├── skills/ ← tool-neutral, single source of truth
|
||||||
|
│ └── obsidian-vault-kb/
|
||||||
|
│ ├── SKILL.md
|
||||||
|
│ ├── references/
|
||||||
|
│ └── scripts/ ← config lives at ~/.agent-skills/<skill>/config.json
|
||||||
|
├── vendor/
|
||||||
|
│ └── claude-code/
|
||||||
|
│ ├── .claude-plugin/ ← (unused here; marketplace.json lives at repo root, see below)
|
||||||
|
│ └── plugins/
|
||||||
|
│ └── obsidian-vault-kb/
|
||||||
|
│ ├── .claude-plugin/
|
||||||
|
│ │ └── plugin.json
|
||||||
|
│ ├── skills/
|
||||||
|
│ │ └── obsidian-vault-kb -> ../../../../../skills/obsidian-vault-kb (symlink)
|
||||||
|
│ ├── commands/
|
||||||
|
│ │ └── setup.md ← -> /obsidian-vault-kb:setup
|
||||||
|
│ └── permissions-whitelist.template.json
|
||||||
|
└── .claude-plugin/
|
||||||
|
└── marketplace.json ← must live at repo root per Claude Code's convention
|
||||||
|
```
|
||||||
|
|
||||||
|
**`skills/<name>/`** is the only place skill content actually lives —
|
||||||
|
`SKILL.md`, `scripts/`, `references/`. It assumes nothing about the host
|
||||||
|
tool: configuration is read from `~/.agent-skills/<skill-name>/config.json`,
|
||||||
|
a tool-neutral location, not `~/.claude/...`.
|
||||||
|
|
||||||
|
**`vendor/<tool>/`** holds everything specific to one host tool: for Claude
|
||||||
|
Code, that's the plugin manifest, the permissions whitelist (Claude Code's
|
||||||
|
`Bash(...)` allow-list syntax), and the `/setup` slash command. The plugin's
|
||||||
|
`skills/<name>` is a **symlink** into the top-level `skills/` directory —
|
||||||
|
there is exactly one copy of the skill content on disk, never a duplicate
|
||||||
|
that can drift out of sync.
|
||||||
|
|
||||||
|
Adding support for another tool (e.g. a future OpenAI/Codex-specific
|
||||||
|
adapter) means adding `vendor/<other-tool>/` with that tool's own
|
||||||
|
conventions, symlinked back to the same `skills/<name>/`, without touching
|
||||||
|
the skill content itself.
|
||||||
|
|
||||||
|
Symlinks assume a POSIX filesystem; this repo doesn't attempt to be
|
||||||
|
Windows-git-checkout-friendly.
|
||||||
|
|
||||||
|
## Adding this marketplace locally (Claude Code)
|
||||||
|
|
||||||
|
```
|
||||||
|
/plugin marketplace add /absolute/path/to/skill-repo
|
||||||
|
/plugin install obsidian-vault-kb
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run `/obsidian-vault-kb:setup` to configure the vault path/mode and the
|
||||||
|
permissions whitelist.
|
||||||
|
|
||||||
|
## Adding a new skill to this repo
|
||||||
|
|
||||||
|
1. Create `skills/<new-skill-name>/` with `SKILL.md`, and `scripts/`
|
||||||
|
/`references/` as needed. Keep configuration at
|
||||||
|
`~/.agent-skills/<new-skill-name>/config.json` if the skill needs
|
||||||
|
persistent config — don't hardcode a Claude-specific path here.
|
||||||
|
2. For Claude Code support, create
|
||||||
|
`vendor/claude-code/plugins/<new-skill-name>/` with `.claude-plugin/plugin.json`,
|
||||||
|
a `skills/<new-skill-name>` symlink back to the top-level `skills/`
|
||||||
|
directory, and (if the skill needs restricted shell access) a
|
||||||
|
`commands/setup.md` plus `permissions-whitelist.template.json` following
|
||||||
|
the `obsidian-vault-kb` pattern — wrapper scripts that resolve their own
|
||||||
|
scope from the config file rather than accepting raw paths as arguments,
|
||||||
|
whitelisted only by exact absolute path, never generic tool wildcards
|
||||||
|
like `Bash(find:*)`.
|
||||||
|
3. Add an entry for the new plugin to `.claude-plugin/marketplace.json`.
|
||||||
|
4. Bump `version` in the plugin's `plugin.json` and its `marketplace.json`
|
||||||
|
entry on updates.
|
||||||
@@ -1,88 +1,72 @@
|
|||||||
# skill-repo
|
# skill-repo
|
||||||
|
|
||||||
Private collection of tool-neutral Agent Skills, plus optional vendor-specific
|
A collection of Claude Code plugins (Agent Skills) for working with git
|
||||||
setup/integration adapters (currently: Claude Code).
|
repositories and Obsidian vaults. Each plugin does one job, ships its own
|
||||||
|
setup command, and only asks for the minimum shell access it actually
|
||||||
|
needs.
|
||||||
|
|
||||||
## Why this structure
|
> Looking for how this repo is structured internally, or how to add a new
|
||||||
|
> plugin to it? See [ARCHITECTURE.md](ARCHITECTURE.md). This README is
|
||||||
|
> about what's in the box and how to use it.
|
||||||
|
|
||||||
The `SKILL.md` format (YAML frontmatter + Markdown body + `scripts/` /
|
## What's in here
|
||||||
`references/` / `assets/`) is an open standard published at
|
|
||||||
[agentskills.io](https://agentskills.io), adopted by Claude Code, OpenAI
|
|
||||||
Codex, GitHub Copilot, Cursor, Gemini CLI, and others — the same skill files
|
|
||||||
work unmodified across these tools. What's *not* portable is host-specific
|
|
||||||
plumbing: slash commands, permission/allow-list syntax, plugin/marketplace
|
|
||||||
manifests. This repo keeps those two concerns physically separate.
|
|
||||||
|
|
||||||
## Structure
|
| Plugin | What it does |
|
||||||
|
|---|---|
|
||||||
|
| [`git-manager`](vendor/claude-code/plugins/git-manager/README.md) | Runs git commands (status, log, commit, push, pull, ...) across one or more repositories through a single whitelisted script — no more permission prompts breaking on `cd && git ...` chains. |
|
||||||
|
| [`obsidian-vault-kb`](vendor/claude-code/plugins/obsidian-vault-kb/README.md) | Turns an Obsidian vault into a searchable knowledge base Claude can consult and (optionally) write back to. |
|
||||||
|
|
||||||
```
|
Click through to each plugin's own README for details, examples, and setup
|
||||||
skill-repo/
|
options.
|
||||||
├── skills/ ← tool-neutral, single source of truth
|
|
||||||
│ └── obsidian-vault-kb/
|
|
||||||
│ ├── SKILL.md
|
|
||||||
│ ├── references/
|
|
||||||
│ └── scripts/ ← config lives at ~/.agent-skills/<skill>/config.json
|
|
||||||
├── vendor/
|
|
||||||
│ └── claude-code/
|
|
||||||
│ ├── .claude-plugin/ ← (unused here; marketplace.json lives at repo root, see below)
|
|
||||||
│ └── plugins/
|
|
||||||
│ └── obsidian-vault-kb/
|
|
||||||
│ ├── .claude-plugin/
|
|
||||||
│ │ └── plugin.json
|
|
||||||
│ ├── skills/
|
|
||||||
│ │ └── obsidian-vault-kb -> ../../../../../skills/obsidian-vault-kb (symlink)
|
|
||||||
│ ├── commands/
|
|
||||||
│ │ └── setup.md ← -> /obsidian-vault-kb:setup
|
|
||||||
│ └── permissions-whitelist.template.json
|
|
||||||
└── .claude-plugin/
|
|
||||||
└── marketplace.json ← must live at repo root per Claude Code's convention
|
|
||||||
```
|
|
||||||
|
|
||||||
**`skills/<name>/`** is the only place skill content actually lives —
|
## Installing
|
||||||
`SKILL.md`, `scripts/`, `references/`. It assumes nothing about the host
|
|
||||||
tool: configuration is read from `~/.agent-skills/<skill-name>/config.json`,
|
|
||||||
a tool-neutral location, not `~/.claude/...`.
|
|
||||||
|
|
||||||
**`vendor/<tool>/`** holds everything specific to one host tool: for Claude
|
From within Claude Code:
|
||||||
Code, that's the plugin manifest, the permissions whitelist (Claude Code's
|
|
||||||
`Bash(...)` allow-list syntax), and the `/setup` slash command. The plugin's
|
|
||||||
`skills/<name>` is a **symlink** into the top-level `skills/` directory —
|
|
||||||
there is exactly one copy of the skill content on disk, never a duplicate
|
|
||||||
that can drift out of sync.
|
|
||||||
|
|
||||||
Adding support for another tool (e.g. a future OpenAI/Codex-specific
|
|
||||||
adapter) means adding `vendor/<other-tool>/` with that tool's own
|
|
||||||
conventions, symlinked back to the same `skills/<name>/`, without touching
|
|
||||||
the skill content itself.
|
|
||||||
|
|
||||||
Symlinks assume a POSIX filesystem; this repo doesn't attempt to be
|
|
||||||
Windows-git-checkout-friendly.
|
|
||||||
|
|
||||||
## Adding this marketplace locally (Claude Code)
|
|
||||||
|
|
||||||
```
|
```
|
||||||
/plugin marketplace add /absolute/path/to/skill-repo
|
/plugin marketplace add /absolute/path/to/skill-repo
|
||||||
|
```
|
||||||
|
|
||||||
|
(or a Git URL, once this repo is hosted somewhere reachable). Then install
|
||||||
|
whichever plugin(s) you want:
|
||||||
|
|
||||||
|
```
|
||||||
|
/plugin install git-manager
|
||||||
/plugin install obsidian-vault-kb
|
/plugin install obsidian-vault-kb
|
||||||
```
|
```
|
||||||
|
|
||||||
Then run `/obsidian-vault-kb:setup` to configure the vault path/mode and the
|
Each plugin has its own `/‹plugin-name›:setup` command — run it once after
|
||||||
permissions whitelist.
|
installing to configure paths and permissions. See the individual plugin
|
||||||
|
READMEs for what each setup asks for.
|
||||||
|
|
||||||
## Adding a new skill to this repo
|
## A shared design philosophy
|
||||||
|
|
||||||
1. Create `skills/<new-skill-name>/` with `SKILL.md`, and `scripts/`
|
Both plugins in this repo follow the same rules, worth knowing upfront:
|
||||||
/`references/` as needed. Keep configuration at
|
|
||||||
`~/.agent-skills/<new-skill-name>/config.json` if the skill needs
|
- **Minimal permissions.** Neither plugin whitelists generic commands like
|
||||||
persistent config — don't hardcode a Claude-specific path here.
|
`Bash(git:*)`, `Bash(find:*)`, or `Bash(cd:*)`. Each whitelists exactly
|
||||||
2. For Claude Code support, create
|
one wrapper script, pinned to its exact absolute path. The wrapper script
|
||||||
`vendor/claude-code/plugins/<new-skill-name>/` with `.claude-plugin/plugin.json`,
|
itself enforces what it's allowed to do (allowed git subcommands, vault
|
||||||
a `skills/<new-skill-name>` symlink back to the top-level `skills/`
|
boundaries) — the whitelist just says "this specific, self-limiting
|
||||||
directory, and (if the skill needs restricted shell access) a
|
script may run without asking."
|
||||||
`commands/setup.md` plus `permissions-whitelist.template.json` following
|
- **Nothing destructive is silent.** Actions that change files — writing
|
||||||
the `obsidian-vault-kb` pattern — wrapper scripts that resolve their own
|
notes, pushing to a remote — either aren't whitelisted (so you still get
|
||||||
scope from the config file rather than accepting raw paths as arguments,
|
a normal confirmation prompt) or are scoped tightly enough that there's
|
||||||
whitelisted only by exact absolute path, never generic tool wildcards
|
nothing surprising they can do.
|
||||||
like `Bash(find:*)`.
|
- **Config lives outside Claude Code**, at `~/.agent-skills/<plugin-name>/config.json`.
|
||||||
3. Add an entry for the new plugin to `.claude-plugin/marketplace.json`.
|
Both plugins follow the open [Agent Skills](https://agentskills.io)
|
||||||
4. Bump `version` in the plugin's `plugin.json` and its `marketplace.json`
|
format for their actual skill content (`SKILL.md` + `scripts/`), so that
|
||||||
entry on updates.
|
part works the same if you ever run them from a different
|
||||||
|
Agent-Skills-compatible tool — only the Claude Code-specific setup
|
||||||
|
command and permissions live under `vendor/claude-code/`.
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
After pulling changes to this repo:
|
||||||
|
|
||||||
|
```
|
||||||
|
/plugin marketplace update skill-repo
|
||||||
|
```
|
||||||
|
|
||||||
|
Re-run a plugin's `/‹plugin-name›:setup` if its permissions or config
|
||||||
|
format changed (check that plugin's README/changelog).
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
name: git-manager
|
||||||
|
description: Manages git operations (status, log, diff, show, fetch, remote, branch, checkout, add, commit, push, pull) across one or more git repositories — including non-code repositories such as an Obsidian vault kept under version control — through a single wrapper script instead of raw shell `git`/`cd` commands. Always use this skill instead of running `git` or `cd` directly whenever the task involves checking status, committing, pushing, pulling, or otherwise managing a git repository, especially when multiple repositories are involved in the same session (e.g. a code repo and a separate notes/vault repo).
|
||||||
|
---
|
||||||
|
|
||||||
|
# Git Manager
|
||||||
|
|
||||||
|
This skill wraps all git operations in a single script,
|
||||||
|
`scripts/git_cmd.sh`, so that:
|
||||||
|
|
||||||
|
- There is never a need for `cd <path> && git ...` to operate on a
|
||||||
|
non-default repository — pass `--repo <name>` instead.
|
||||||
|
- There is never a need to chain multiple git commands with `&&`/`;`/`|` in
|
||||||
|
one shell invocation — each git operation is its own separate call to the
|
||||||
|
wrapper.
|
||||||
|
|
||||||
|
This matters beyond convenience: on the host tool side (see "Setup and
|
||||||
|
permissions" below), only this single wrapper script is whitelisted to run
|
||||||
|
without a confirmation prompt. Chained or `cd`-based commands don't match
|
||||||
|
that whitelist entry, so avoiding them isn't just tidier — it's what keeps
|
||||||
|
every git operation actually covered by the whitelist instead of falling
|
||||||
|
back to prompts (or, worse, slipping through on a stale broad rule).
|
||||||
|
|
||||||
|
## How to run git commands
|
||||||
|
|
||||||
|
Always use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
<skill-dir>/scripts/git_cmd.sh <subcommand> [--repo <name>] [-- <git-args...>]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `<subcommand>` must be one of: `status`, `log`, `diff`, `show`, `fetch`,
|
||||||
|
`remote`, `branch`, `checkout`, `add`, `commit`, `push`, `pull`. Anything
|
||||||
|
else is rejected by the script itself before git runs.
|
||||||
|
- `--repo <name>` is optional. Omit it to operate on the git repository
|
||||||
|
containing the current working directory (this is the normal case for a
|
||||||
|
single-repo Claude Code project/session). Use it to target a different,
|
||||||
|
pre-configured repository without changing the working directory — e.g.
|
||||||
|
a separate Obsidian vault repo alongside a code repo in the same session.
|
||||||
|
- Everything after `--` is passed through to `git` literally (e.g.
|
||||||
|
`-- -m "commit message"`, `-- --oneline -10`, `-- origin main`).
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
<skill-dir>/scripts/git_cmd.sh status
|
||||||
|
<skill-dir>/scripts/git_cmd.sh log -- --oneline -10
|
||||||
|
<skill-dir>/scripts/git_cmd.sh add -- -A
|
||||||
|
<skill-dir>/scripts/git_cmd.sh commit -- -m "Update DNS notes"
|
||||||
|
<skill-dir>/scripts/git_cmd.sh push --repo homelab-notes -- origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
Never call `git` directly, and never use `cd` to switch into a different
|
||||||
|
repo before running git — use `--repo` instead. If a task genuinely needs a
|
||||||
|
subcommand outside the allowed list (e.g. `stash`, `merge`, `rebase`,
|
||||||
|
`reset`, `tag`), say so explicitly to the user rather than working around
|
||||||
|
the restriction (e.g. via `git -C` called outside this script, or editing
|
||||||
|
`.git` internals directly) — that path isn't whitelisted and existing on
|
||||||
|
purpose as a guardrail, not an oversight.
|
||||||
|
|
||||||
|
## Multi-repo setup
|
||||||
|
|
||||||
|
If a task refers to a named repo (e.g. "push the homelab-notes vault") and
|
||||||
|
`~/.agent-skills/git-manager/config.json` doesn't have an entry for it yet,
|
||||||
|
ask the user for its absolute path and add it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat ~/.agent-skills/git-manager/config.json 2>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"repos": [
|
||||||
|
{"name": "homelab-notes", "path": "/absolute/path/to/vault"},
|
||||||
|
{"name": "dwh-pipeline", "path": "/absolute/path/to/repo"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Merge new entries in rather than overwriting existing ones. This file is
|
||||||
|
optional — omitting `--repo` and relying on the current working directory
|
||||||
|
works without any configuration at all.
|
||||||
|
|
||||||
|
## Setup and permissions
|
||||||
|
|
||||||
|
Configuration lives at the tool-neutral path
|
||||||
|
`~/.agent-skills/git-manager/config.json`, not a Claude-specific location.
|
||||||
|
Host-specific setup (permissions whitelist, hooks) lives under `vendor/<tool>/`
|
||||||
|
in this repo, not in this skill itself. For Claude Code, see
|
||||||
|
`vendor/claude-code/plugins/git-manager/commands/setup.md`
|
||||||
|
(`/git-manager:setup`), which whitelists exactly one command —
|
||||||
|
`Bash(<skill-dir>/scripts/git_cmd.sh:*)` — and optionally installs a
|
||||||
|
PreToolUse hook that forces a normal confirmation prompt (not a silent
|
||||||
|
block, not a silent allow) for any Bash call containing shell chaining
|
||||||
|
operators (`&&`, `;`, `|`, backticks, `$(...)`), as a safety net against
|
||||||
|
Claude Code's own permission-matching not always splitting compound
|
||||||
|
commands correctly.
|
||||||
Executable
+75
@@ -0,0 +1,75 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Shared helpers for the git-manager skill. Sourced by git_cmd.sh, not
|
||||||
|
# meant to be executed directly.
|
||||||
|
|
||||||
|
CONFIG_FILE="$HOME/.agent-skills/git-manager/config.json"
|
||||||
|
|
||||||
|
# Subcommands this skill is allowed to run. Anything not in this list is
|
||||||
|
# rejected outright by git_cmd.sh, regardless of what the caller asks for.
|
||||||
|
ALLOWED_SUBCOMMANDS=(status log diff show fetch remote branch checkout add commit push pull)
|
||||||
|
|
||||||
|
is_allowed_subcommand() {
|
||||||
|
local sub="$1"
|
||||||
|
local allowed
|
||||||
|
for allowed in "${ALLOWED_SUBCOMMANDS[@]}"; do
|
||||||
|
[ "$sub" = "$allowed" ] && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve a repo's absolute path.
|
||||||
|
# - If a name is given, look it up in the config file's "repos" array.
|
||||||
|
# - If no name is given, default to $PWD, but only if it's actually inside
|
||||||
|
# a git work tree.
|
||||||
|
# Never accepts a raw path from the caller directly — named repos always go
|
||||||
|
# through the config file, so the whitelisted script can't be pointed at an
|
||||||
|
# arbitrary directory outside what's configured (or the current project
|
||||||
|
# directory Claude Code itself already scoped the session to).
|
||||||
|
resolve_repo_path() {
|
||||||
|
local repo_name="${1:-}"
|
||||||
|
|
||||||
|
if [ -n "$repo_name" ]; then
|
||||||
|
if [ ! -f "$CONFIG_FILE" ]; then
|
||||||
|
echo "Error: no configuration found at $CONFIG_FILE. Run setup first, or omit the repo name to use the current directory." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! command -v python3 >/dev/null 2>&1; then
|
||||||
|
echo "Error: python3 is required to parse the config file." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
python3 - "$CONFIG_FILE" "$repo_name" << 'PYEOF'
|
||||||
|
import json, sys
|
||||||
|
config_file, repo_name = sys.argv[1], sys.argv[2]
|
||||||
|
with open(config_file) as f:
|
||||||
|
cfg = json.load(f)
|
||||||
|
repos = cfg.get("repos", [])
|
||||||
|
match = [r for r in repos if r.get("name") == repo_name]
|
||||||
|
if not match:
|
||||||
|
sys.stderr.write(f"Error: no repo named '{repo_name}' in config.\n")
|
||||||
|
sys.exit(1)
|
||||||
|
print(match[0]["path"])
|
||||||
|
PYEOF
|
||||||
|
else
|
||||||
|
echo "$PWD"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate that a resolved path is a real, existing directory that is
|
||||||
|
# actually inside a git work tree. Prints the canonical repo root.
|
||||||
|
canonicalize_and_check_repo() {
|
||||||
|
local path="$1"
|
||||||
|
local real
|
||||||
|
real=$(realpath -e "$path" 2>/dev/null) || {
|
||||||
|
echo "Error: path '$path' does not exist." >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if [ ! -d "$real" ]; then
|
||||||
|
echo "Error: path '$real' is not a directory." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! git -C "$real" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
echo "Error: '$real' is not inside a git work tree." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
git -C "$real" rev-parse --show-toplevel
|
||||||
|
}
|
||||||
Executable
+74
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Single entry point for all git operations in this skill. Designed so
|
||||||
|
# there is never a reason to use `cd` or shell chaining (&&, ;, |) to run
|
||||||
|
# git commands across different repos in one session.
|
||||||
|
#
|
||||||
|
# Usage: git_cmd.sh <subcommand> [--repo <name>] [-- <git-args...>]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# git_cmd.sh status
|
||||||
|
# git_cmd.sh log --repo homelab-notes -- --oneline -10
|
||||||
|
# git_cmd.sh commit --repo homelab-notes -- -m "Update DNS notes"
|
||||||
|
# git_cmd.sh push --repo homelab-notes -- origin main
|
||||||
|
#
|
||||||
|
# - <subcommand> must be one of the allowed subcommands in _lib.sh; anything
|
||||||
|
# else is rejected before git is ever invoked.
|
||||||
|
# - --repo <name> is optional. If omitted, operates on the current working
|
||||||
|
# directory (which must already be inside a git work tree) — this is what
|
||||||
|
# replaces `cd <path> && git ...`, since Claude Code already sets the
|
||||||
|
# working directory per project/session.
|
||||||
|
# - Everything after `--` is passed through to git as-is via argv (not
|
||||||
|
# re-interpreted by a shell), so quoting/spaces in e.g. commit messages
|
||||||
|
# are safe.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=_lib.sh
|
||||||
|
source "$SCRIPT_DIR/_lib.sh"
|
||||||
|
|
||||||
|
SUBCOMMAND="${1:-}"
|
||||||
|
if [ -z "$SUBCOMMAND" ]; then
|
||||||
|
echo "Usage: git_cmd.sh <subcommand> [--repo <name>] [-- <git-args...>]" >&2
|
||||||
|
echo "Allowed subcommands: ${ALLOWED_SUBCOMMANDS[*]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
|
||||||
|
if ! is_allowed_subcommand "$SUBCOMMAND"; then
|
||||||
|
echo "Error: subcommand '$SUBCOMMAND' is not allowed." >&2
|
||||||
|
echo "Allowed subcommands: ${ALLOWED_SUBCOMMANDS[*]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
REPO_NAME=""
|
||||||
|
GIT_ARGS=()
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--repo)
|
||||||
|
REPO_NAME="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
GIT_ARGS=("$@")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Error: unexpected argument '$1'. Put git flags/args after '--'." >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
REPO_RAW="$(resolve_repo_path "$REPO_NAME")"
|
||||||
|
REPO="$(canonicalize_and_check_repo "$REPO_RAW")"
|
||||||
|
|
||||||
|
echo "== Repo: $REPO =="
|
||||||
|
echo "== Running: git $SUBCOMMAND ${GIT_ARGS[*]:-} =="
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [ "${#GIT_ARGS[@]}" -eq 0 ]; then
|
||||||
|
git -C "$REPO" "$SUBCOMMAND"
|
||||||
|
else
|
||||||
|
git -C "$REPO" "$SUBCOMMAND" "${GIT_ARGS[@]}"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "git-manager",
|
||||||
|
"description": "Manages git operations across one or more repositories (code, Obsidian vaults, etc.) through a single whitelisted wrapper script, avoiding the cd/chaining patterns that break Claude Code's Bash permission whitelist.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Henner"
|
||||||
|
},
|
||||||
|
"keywords": ["git", "version-control", "workflow"]
|
||||||
|
}
|
||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
# git-manager
|
||||||
|
|
||||||
|
Run git commands across one or more repositories — your code, an Obsidian
|
||||||
|
vault kept under version control, whatever else — without Claude Code
|
||||||
|
needing to `cd` into a directory or chain shell commands together. That
|
||||||
|
matters because chained commands (`cd path && git ...`, `git add && git
|
||||||
|
commit && git push`) are exactly what breaks Bash permission whitelists:
|
||||||
|
either the whole chain needs separate approval every time, or — depending
|
||||||
|
on the Claude Code version — a chain can slip through on a rule that was
|
||||||
|
only meant to cover one piece of it. This plugin sidesteps the problem
|
||||||
|
instead of fighting it: there's simply never a reason to `cd` or chain.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
All git operations go through a single script, `git_cmd.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git_cmd.sh <subcommand> [--repo <name>] [-- <git-args...>]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **No `cd` needed.** Omit `--repo` and it operates on whatever git
|
||||||
|
repository your current working directory is in — which, in a normal
|
||||||
|
Claude Code session, is already the right repo. Use `--repo <name>` to
|
||||||
|
target a *different*, pre-registered repository (e.g. your notes vault)
|
||||||
|
without leaving your current directory.
|
||||||
|
- **No chaining needed.** Each git operation is its own call to the
|
||||||
|
script. Want to add, commit, and push? That's three separate calls, not
|
||||||
|
one chained command — which is exactly what keeps every single one of
|
||||||
|
them covered by the whitelist.
|
||||||
|
- **A fixed set of allowed subcommands:** `status`, `log`, `diff`, `show`,
|
||||||
|
`fetch`, `remote`, `branch`, `checkout`, `add`, `commit`, `push`, `pull`.
|
||||||
|
Anything else (`reset`, `rebase`, `filter-branch`, arbitrary `git config`,
|
||||||
|
...) is rejected by the script itself, before git ever runs — not because
|
||||||
|
those operations are inherently dangerous, but because they're
|
||||||
|
consequential enough that they should go through a normal confirmation
|
||||||
|
prompt rather than being auto-approved.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
After installing the plugin:
|
||||||
|
|
||||||
|
```
|
||||||
|
/git-manager:setup
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
|
||||||
|
1. Ask you (optionally) to register named repositories — useful if you
|
||||||
|
work with more than one repo in the same session, e.g. a code repo plus
|
||||||
|
a separate Obsidian vault repo. Skip this if you're fine relying on the
|
||||||
|
default (current working directory).
|
||||||
|
2. Add exactly one entry to your Claude Code permissions:
|
||||||
|
`Bash(<path-to-git_cmd.sh>:*)` — nothing broader.
|
||||||
|
3. Ask whether you want an additional safety net installed: a hook that
|
||||||
|
forces a **normal confirmation prompt** (not a silent block, not a
|
||||||
|
silent pass-through) any time a Bash command contains shell chaining or
|
||||||
|
substitution characters (`&&`, `;`, `|`, `` ` ``, `$(...)`) — see
|
||||||
|
"The anti-chaining hook" below.
|
||||||
|
|
||||||
|
## Usage examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Current repo (no --repo needed)
|
||||||
|
git_cmd.sh status
|
||||||
|
git_cmd.sh log -- --oneline -10
|
||||||
|
git_cmd.sh add -- -A
|
||||||
|
git_cmd.sh commit -- -m "Update notes"
|
||||||
|
git_cmd.sh push -- origin main
|
||||||
|
|
||||||
|
# A different, pre-registered repo — still no cd
|
||||||
|
git_cmd.sh status --repo homelab-notes
|
||||||
|
git_cmd.sh push --repo homelab-notes -- origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
Everything after `--` is passed to `git` as literal arguments (not
|
||||||
|
re-parsed by a shell), so quoting in commit messages etc. is safe.
|
||||||
|
|
||||||
|
## Registering repositories
|
||||||
|
|
||||||
|
Repositories are looked up by name in
|
||||||
|
`~/.agent-skills/git-manager/config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"repos": [
|
||||||
|
{"name": "homelab-notes", "path": "/absolute/path/to/vault"},
|
||||||
|
{"name": "dwh-pipeline", "path": "/absolute/path/to/repo"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This file is entirely optional. Without any entries, `--repo` simply isn't
|
||||||
|
available and every call operates on the current directory. Add entries any
|
||||||
|
time by asking Claude to register a new repo, or by editing the file
|
||||||
|
directly — writing to this file is never in the permissions whitelist, so
|
||||||
|
you'll always see (and can decline) that change.
|
||||||
|
|
||||||
|
## The anti-chaining hook
|
||||||
|
|
||||||
|
Optional, offered during `/git-manager:setup`. If installed, it watches
|
||||||
|
every Bash command Claude Code is about to run (not just git ones) and,
|
||||||
|
if the command contains a chaining or substitution operator, forces the
|
||||||
|
normal permission prompt instead of letting whitelist matching decide.
|
||||||
|
It never silently blocks anything and never silently approves anything —
|
||||||
|
worst case, you get one extra confirmation prompt you didn't strictly need;
|
||||||
|
best case, it catches a chained command before it runs unreviewed.
|
||||||
|
|
||||||
|
With `git_cmd.sh` in place, chained commands involving git shouldn't come
|
||||||
|
up in the first place — this hook is a backstop for the rare cases they do
|
||||||
|
anyway (or for other tools/skills in your setup that aren't as careful
|
||||||
|
about it).
|
||||||
|
|
||||||
|
## What's deliberately *not* included
|
||||||
|
|
||||||
|
- No `Bash(git:*)` — that would also permit `git reset --hard`, `git clean
|
||||||
|
-fdx`, `git filter-branch`, etc.
|
||||||
|
- No `Bash(cd:*)` — removed as a category, not just discouraged.
|
||||||
|
- No auto-approved writes to the config file — registering a repo is
|
||||||
|
always a visible, confirmed step.
|
||||||
|
- `push --force` is technically reachable (it's just `push` with extra
|
||||||
|
args) since it's a normal, if consequential, part of a git workflow —
|
||||||
|
it isn't specially gated beyond the normal `push` approval.
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
---
|
||||||
|
description: Set up the git-manager skill (permissions whitelist, optional anti-chaining hook)
|
||||||
|
argument-hint: [repo-name] [repo-path]
|
||||||
|
---
|
||||||
|
|
||||||
|
Set up the `git-manager` skill end to end. As part of the `git-manager`
|
||||||
|
plugin, this command is automatically namespaced by Claude Code and invoked
|
||||||
|
as `/git-manager:setup`. Follow these steps in order.
|
||||||
|
|
||||||
|
## 1. Determine the skill's install directory
|
||||||
|
|
||||||
|
This plugin's `skills/git-manager/` is a symlink into the repo's
|
||||||
|
tool-neutral `skills/git-manager/` directory (the single source of truth
|
||||||
|
for `SKILL.md` and `scripts/`, shared across all vendor adapters in this
|
||||||
|
repo). Resolve it to its real, absolute, symlink-free path:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
realpath <plugin-dir>/skills/git-manager
|
||||||
|
```
|
||||||
|
|
||||||
|
This resolved path is `<SKILL_DIR>` for the rest of this setup.
|
||||||
|
|
||||||
|
## 2. Optionally register named repos
|
||||||
|
|
||||||
|
If `$ARGUMENTS` contains a repo name and path, or the user wants to
|
||||||
|
pre-register repos now (e.g. an Obsidian vault repo used alongside a code
|
||||||
|
repo), write/merge them into
|
||||||
|
`~/.agent-skills/git-manager/config.json`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.agent-skills/git-manager
|
||||||
|
cat > ~/.agent-skills/git-manager/config.json << 'EOF'
|
||||||
|
{
|
||||||
|
"repos": [
|
||||||
|
{"name": "<short-name>", "path": "<absolute-repo-path>"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
If the file already exists, merge new entries in rather than overwriting.
|
||||||
|
This step is entirely optional — the skill works without any named repos,
|
||||||
|
operating on the current working directory by default.
|
||||||
|
|
||||||
|
## 3. Generate and merge the minimal permissions whitelist
|
||||||
|
|
||||||
|
Exactly one command needs whitelisting — everything else about this skill's
|
||||||
|
safety comes from the wrapper script's own subcommand allowlist and repo
|
||||||
|
resolution, not from a long list of Bash rules:
|
||||||
|
|
||||||
|
- `Bash(<SKILL_DIR>/scripts/git_cmd.sh:*)`
|
||||||
|
- `Read(~/.agent-skills/git-manager/config.json)`
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
1. 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 —
|
||||||
|
note that if they work across many separate git repos/projects, the
|
||||||
|
user-level file avoids repeating this setup per project.
|
||||||
|
2. If the target file doesn't exist yet, create it containing just these
|
||||||
|
two entries under `permissions.allow`.
|
||||||
|
3. If it exists, read it first and merge: add only entries not already
|
||||||
|
present. Don't duplicate, don't remove or overwrite unrelated existing
|
||||||
|
permissions.
|
||||||
|
4. Show the user exactly what was added before writing.
|
||||||
|
|
||||||
|
Do **not** whitelist a generic `Bash(git:*)` or anything targeting `cd`.
|
||||||
|
Do **not** whitelist writing to the config file — that keeps prompting for
|
||||||
|
confirmation, so registering a new repo is always a visible, confirmed
|
||||||
|
action.
|
||||||
|
|
||||||
|
## 4. Offer the anti-chaining hook
|
||||||
|
|
||||||
|
Explain to the user: this hook never silently blocks and never silently
|
||||||
|
allows anything. It only ever forces the *normal* confirmation prompt
|
||||||
|
(`permissionDecision: "ask"`) when a Bash command contains shell chaining
|
||||||
|
or substitution operators (`&&`, `;`, `|`, backticks, `$(...)`). This exists
|
||||||
|
because Claude Code's own Bash allow-list matching has had bugs where
|
||||||
|
compound commands either bypass per-command checks entirely, or where a
|
||||||
|
chain of individually-allowed commands still isn't recognized as such — see
|
||||||
|
`hooks/force-ask-on-chaining.sh` for the reasoning and a link to the
|
||||||
|
relevant upstream issue. With this hook installed, if a chained command
|
||||||
|
ever gets constructed anyway (it shouldn't, since `git_cmd.sh` removes the
|
||||||
|
need for `cd`/chaining), the user will always see a normal approval prompt
|
||||||
|
for it rather than it silently going through or silently failing.
|
||||||
|
|
||||||
|
Ask the user whether to install it. If yes:
|
||||||
|
|
||||||
|
1. Locate `hooks/force-ask-on-chaining.sh` next to this plugin's
|
||||||
|
`plugin.json` and note its absolute path, `<HOOK_PATH>`.
|
||||||
|
2. Ensure it's executable: `chmod +x <HOOK_PATH>`.
|
||||||
|
3. Merge this into the same settings file chosen in step 3 (project or
|
||||||
|
user-level — ask if unclear, but note this hook is most useful applied
|
||||||
|
broadly at the user level, since it's about Bash hygiene in general, not
|
||||||
|
specific to git):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Bash",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "<HOOK_PATH>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Merge into any existing `hooks.PreToolUse` array rather than overwriting it
|
||||||
|
— don't remove other existing PreToolUse hooks for the `Bash` matcher or
|
||||||
|
other matchers.
|
||||||
|
|
||||||
|
If the user declines, skip this step entirely; the whitelist from step 3
|
||||||
|
still works on its own, it just doesn't have this extra safety net.
|
||||||
|
|
||||||
|
## 5. Make the wrapper script executable
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x <SKILL_DIR>/scripts/git_cmd.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Confirm and offer a test run
|
||||||
|
|
||||||
|
Summarize to the user:
|
||||||
|
- Any repos registered in step 2
|
||||||
|
- The exact permission entries added, and where
|
||||||
|
- Whether the anti-chaining hook was installed, and where
|
||||||
|
|
||||||
|
Then offer to run `<SKILL_DIR>/scripts/git_cmd.sh status` (against the
|
||||||
|
current directory, or a registered repo) as a quick sanity check.
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# PreToolUse hook for the Bash tool. Does NOT silently block anything —
|
||||||
|
# per design, it only ever forces the normal confirmation prompt
|
||||||
|
# (permissionDecision: "ask") when a Bash command contains shell chaining
|
||||||
|
# operators. It never returns "deny" and never returns "allow" itself; it
|
||||||
|
# either stays out of the way (no JSON output) or asks. This exists as a
|
||||||
|
# safety net against Claude Code's Bash allow-list not always splitting
|
||||||
|
# compound commands the way the docs describe (see e.g.
|
||||||
|
# https://github.com/anthropics/claude-code/issues/20085), so a chained
|
||||||
|
# command can't slip through on a whitelisted prefix without the user
|
||||||
|
# seeing it.
|
||||||
|
#
|
||||||
|
# Register in settings.json under hooks.PreToolUse with a matcher of "Bash".
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
INPUT="$(cat)"
|
||||||
|
|
||||||
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
|
# No jq available — fail open (no output = no opinion), rather than
|
||||||
|
# breaking every Bash call because a dependency is missing.
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
TOOL_NAME="$(echo "$INPUT" | jq -r '.tool_name // empty')"
|
||||||
|
if [ "$TOOL_NAME" != "Bash" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
CMD="$(echo "$INPUT" | jq -r '.tool_input.command // empty')"
|
||||||
|
|
||||||
|
# Look for shell chaining/substitution operators anywhere in the command.
|
||||||
|
# Deliberately broad (better a false positive prompt than a missed chain):
|
||||||
|
# && || ; | backticks $( )
|
||||||
|
if echo "$CMD" | grep -qE '[;&|`]|\$\('; then
|
||||||
|
jq -n \
|
||||||
|
--arg reason "Command appears to chain multiple shell commands (&&, ;, |, or command substitution). Forcing a normal confirmation prompt instead of relying on the allow-list, since compound commands can bypass per-command whitelisting." \
|
||||||
|
'{
|
||||||
|
hookSpecificOutput: {
|
||||||
|
hookEventName: "PreToolUse",
|
||||||
|
permissionDecision: "ask",
|
||||||
|
permissionDecisionReason: $reason
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"_comment": "Template for the git-manager skill's Claude Code permissions. <SKILL_DIR> must be replaced with the absolute, symlink-resolved path of skills/git-manager (see commands/setup.md step 1) before merging into settings.json. Exactly one Bash rule is whitelisted — the wrapper script itself — never a generic Bash(git:*) or anything targeting cd. The wrapper script enforces its own subcommand allowlist (status, log, diff, show, fetch, remote, branch, checkout, add, commit, push, pull) and resolves repo paths only from ~/.agent-skills/git-manager/config.json or the current working directory, never from an arbitrary caller-supplied path.",
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(<SKILL_DIR>/scripts/git_cmd.sh:*)",
|
||||||
|
"Read(~/.agent-skills/git-manager/config.json)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"_deliberately_not_whitelisted": [
|
||||||
|
"No generic Bash(git:*) — that would allow any git subcommand, including destructive ones like reset --hard, clean -fdx, or filter-branch, which are not on the wrapper's allowlist for a reason.",
|
||||||
|
"No Bash(cd:*) — the wrapper's --repo flag and its default-to-$PWD behavior remove any legitimate need for cd, and cd:* is a known vector for whitelist bypass via chaining (e.g. cd X && anything).",
|
||||||
|
"No write access to the config file — registering a new named repo stays a confirmed action every time."
|
||||||
|
]
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
../../../../../skills/git-manager
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
# obsidian-vault-kb
|
||||||
|
|
||||||
|
Turns an Obsidian vault — a folder of Markdown notes with frontmatter,
|
||||||
|
`#tags`, and `[[wikilinks]]` — into a knowledge base Claude can search and
|
||||||
|
reason over. Works with any vault, on any topic: homelab documentation,
|
||||||
|
project notes, research, whatever you keep in Obsidian.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
- **Reads your vault's own structure** rather than imposing one: your
|
||||||
|
folders become topical categories, your tags and frontmatter become
|
||||||
|
filters, your wikilinks let Claude follow related notes (backlinks) the
|
||||||
|
same way you would clicking through the graph.
|
||||||
|
- **Answers grounded in what's actually in your notes.** If the vault
|
||||||
|
doesn't have an answer, Claude says so instead of guessing, and keeps
|
||||||
|
general knowledge clearly separate from what came from your vault.
|
||||||
|
- **Optionally writes back**, depending on the mode you choose at setup —
|
||||||
|
from strictly read-only, to appending new dated notes, to actively
|
||||||
|
updating existing ones.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
After installing the plugin:
|
||||||
|
|
||||||
|
```
|
||||||
|
/obsidian-vault-kb:setup
|
||||||
|
```
|
||||||
|
|
||||||
|
This will ask you for:
|
||||||
|
|
||||||
|
1. **The vault path(s).** You can register more than one vault (e.g. a
|
||||||
|
personal one and a work one) and refer to them by name.
|
||||||
|
2. **A mode:**
|
||||||
|
- `read-only` — Claude can search and read, never write anything.
|
||||||
|
- `append` — Claude can also file new findings as new, dated notes.
|
||||||
|
Existing notes are never touched.
|
||||||
|
- `maintain` — Claude can also update existing notes directly (e.g. add
|
||||||
|
a dated "Update" section, change a status field).
|
||||||
|
|
||||||
|
It also whitelists three narrow, read-only helper scripts (see below) so
|
||||||
|
searching your vault doesn't require a permission prompt on every single
|
||||||
|
`find`/`grep` call.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
You don't need to invoke anything explicitly — just ask Claude things like:
|
||||||
|
|
||||||
|
- "What did I write about the Ceph cluster rebuild?"
|
||||||
|
- "Check my notes for how I set up mTLS on Postfix."
|
||||||
|
- "Any open items in my homelab notes related to DNS?"
|
||||||
|
|
||||||
|
Claude recognizes references to "my notes", "my vault", "my docs", etc.
|
||||||
|
and searches automatically. Under the hood it uses three scripts:
|
||||||
|
|
||||||
|
- `vault_index.sh` — folder structure, note counts, tags, index/MOC files
|
||||||
|
- `vault_search.sh "<query>"` — full-text search, optionally scoped to a
|
||||||
|
subfolder
|
||||||
|
- `vault_backlinks.sh "<note-name>"` — notes linking to a given note
|
||||||
|
|
||||||
|
All three resolve the vault path only from
|
||||||
|
`~/.agent-skills/obsidian-vault-kb/config.json` — they can't be pointed at
|
||||||
|
a different directory than what you configured, which is what makes it
|
||||||
|
safe to whitelist them without granting broader filesystem access.
|
||||||
|
|
||||||
|
## Writing to your vault
|
||||||
|
|
||||||
|
Only relevant in `append` or `maintain` mode:
|
||||||
|
|
||||||
|
- **`append`** — new findings are filed as new notes (e.g.
|
||||||
|
`Notes/2026-08-04-dns-fix.md`), with frontmatter and wikilinks to related
|
||||||
|
topics. Existing files are never modified.
|
||||||
|
- **`maintain`** — Claude may extend an existing note directly instead of
|
||||||
|
creating a new one, when that's the more natural place for the
|
||||||
|
information. You'll always see a summary of what's about to be written
|
||||||
|
before it happens.
|
||||||
|
- Writing into the vault is never in the permissions whitelist, regardless
|
||||||
|
of mode — every write stays a normal, visible, confirmable action.
|
||||||
|
|
||||||
|
## Multiple vaults
|
||||||
|
|
||||||
|
Registered vaults live in `~/.agent-skills/obsidian-vault-kb/config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"vaults": [
|
||||||
|
{"name": "homelab", "path": "/absolute/path/to/homelab-vault"},
|
||||||
|
{"name": "worldbuilding", "path": "/absolute/path/to/tangier-vault"}
|
||||||
|
],
|
||||||
|
"mode": "append"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you only ever use one vault, you don't need to name it when asking
|
||||||
|
Claude to search — it's used by default.
|
||||||
|
|
||||||
|
## What's deliberately *not* included
|
||||||
|
|
||||||
|
- No generic `Bash(find:*)`, `Bash(rg:*)`, `Bash(grep:*)`, or `Bash(cat:*)`
|
||||||
|
— those would allow reading anywhere on the filesystem, not just your
|
||||||
|
vault.
|
||||||
|
- No auto-approved writes — not to notes, not to the config file.
|
||||||
|
- Binary attachments (PDFs, images) inside the vault aren't read
|
||||||
|
automatically, only referenced, unless you ask for them specifically.
|
||||||
Reference in New Issue
Block a user