diff --git a/CHANGELOG.md b/CHANGELOG.md
index 33b760e..a0502c8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## [v0.6.4] - 2026-05-21
+
+### Features
+
+- `repo workspace-push
[-b BRANCH] [-m MSG]` — closes the agent edit-and-PR loop. Optionally checks out / creates a branch, optionally stages + commits any uncommitted changes (when `-m` is given), then `git push -u origin HEAD`. Same `/tmp/fledge-gh-*` safety guard as the other workspace commands. In `--json` mode emits `{branch, repo, path}` so the agent can chain straight into `prs create -R … -H …`.
+- `prs create --head ` — wires gh's `--head` through so an agent can create a PR for a branch that lives in a different working tree (the typical post-`workspace-push` case where the cwd isn't the cloned repo).
+
## [v0.6.3] - 2026-05-21
### Features
diff --git a/bin/fledge-github-prs b/bin/fledge-github-prs
index 450cadd..c1fd54a 100755
--- a/bin/fledge-github-prs
+++ b/bin/fledge-github-prs
@@ -21,6 +21,7 @@ INCLUDE_COMMENTS=false
MERGE_METHOD="squash"
REVIEW_KIND=""
REPO=""
+HEAD_BRANCH=""
# When -R/--repo is set, every gh sub-call gets `--repo $REPO`
# appended via REPO_FLAGS. Empty when -R isn't set, so the gh CLI
@@ -76,6 +77,7 @@ while [ $# -gt 0 ]; do
--request-changes) REVIEW_KIND="request-changes"; shift;;
--comment-review) REVIEW_KIND="comment"; shift;;
-R|--repo) REPO="${2:-}"; shift 2;;
+ -H|--head) HEAD_BRANCH="${2:-}"; shift 2;;
-h|--help)
cat <<'EOF'
fledge github prs — manage GitHub pull requests via the gh CLI
@@ -101,6 +103,8 @@ CREATE OPTIONS:
-t, --title PR title
-b, --body PR body
--base Base branch (default: repo default)
+ -H, --head Head branch to PR from (default: current local branch).
+ Required when -R targets a repo that isn't your cwd.
--draft Open as draft PR
--fill Infer title/body from commits
--ai Generate title and body using AI (fledge ask)
@@ -387,10 +391,11 @@ Output only the title and body. No preamble, no meta-commentary."
# Build gh pr create argument list
ARGS=()
- [ -n "$REPO" ] && ARGS+=(--repo "$REPO")
- [ -n "$TITLE" ] && ARGS+=(--title "$TITLE")
- [ -n "$BODY" ] && ARGS+=(--body "$BODY")
- [ -n "$BASE" ] && ARGS+=(--base "$BASE")
+ [ -n "$REPO" ] && ARGS+=(--repo "$REPO")
+ [ -n "$TITLE" ] && ARGS+=(--title "$TITLE")
+ [ -n "$BODY" ] && ARGS+=(--body "$BODY")
+ [ -n "$BASE" ] && ARGS+=(--base "$BASE")
+ [ -n "$HEAD_BRANCH" ] && ARGS+=(--head "$HEAD_BRANCH")
[ "$DRAFT" = "true" ] && ARGS+=(--draft)
[ "$FILL" = "true" ] && ARGS+=(--fill)
if [ "$JSON" = "true" ]; then
diff --git a/bin/fledge-github-repo b/bin/fledge-github-repo
index c876562..3bccb1d 100755
--- a/bin/fledge-github-repo
+++ b/bin/fledge-github-repo
@@ -70,15 +70,30 @@ if [ $# -gt 0 ] && [ "${1:0:1}" != "-" ]; then
shift
fi
;;
+ workspace-push)
+ ACTION="workspace-push"
+ shift
+ if [ $# -gt 0 ] && [ "${1:0:1}" != "-" ]; then
+ CLONE_DIR="$1"
+ shift
+ fi
+ ;;
esac
fi
+# Workspace-push specific args. Reusing -b/--branch and -m/--message;
+# parsed alongside the existing options below via a small extension.
+WORKSPACE_BRANCH=""
+WORKSPACE_MESSAGE=""
+
while [ $# -gt 0 ]; do
case "$1" in
- -R|--repo) REPO="${2:-}"; shift 2;;
- -r|--ref) REF="${2:-}"; shift 2;;
- --depth) DEPTH="${2:-}"; shift 2;;
- --json) JSON=true; shift;;
+ -R|--repo) REPO="${2:-}"; shift 2;;
+ -r|--ref) REF="${2:-}"; shift 2;;
+ -b|--branch) WORKSPACE_BRANCH="${2:-}"; shift 2;;
+ -m|--message) WORKSPACE_MESSAGE="${2:-}"; shift 2;;
+ --depth) DEPTH="${2:-}"; shift 2;;
+ --json) JSON=true; shift;;
-h|--help)
cat <<'EOF'
fledge github repo — read repo metadata and file contents via the gh CLI
@@ -88,12 +103,15 @@ USAGE:
fledge github repo file Read a file OR list a directory
fledge github repo clone [DIR] Clone a repo via `gh repo clone`
fledge github repo workspace Clone into a fresh /tmp dir, print path
+ fledge github repo workspace-push Commit (if -m) + push the workspace branch
fledge github repo workspace-clean rm -rf a workspace dir (safety-checked)
OPTIONS:
-R, --repo Target repo (default: current working repo).
view + clone + workspace also accept it as a positional.
-r, --ref [ Branch / tag / SHA (file: lookup; workspace: checkout)
+ -b, --branch (workspace-push) target branch — checks out / creates it
+ -m, --message (workspace-push) commit message; omit to push existing commits only
--depth Shallow-clone depth (clone + workspace)
--json Output JSON
@@ -121,6 +139,7 @@ EXAMPLES:
fledge github repo clone CorvidLabs/corvid-verify /tmp/cv --depth 1
fledge github repo workspace CorvidLabs/corvid-verify
fledge github repo workspace CorvidLabs/corvid-verify -r feat/jwks-endpoint --depth 1
+ fledge github repo workspace-push /tmp/fledge-gh-... -b fix/kid -m "Add kid claim"
fledge github repo workspace-clean /tmp/fledge-gh-CorvidLabs-corvid-verify-abc123
EOF
exit 0
@@ -210,6 +229,84 @@ if [ "$ACTION" = "workspace" ]; then
exit 0
fi
+if [ "$ACTION" = "workspace-push" ]; then
+ if [ -z "$CLONE_DIR" ]; then
+ echo "fledge github repo workspace-push: missing DIR. Try: fledge github repo workspace-push /tmp/fledge-gh-XXX -b feat/fix -m \"msg\"" >&2
+ exit 64
+ fi
+ # Same safety guard as workspace-clean: only operate on dirs we
+ # ourselves created, so an agent reasoning bug can't shove arbitrary
+ # local changes upstream via this command.
+ case "$CLONE_DIR" in
+ /tmp/fledge-gh-*) ;;
+ *)
+ echo "fledge github repo workspace-push: refusing to push from '$CLONE_DIR' (must be a /tmp/fledge-gh-* workspace)" >&2
+ exit 64
+ ;;
+ esac
+ if [ ! -d "$CLONE_DIR/.git" ]; then
+ echo "fledge github repo workspace-push: '$CLONE_DIR' isn't a git workspace" >&2
+ exit 1
+ fi
+
+ # If a target branch was given, switch to it (creating from HEAD when
+ # it doesn't yet exist). Without -b we just use whatever branch is
+ # currently checked out — convenient when the agent passed -r to
+ # `workspace` to land on a feature branch directly.
+ if [ -n "$WORKSPACE_BRANCH" ]; then
+ if git -C "$CLONE_DIR" rev-parse --verify "$WORKSPACE_BRANCH" >/dev/null 2>&1; then
+ git -C "$CLONE_DIR" checkout "$WORKSPACE_BRANCH" >&2
+ else
+ git -C "$CLONE_DIR" checkout -b "$WORKSPACE_BRANCH" >&2
+ fi
+ fi
+
+ CURRENT_BRANCH="$(git -C "$CLONE_DIR" rev-parse --abbrev-ref HEAD)"
+ if [ "$CURRENT_BRANCH" = "HEAD" ]; then
+ echo "fledge github repo workspace-push: detached HEAD; pass -b BRANCH to create one" >&2
+ exit 1
+ fi
+
+ # Stage + commit any uncommitted changes when --message is given.
+ # If the working tree is already clean OR no message was provided,
+ # skip the commit step and just push whatever's there. This lets
+ # the agent use the same command for two cases: (1) it edited files
+ # and wants to commit + push in one go, or (2) it ran git-commit
+ # via the git plugin already and just needs the push.
+ if [ -n "$WORKSPACE_MESSAGE" ]; then
+ # Stage everything. `git add -A` mirrors the typical CLI commit
+ # flow; the agent doesn't need to enumerate paths.
+ git -C "$CLONE_DIR" add -A >&2
+ # Only commit if there's actually something staged — re-running
+ # workspace-push on a clean tree should still push, not error.
+ if ! git -C "$CLONE_DIR" diff --cached --quiet; then
+ git -C "$CLONE_DIR" commit -m "$WORKSPACE_MESSAGE" >&2
+ fi
+ fi
+
+ # Push the current branch upstream. `-u` sets the tracking branch so
+ # subsequent pushes are idempotent. Push stdout/stderr to stderr so
+ # our final JSON / branch-name output isn't muddied.
+ if ! git -C "$CLONE_DIR" push -u origin "$CURRENT_BRANCH" >&2; then
+ echo "fledge github repo workspace-push: git push failed" >&2
+ exit 1
+ fi
+
+ # Detect the repo's owner/name from the cloned remote so the agent
+ # can chain into `prs create -R OWNER/NAME -H BRANCH` without having
+ # to remember what it cloned.
+ ORIGIN_URL="$(git -C "$CLONE_DIR" remote get-url origin 2>/dev/null || true)"
+ # Strip https:// or git@github.com: prefix and a trailing .git.
+ REMOTE_REPO="$(printf '%s' "$ORIGIN_URL" | sed -E 's#(https?://[^/]+/|git@[^:]+:)##; s#\.git$##')"
+
+ if [ "$JSON" = "true" ]; then
+ printf '{"branch":"%s","repo":"%s","path":"%s"}\n' "$CURRENT_BRANCH" "$REMOTE_REPO" "$CLONE_DIR"
+ else
+ printf 'Pushed branch %s to %s\n' "$CURRENT_BRANCH" "$REMOTE_REPO"
+ fi
+ exit 0
+fi
+
if [ "$ACTION" = "workspace-clean" ]; then
if [ -z "$CLONE_DIR" ]; then
echo "fledge github repo workspace-clean: missing DIR. Try: fledge github repo workspace-clean /tmp/fledge-gh-XXXX" >&2
diff --git a/plugin.toml b/plugin.toml
index a0bc7d9..121eaae 100644
--- a/plugin.toml
+++ b/plugin.toml
@@ -1,14 +1,14 @@
[plugin]
name = "fledge-plugin-github"
-version = "0.6.3"
+version = "0.6.4"
description = "GitHub commands for fledge — list/view/create/comment/review/merge PRs, list/view/create/comment/close issues, read repo files, view CI checks, and poll for daemon events via the gh CLI"
author = "0xLeif"
license = "MIT"
[[commands]]
name = "github"
-description = "GitHub commands via the gh CLI. All subcommands accept -R OWNER/NAME to target any repo (the agent's working dir is otherwise used). Subcommands: prs (list/view [--diff --comments]/create/comment -b BODY/review --approve|--request-changes|--comment-review -b BODY/merge /close /reopen ), issues (list/view [--comments]/create/comment -b BODY/close /reopen ), repo (view OWNER/NAME / file [--ref REF] (handles files and dirs) / clone OWNER/NAME [DIR] / workspace OWNER/NAME [--ref REF] [--depth N] (clones into /tmp/fledge-gh-* and prints the path — preferred for agents needing a local edit sandbox) / workspace-clean DIR), checks (CI status), poll (daemon event polling)."
+description = "GitHub commands via the gh CLI. All subcommands accept -R OWNER/NAME to target any repo (the agent's working dir is otherwise used). Subcommands: prs (list/view [--diff --comments]/create [-H HEAD_BRANCH]/comment -b BODY/review --approve|--request-changes|--comment-review -b BODY/merge /close /reopen ), issues (list/view [--comments]/create/comment -b BODY/close /reopen ), repo (view OWNER/NAME / file [--ref REF] (handles files and dirs) / clone OWNER/NAME [DIR] / workspace OWNER/NAME [--ref REF] [--depth N] (clones into /tmp/fledge-gh-* and prints the path — preferred for agents needing a local edit sandbox) / workspace-push DIR [-b BRANCH] [-m MSG] (commits + pushes the workspace branch — chain into 'prs create -R … -H …') / workspace-clean DIR), checks (CI status), poll (daemon event polling)."
binary = "bin/fledge-github"
args = [
- { name = "args", type = "string", required = true, description = "Subcommand and options. Pass -R OWNER/NAME for cross-repo lookups. For agent edit sandboxes use 'repo workspace OWNER/NAME [-r BRANCH] [--depth 1]' — it returns a /tmp path; clean up with 'repo workspace-clean '. Examples: 'prs view 5 -R CorvidLabs/corvid-verify --diff', 'repo file Sources -R CorvidLabs/corvid-verify --ref feat/x', 'repo workspace CorvidLabs/corvid-verify -r feat/jwks-endpoint --depth 1'" },
+ { name = "args", type = "string", required = true, description = "Subcommand and options. Edit-and-PR loop for another repo: 1) 'repo workspace OWNER/NAME [-r BRANCH] [--depth 1]' returns a /tmp path, 2) edit files there with files-edit/files-write, 3) 'repo workspace-push -b feat/x -m \"commit msg\"' commits + pushes, 4) 'prs create -R OWNER/NAME -H feat/x -t \"…\" -b \"…\"' opens the PR, 5) 'repo workspace-clean ' tidies /tmp." },
]
]