Skip to content
5 changes: 3 additions & 2 deletions AGENTS-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ ghsudo gh issue comment 42 --body "Done!"

## Rules

- **Always** use `https://` URLs for git remotes so that `git push`/`pull` can use the `gh` credential helper. `ghsudo` itself injects `GH_TOKEN`/`GITHUB_TOKEN` for the `gh` CLI and does not depend on the remote URL scheme. If git push/pull fails over HTTPS, remind the user to run `gh auth setup-git`.
- **Never** bypass `ghsudo` or ask the user for the write token directly.
- Exit code 2 (denied): stop and report to the user.
- Exit code 3 (no interactive session): inform the user approval is not possible.
- Exit code 2 (denied or timed out): stop and report to the user.
- Exit code 3 (no GUI display or no supported dialog tool): inform the user approval is not possible.
- Exit code 4 (not set up): tell the user to follow <https://github.com/lklimek/ghsudo#readme>.
5 changes: 3 additions & 2 deletions CLAUDE-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ ghsudo gh issue comment 42 --body "Done!"

## Rules

- **Always** use `https://` URLs for git remotes so that `git push`/`pull` can use the `gh` credential helper. `ghsudo` itself injects `GH_TOKEN`/`GITHUB_TOKEN` for the `gh` CLI and does not depend on the remote URL scheme. If git push/pull fails over HTTPS, remind the user to run `gh auth setup-git`.
- **Never** bypass `ghsudo` or ask the user for the write token directly.
- Exit code 2 (denied): stop and report to the user.
- Exit code 3 (no interactive session): inform the user approval is not possible.
- Exit code 2 (denied or timed out): stop and report to the user.
- Exit code 3 (no GUI display or no supported dialog tool): inform the user approval is not possible.
- Exit code 4 (not set up): tell the user to follow <https://github.com/lklimek/ghsudo#readme>.
69 changes: 69 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# GitHub access — two-token model

You have a **read-only** `GH_TOKEN`. For write operations, use `ghsudo`:

```
ghsudo gh pr merge 123 --merge
ghsudo gh issue comment 42 --body "Done!"
```

`ghsudo` shows a dialog and runs the command with elevated permissions only after user approval.

## Rules

- **Always** use `https://` URLs for git remotes so that `git push`/`pull` can use the `gh` credential helper. `ghsudo` itself injects `GH_TOKEN`/`GITHUB_TOKEN` for the `gh` CLI and does not depend on the remote URL scheme. If git push/pull fails over HTTPS, remind the user to run `gh auth setup-git`.
- **Never** bypass `ghsudo` or ask the user for the write token directly.
- Exit code 2 (denied or timed out): stop and report to the user.
- Exit code 3 (no GUI display or no supported dialog tool): inform the user approval is not possible.
- Exit code 4 (not set up): tell the user to follow <https://github.com/lklimek/ghsudo#readme>.

# ghsudo — Developer Guide

## Project structure

- `src/ghsudo/__main__.py` — CLI entry point and all logic (single-file tool)
- `src/ghsudo/__init__.py` — package init, reads version from package metadata
- `pyproject.toml` — build config and **single source of truth for version**
- `tests/` — pytest tests

## Version management

Version is defined **only** in `pyproject.toml`. The `__init__.py` reads it at
runtime via `importlib.metadata.version("ghsudo")`.

When bumping the version, edit **only** `pyproject.toml`.

## Testing

```bash
python -m pytest tests/ -v
```

Only run formatting/linting right before committing:

```bash
python -m ruff check src/ tests/ && python -m ruff format src/ tests/
```

## Release process

1. **Bump version** in `pyproject.toml` (semver: major.minor.patch)
2. **Commit**: `git commit -m "build: bump version to X.Y.Z"`
3. **Push**: `git push`
4. **Create GitHub release** (triggers PyPI publish via CI):
```bash
ghsudo gh release create vX.Y.Z --generate-notes
```
5. **Verify** the publish workflow completes on GitHub Actions.

The `publish.yml` workflow builds and publishes to PyPI automatically when a
GitHub release is created.

## Platform support

Only Linux is actively tested. macOS/Windows have basic support but are untested.

## Security design

GUI-only approval is intentional — terminal prompts are trivially auto-approvable
by AI agents. A graphical display (`DISPLAY`/`WAYLAND_DISPLAY`) is required.
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The naive solutions both have drawbacks:
2. **Write token** — stored encrypted on your machine. When the agent needs to perform a write operation (a `gh` command that would otherwise fail with HTTP 403), it calls `ghsudo` instead.

`ghsudo` then:
- Shows you a **GUI popup** (or terminal prompt) listing the exact command to be executed.
- Shows you a **GUI popup** listing the exact command to be executed.
- **Waits for your explicit approval** before proceeding.
- If approved, re-runs the command with the elevated write token injected into the environment.
- If denied (or timed out after 60 s), exits with a non-zero code so the agent knows it was blocked.
Expand All @@ -42,8 +42,16 @@ cd ghsudo
pip install .
```

> **Note:** For `git push`/`pull` to work with `ghsudo`'s elevated token, your remotes need `https://` URLs (not SSH). `ghsudo` injects `GH_TOKEN`/`GITHUB_TOKEN` which the `gh` credential helper uses for HTTPS Git operations. (`ghsudo gh ...` commands work regardless of remote URL scheme.)
> To configure `gh` as the Git credential helper, run:
> ```bash
> gh auth setup-git
> ```

**Requirement:** Python 3.10+

> **Note:** Only **Linux** is actively tested. macOS and Windows have basic support (GUI dialogs, path handling) but are **not tested** — contributions welcome.

## Quick Start

```bash
Expand Down Expand Up @@ -181,7 +189,6 @@ GitHub Sudo — re-execute commands with per-org elevated tokens.

Options:
--org ORG Target org (auto-detected from -R flag or git remote)
--no-gui Skip GUI dialog, use terminal prompt only
--setup ORG Store encrypted GitHub PAT for an org
--verify [ORG] Verify stored token(s)
--revoke [ORG] Revoke stored token(s)
Expand Down Expand Up @@ -211,7 +218,9 @@ On **Linux**, `ghsudo` tries (in order): `xmessage`, `zenity`, `kdialog`.
On **macOS**, it uses `osascript` (the built-in AppleScript runner).
On **Windows**, it uses PowerShell's `MessageBox`.

If no GUI is available (e.g. headless server), it falls back to a terminal prompt. Use `--no-gui` to force terminal-only mode.
A graphical display is **required** — `ghsudo` will refuse to run without one, because a terminal prompt can be trivially auto-approved by an AI agent, defeating the purpose. If no GUI toolkit is found, `ghsudo` exits with code 3.

> **Tip:** If you run your agent on a remote machine via SSH, use `ssh -X` (X11 forwarding) so that `ghsudo` GUI dialogs appear on your local display.

The dialog auto-denies after **60 seconds** of no response to prevent the agent from hanging indefinitely.
Comment thread
lklimek marked this conversation as resolved.

Expand All @@ -238,7 +247,7 @@ The dialog auto-denies after **60 seconds** of no response to prevent the agent
| 0 | Success |
| 1 | Error |
| 2 | User denied the request |
| 3 | No interactive session available to ask for approval |
| 3 | No graphical display available, or no supported GUI dialog tool found |
| 4 | No token stored for the target org |

## Debugging
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "ghsudo"
version = "0.1.0"
version = "0.2.0"
description = "GitHub Sudo — re-execute commands with an elevated GitHub token after user approval"
readme = "README.md"
license = { file = "LICENSE" }
Expand Down
7 changes: 6 additions & 1 deletion src/ghsudo/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
"""ghsudo — GitHub Sudo: re-execute commands with an elevated GitHub token."""

__version__ = "1.0.0"
from importlib.metadata import PackageNotFoundError, version

try:
__version__ = version("ghsudo")
except PackageNotFoundError:
__version__ = "0.0.0+unknown"
Loading