Fixed setups
This commit is contained in:
+42
-22
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user