Added git-manager, updated READMEs
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user