Fixed setups

This commit is contained in:
Henner M. Kruse
2026-08-04 11:15:24 +02:00
parent e485d33f00
commit 6c6fc35a1f
9 changed files with 609 additions and 288 deletions
+42 -22
View File
@@ -5,8 +5,10 @@ description: Manages git operations (status, log, diff, show, fetch, remote, bra
# Git Manager
This skill wraps all git operations in a single script,
`scripts/git_cmd.sh`, so that:
This skill wraps all git operations in a single script, permanently
installed at `~/.agent-skills/git-manager/bin/git_cmd.sh` by this plugin's
setup (not run from wherever this skill's own files happen to live — see
"Setup and permissions" below for why), so that:
- There is never a need for `cd <path> && git ...` to operate on a
non-default repository — pass `--repo <name>` instead.
@@ -26,28 +28,44 @@ back to prompts (or, worse, slipping through on a stale broad rule).
Always use:
```bash
<skill-dir>/scripts/git_cmd.sh <subcommand> [--repo <name>] [-- <git-args...>]
~/.agent-skills/git-manager/bin/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.
- `--repo <name>` targets a specific pre-configured repository. The script
itself (not this skill's own logic) decides what happens if it's
omitted: with exactly one repo registered in
`~/.agent-skills/git-manager/config.json`, that one is used
automatically; with more than one, the script errors and lists the
registered names so you can retry with `--repo`; with none registered,
it errors saying to register one first. The current working directory
is never consulted for this — whether it happens to be a git repository
itself is not a signal this skill acts on. If the task names a repo
explicitly, pass `--repo <name>` for that one regardless of how many are
registered.
- Everything after `--` is passed through to `git` literally (e.g.
`-- -m "commit message"`, `-- --oneline -10`, `-- origin main`).
- Don't pre-check whether `~/.agent-skills/git-manager/bin/git_cmd.sh`
exists with a separate command (e.g. `test -x ... && ...`) before
calling it — that's exactly the kind of chained command this skill exists
to avoid, and it'll trigger the anti-chaining hook if installed. Just
invoke it directly. If it fails with "No such file or directory" (or
similar), that means setup hasn't been run for this tool yet — tell the
user to run it first (for Claude Code: `/git-manager:setup`) rather than
trying to invoke the script from wherever this skill's own files happen
to be mounted (a plugin cache, a symlink target, etc.) — that location
isn't guaranteed stable and isn't what gets whitelisted.
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
~/.agent-skills/git-manager/bin/git_cmd.sh status
~/.agent-skills/git-manager/bin/git_cmd.sh log -- --oneline -10
~/.agent-skills/git-manager/bin/git_cmd.sh add -- -A
~/.agent-skills/git-manager/bin/git_cmd.sh commit -- -m "Update DNS notes"
~/.agent-skills/git-manager/bin/git_cmd.sh push --repo homelab-notes -- origin main
```
Never call `git` directly, and never use `cd` to switch into a different
@@ -62,11 +80,13 @@ purpose as a guardrail, not an oversight.
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
```
ask the user for its absolute path and add it. Use the Read/Write file
tools for this, not `cat`/Bash — the whitelisted permission for this file
is `Read(~/.agent-skills/git-manager/config.json)`, which covers the Read
file tool, not a Bash `cat` invocation of the same path (those are
separate permission checks in Claude Code, even though they access the
same file). Read the file (it may not exist yet, that's fine — treat that
as no repos registered):
```json
{
@@ -77,9 +97,9 @@ cat ~/.agent-skills/git-manager/config.json 2>/dev/null
}
```
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.
Merge new entries in rather than overwriting existing ones. At least one
repo must be registered for this skill to do anything there is no
current-working-directory fallback.
## Setup and permissions
@@ -89,7 +109,7 @@ 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
`Bash(~/.agent-skills/git-manager/bin/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
+36 -24
View File
@@ -17,41 +17,53 @@ is_allowed_subcommand() {
return 1
}
# Resolve a repo's absolute path.
# Resolve a repo's absolute path. Only ever looks at configured repos —
# never falls back to the current working directory, regardless of
# whether it happens to be a git repo itself. This is deliberate: the
# current directory being a git repo is not a signal this script acts on.
# - 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).
# - If no name is given, look at the config: exactly one registered repo
# is used automatically; zero or more than one is an error requiring an
# explicit --repo.
# Never accepts a raw path from the caller directly — only ever a name
# resolved through the config file, so the whitelisted script can't be
# pointed at an arbitrary directory outside what's configured.
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'
if [ ! -f "$CONFIG_FILE" ]; then
echo "Error: no configuration found at $CONFIG_FILE. Register at least one repo first (run setup, or ask to register one)." >&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")
if repo_name:
match = [r for r in repos if r.get("name") == repo_name]
if not match:
names = ", ".join(r.get("name", "?") for r in repos) or "(none)"
sys.stderr.write(f"Error: no repo named '{repo_name}' in config. Registered: {names}\n")
sys.exit(1)
print(match[0]["path"])
elif len(repos) == 1:
print(repos[0]["path"])
elif len(repos) == 0:
sys.stderr.write("Error: no repos registered in config. Register at least one first.\n")
sys.exit(1)
else:
names = ", ".join(r.get("name", "?") for r in repos)
sys.stderr.write(f"Error: multiple repos configured ({names}); specify one with --repo <name>.\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
+25 -15
View File
@@ -16,11 +16,12 @@ research or study notes).
### 1. Check configuration (always first)
Configuration lives in `~/.agent-skills/obsidian-vault-kb/config.json`. Before any
substantive action, check whether this file exists:
```bash
cat ~/.agent-skills/obsidian-vault-kb/config.json 2>/dev/null
```
substantive action, check whether this file exists using the Read file
tool, not `cat`/Bash — the whitelisted permission for this file is
`Read(~/.agent-skills/obsidian-vault-kb/config.json)`, which covers the
Read file tool, not a Bash `cat` invocation of the same path (those are
separate permission checks in Claude Code, even though they access the
same file).
**If the file exists:** Use the configuration from it (vault path(s), mode)
and go straight to step 2 (search). Do not ask again.
@@ -65,26 +66,35 @@ especially when using tags/frontmatter/wikilinks.
Short version of the approach:
1. Get an overview of the vault structure:
`<skill-dir>/scripts/vault_index.sh [vault-name]` — returns folders as
`~/.agent-skills/obsidian-vault-kb/bin/vault_index.sh [vault-name]` — returns folders as
rough categories, note counts, existing frontmatter tags, and possible
index/MOC files.
2. Targeted full-text search with
`<skill-dir>/scripts/vault_search.sh "<query>" [vault-name] [subfolder]`
`~/.agent-skills/obsidian-vault-kb/bin/vault_search.sh "<query>" [vault-name] [subfolder]`
— scoped to the most plausible subfolder from step 1 where useful.
3. For a specific note, follow backlinks with
`<skill-dir>/scripts/vault_backlinks.sh "<note-name>" [vault-name]` to
`~/.agent-skills/obsidian-vault-kb/bin/vault_backlinks.sh "<note-name>" [vault-name]` to
find related notes that link to it.
4. Open matching files (via the `view`/read tool, not a shell command) to
read their frontmatter (tags, `status`, `aliases`) and content in full
once a promising candidate has been found.
These three scripts always resolve the vault path from
`~/.agent-skills/obsidian-vault-kb/config.json` themselves and refuse to operate
outside the configured vault — they never take a raw filesystem path as an
argument. This is intentional: it's what allows them (and only them) to be
safely whitelisted in Claude Code's permissions without granting broader
shell/filesystem access. See "Setup and permissions" below and
`references/search-strategy.md` for manual fallback commands (`find`, `rg`,
These three scripts (permanently installed at
`~/.agent-skills/obsidian-vault-kb/bin/` by this plugin's setup, not run
from wherever this skill's own files happen to live) always resolve the
vault path from `~/.agent-skills/obsidian-vault-kb/config.json` themselves
and refuse to operate outside the configured vault — they never take a raw
filesystem path as an argument. This is intentional: it's what allows them
(and only them) to be safely whitelisted in Claude Code's permissions
without granting broader shell/filesystem access. Don't pre-check whether
`~/.agent-skills/obsidian-vault-kb/bin/` exists with a separate command
(e.g. `test -d ... && ...`) before calling one of these scripts — that's
exactly the kind of chained command this design exists to avoid, and it'll
trigger the anti-chaining hook if installed. Just invoke the script
directly. If it fails with "No such file or directory" (or similar), setup
hasn't been run for this tool yet — tell the user to run it first (for
Claude Code: `/obsidian-vault-kb:setup`). See "Setup and permissions" below
and `references/search-strategy.md` for manual fallback commands (`find`, `rg`,
`grep`) for cases the scripts don't cover — those are not whitelisted and
will prompt for confirmation, by design.