pi-gogs-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 summerway
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # pi-gogs-cli
2
+
3
+ A [pi coding agent](https://github.com/earendil-works/pi-coding-agent) skill that ships **gogs-cli** — a GitHub-CLI-style (`gh`) command line for self-hosted [Gogs](https://gogs.io) instances. Usable by any agent (pi, Codex, Claude Code, ...) or by a human, on desktop, headless boxes, containers, and CI. Python 3 stdlib only — zero dependencies.
4
+
5
+ ## Why
6
+
7
+ Gogs has a usable API for repos/issues/labels but **no pull-request API at all** (`/pulls` 404s), and its issues list excludes PRs. Existing CLIs either ignore PRs or drive a real browser. gogs-cli gives an agent full repo/issue/PR capability with one stateless tool:
8
+
9
+ - **repo / issue / label** — plain Gogs API (`gh`-style verbs, `issue develop` branches off an issue gh-style)
10
+ - **pr list/create/merge/close/reopen** — headless **webform engine**: session login + CSRF form POSTs against the Gogs Web UI itself, no browser, no OS dependency
11
+ - every webform write is **verified through the API** before success is reported — a submitted form alone is never reported as success
12
+ - `--json` on every command; structured `reason` warnings (exit 2) for `pr merge` no-ops (already merged / empty diff / conflict)
13
+
14
+ ## Install (pi)
15
+
16
+ ```bash
17
+ pi install npm:pi-gogs-cli
18
+ ```
19
+
20
+ The skill's `scripts/gogs-cli` lands under `~/.pi/agent/npm/pi-gogs-cli/...`; symlink it onto PATH:
21
+
22
+ ```bash
23
+ ln -sf ~/.pi/agent/npm/pi-gogs-cli/skills/gogs-cli/scripts/gogs-cli ~/.local/bin/gogs-cli
24
+ ```
25
+
26
+ Any other agent or a human can use the same checkout directly.
27
+
28
+ ## Configuration
29
+
30
+ All config is loaded from disk, never printed.
31
+
32
+ | File | Keys |
33
+ |---|---|
34
+ | `~/.config/gogs-cli/config` | `GOGS_TOKEN` (required for API ops), `GOGS_USERNAME` / `GOGS_PASSWORD` (webform PR ops) |
35
+ | `~/.codex/local/gogs-workflow/.env` | legacy location, still honored |
36
+ | `<repo-root>/.gogs.local.env` | `GOGS_BASE_URL`, `GOGS_WEB_BASE_URL`, `GOGS_USERNAME`, `GOGS_PASSWORD` |
37
+
38
+ Repo-level wins. Create a token in Gogs under `user/settings/applications`.
39
+
40
+ **Base resolution:** `GOGS_BASE_URL` is probed live; then each address in `GOGS_FALLBACK_URLS` (space/comma-separated) in order — first responder wins, cached per process:
41
+
42
+ ```bash
43
+ GOGS_FALLBACK_URLS="http://192.168.1.10:10015 https://gogs.example.com"
44
+ ```
45
+
46
+ Same invocation on-LAN and off-LAN, no config change. No built-in default; unconfigured runs exit with setup instructions.
47
+
48
+ Default `OWNER/REPO` derives from `git remote get-url origin`.
49
+
50
+ ## Command surface
51
+
52
+ ```
53
+ gogs-cli repo view|list
54
+ gogs-cli issue list|view|create|edit|close|reopen|comment|develop
55
+ gogs-cli label list
56
+ gogs-cli pr list|view|create|merge|close|reopen
57
+ ```
58
+
59
+ Full reference lives in the skill itself (`skills/gogs-cli/SKILL.md`) and is what agents see; the authoritative endpoint map is `skills/gogs-cli/references/gogs-api.md`.
60
+
61
+ ## Merge styles
62
+
63
+ If the instance offers only one merge style (many Gogs setups expose `create_merge_commit` only), `--squash`/`--rebase` fall back to the offered style with a note. `--delete-branch` is accepted for `gh` compatibility but prints a reminder (manual delete).
64
+
65
+ ## License
66
+
67
+ [MIT](LICENSE)
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "pi-gogs-cli",
3
+ "version": "1.0.0",
4
+ "description": "pi coding agent skill: gh-style CLI for self-hosted Gogs — repos, issues, labels over the Gogs API; pull requests via headless Web-UI form POSTs (Gogs has no PR API). Python 3 stdlib only, cross-platform",
5
+ "keywords": [
6
+ "pi-package",
7
+ "pi",
8
+ "pi-coding-agent",
9
+ "pi-skill",
10
+ "gogs",
11
+ "cli",
12
+ "git",
13
+ "self-hosted"
14
+ ],
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/summerway/pi-gogs-cli.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/summerway/pi-gogs-cli/issues"
22
+ },
23
+ "files": [
24
+ "skills",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "pi": {
29
+ "skills": ["./skills"]
30
+ }
31
+ }
@@ -0,0 +1,128 @@
1
+ ---
2
+ name: gogs-cli
3
+ description: Reusable, agent-agnostic Gogs CLI mirroring the GitHub CLI (`gh`) command surface. Repos, issues (list/view/create/edit/close/reopen/comment, plus gh-style `issue develop` to branch from an issue), and labels (read-only) over the Gogs API; pull requests (Gogs exposes no PR API) via headless Web-UI form POSTs — list/create/merge/close/reopen, no browser needed. Every webform write is verified through the API before success is reported. Base URL comes from config with optional GOGS_FALLBACK_URLS probing, so the same invocation works from several networks. Use for anything read/write against a Gogs instance. Cross-platform (macOS/Linux/containers), Python 3 stdlib, stateless; no workflow mapping.
4
+ ---
5
+
6
+ # gogs-cli
7
+
8
+ A single command line tool for Gogs capability, usable by any agent (Codex,
9
+ Claude Code, pi, ...) or by a human, on desktop or headless/container hosts.
10
+ Mirrors the GitHub CLI (`gh`) command surface.
11
+
12
+ - **Interface contract:** `references/gogs-api.md` is the authoritative endpoint
13
+ map, sourced from `https://gogs.io/api-reference/introduction`. It is the sole
14
+ reference for the command set; update it when the API or the instance changes.
15
+ - **Encapsulation:** callers see only clean `gh`-style verbs. Whether an
16
+ operation is served by the Gogs API or by Web-UI form POSTs is hidden.
17
+ - **Stateless:** no workflow mapping, no local database, no `LI/LPR` ids.
18
+
19
+ ## Setup
20
+
21
+ `gogs-cli` is on PATH (e.g. `~/.local/bin/gogs-cli`, a symlink to this skill's
22
+ `scripts/gogs-cli`). Python 3 stdlib only — no dependencies.
23
+
24
+ Config (loaded from disk, never printed):
25
+ - `~/.config/gogs-cli/config` → `GOGS_TOKEN` (required for API ops).
26
+ `~/.codex/local/gogs-workflow/.env` is honored as a legacy location.
27
+ - `<repo-root>/.gogs.local.env` → `GOGS_BASE_URL`, `GOGS_WEB_BASE_URL`,
28
+ `GOGS_USERNAME`, `GOGS_PASSWORD` (web creds used only for webform PR ops).
29
+ - Either file may hold any of these keys; repo-level wins. On headless/container
30
+ hosts put everything in the global file, with `GOGS_BASE_URL` pointing at the
31
+ internal address (e.g. `http://gogs:3000` over a shared Docker network).
32
+
33
+ Default `OWNER/REPO` is derived from `git remote get-url origin`; pass an explicit
34
+ `OWNER/REPO` positional to override.
35
+
36
+ **Base resolution.** The Gogs web/API base is resolved at first use and cached
37
+ for the process: the configured `GOGS_BASE_URL`/`GOGS_WEB_BASE_URL` is tried
38
+ first, then each address in `GOGS_FALLBACK_URLS` (space- or comma-separated) in
39
+ order — the first that responds is used. Set e.g.
40
+ `GOGS_FALLBACK_URLS="http://192.168.1.10:10015 https://gogs.example.com"` so the
41
+ same invocation works on-LAN (fast, direct) and off-LAN (public host) with no
42
+ config change. There is no built-in default; with nothing configured the CLI
43
+ exits with setup instructions.
44
+
45
+ ## Commands
46
+
47
+ ```
48
+ # repo — API, read-only
49
+ gogs-cli repo view [OWNER/REPO] [--json]
50
+ gogs-cli repo list [--user USER] [--limit N] [--json]
51
+
52
+ # issue — API
53
+ gogs-cli issue list [OWNER/REPO] [--state open|closed|all] [--label NAME...] [--assignee USER] [--limit N] [--json]
54
+ gogs-cli issue view [OWNER/REPO] NUMBER [--json]
55
+ gogs-cli issue create --title T [--body B | -F FILE] [--assignee USER] [--label NAME...] [--milestone N] [OWNER/REPO] [--json]
56
+ gogs-cli issue edit [OWNER/REPO] NUMBER [--title T] [--body B | -F FILE] [--state open|closed] [--json]
57
+ gogs-cli issue close [OWNER/REPO] NUMBER [--json]
58
+ gogs-cli issue reopen [OWNER/REPO] NUMBER [--json]
59
+ gogs-cli issue comment [OWNER/REPO] NUMBER [--body B | -F FILE] [--json]
60
+ gogs-cli issue develop [OWNER/REPO] NUMBER [--base B] [--name NAME] [--json] # local branch off --base, named from the issue (gh-style)
61
+
62
+ # label — API
63
+ gogs-cli label list [OWNER/REPO] [--json]
64
+
65
+ # pr — view via API; list/create/merge/close/reopen via Web UI (no PR API)
66
+ gogs-cli pr list [OWNER/REPO] [--state open|closed|all] [--limit N] [--json] # Web UI scrape
67
+ gogs-cli pr view [OWNER/REPO] NUMBER [--json] # API
68
+ gogs-cli pr create [--base B] [--head H] --title T [--body B | -F FILE] [OWNER/REPO] [--json] # --base default develop, --head default current branch
69
+ gogs-cli pr merge [OWNER/REPO] NUMBER [--merge|--squash|--rebase] [--delete-branch] [--json]
70
+ gogs-cli pr close [OWNER/REPO] NUMBER [--json]
71
+ gogs-cli pr reopen [OWNER/REPO] NUMBER [--json]
72
+ ```
73
+
74
+ `--json` switches any command to machine-readable JSON output (the raw API
75
+ object) so other tools/agents can compose. `--label NAME` is resolved to label
76
+ IDs via the labels API (Gogs accepts IDs only) — unknown name → error.
77
+
78
+ Exit codes: 0 success · 1 failure · 2 soft warning — `pr merge` that cannot
79
+ proceed (empty diff, conflict, already merged) exits 2 with a structured
80
+ `reason` instead of failing hard (details below). `--delete-branch` is
81
+ accepted for `gh` compatibility but **not automated**: it prints a reminder;
82
+ delete the branch manually.
83
+
84
+ ## How PR ops work (no PR API exists)
85
+
86
+ Gogs exposes **no** pull-request API (`/pulls` returns 404), and the issues list
87
+ **excludes** PRs. So PR operations submit the Gogs Web UI's own forms directly —
88
+ **webform**: Python-stdlib session login + CSRF form POSTs, no browser and no OS
89
+ dependency, identical behavior on macOS, Linux, and containers:
90
+
91
+ - `pr list` scrapes `/pulls?type=all&state=…` (paginated)
92
+ - `pr create` POSTs the compare-page form (`title`/`content`); the redirect
93
+ target carries the new PR number
94
+ - `pr merge` POSTs the `/pulls/N/merge` form (`merge_style`)
95
+ - `pr close/reopen` POST the comment form's hidden `status` field
96
+ (`close`/`reopen`)
97
+ - Web login uses `GOGS_USERNAME`/`GOGS_PASSWORD` from the env files
98
+
99
+ For every write, `gogs-cli` **verifies the real remote state through the issues
100
+ API** (e.g. `pull_request.merged === true`, or issue `state`) before reporting
101
+ success — a submitted form alone is never reported as success.
102
+
103
+ `pr merge` that **cannot proceed** (empty-diff / nothing-to-merge, conflict, or
104
+ already-merged) is reported as a **non-zero warning (exit 2)**, not a hard
105
+ failure: Gogs renders no merge form in these cases, so `gogs-cli` reads the
106
+ page banner plus the issues-API `merged` flag and emits a structured `reason`
107
+ (`empty_diff` · `conflict` · `already_merged` · `not_mergeable`) with Gogs's raw
108
+ banner text — in both human and `--json` output. Genuine failures (auth error,
109
+ submitted-but-not-merged, or `#N` is not a PR) still exit 1.
110
+
111
+ If this Gogs offers only one merge style (e.g. `create_merge_commit` only),
112
+ `--squash`/`--rebase` fall back to the offered style with a stderr note.
113
+
114
+ Limitations: the `/pulls` closed tab groups merged PRs as "closed" — use
115
+ `pr view N` for the precise merged state; `--base/--head` are not supported by
116
+ `pr list`.
117
+
118
+ ## Token safety
119
+
120
+ If `GOGS_TOKEN` is missing, `gogs-cli` stops and prints setup instructions — it
121
+ never guesses or retries. Tokens and `.gogs.local.env` contents are never
122
+ printed. Do not commit credentials or workflow mapping files.
123
+
124
+ ## Importing as a module
125
+
126
+ `gogs_cli.py` is importable. Functions like `resolve_target`, `api`, `api_root`,
127
+ `create`/`view` helpers, and the `webform_*` PR functions can be
128
+ reused directly to avoid duplicating the API client.
@@ -0,0 +1,98 @@
1
+ # Gogs API Reference (authoritative for `gogs-cli`)
2
+
3
+ Source of truth for the `gogs-cli` command surface. Captured from the official
4
+ Gogs API docs (`https://gogs.io/api-reference/introduction`, full index
5
+ `https://gogs.io/llms.txt`) and verified against the local instance
6
+ `http://192.168.100.3:10015` (HTTP status in parentheses, probed 2026-08-01).
7
+
8
+ Any future change to `gogs-cli` commands MUST be justified against this map.
9
+ The official API docs at `https://gogs.io/api-reference/introduction` are the
10
+ sole interface reference; update this file when the API or the instance changes.
11
+
12
+ ## Conventions
13
+
14
+ - Base path: `/api/v1`.
15
+ - Auth: `Authorization: token <TOKEN>` (the token is read from
16
+ `~/.codex/local/gogs-workflow/.env` as `GOGS_TOKEN`; never printed).
17
+ - `:owner/:repo` resolves from the `origin` git remote unless overridden.
18
+ - List endpoints paginate with `?page=N` (~10/page). Gogs ignores `limit`; the
19
+ `type=` param (a Gitea feature) is also ignored. `state=all` is **not** honored
20
+ — pass a concrete `state=open`/`closed` and combine them for "all".
21
+
22
+ ## Repository (API — read only is wrapped)
23
+
24
+ | Method | Path | Status | `gogs-cli` |
25
+ |---|---|---|---|
26
+ | GET | `/repos/:owner/:repo` | 200 | `repo view` |
27
+ | GET | `/user/repos` | 200 | `repo list` (authenticated user) |
28
+ | GET | `/users/:username/repos` | 200 | `repo list --user <name>` |
29
+ | GET | `/repos/:owner/:repo/branches` | 200 | _(not wrapped; use `git`)_ |
30
+
31
+ `repo create/delete/fork/collaborators` are management endpoints — intentionally
32
+ **not** wrapped.
33
+
34
+ ## Issues (API)
35
+
36
+ | Method | Path | Status | `gogs-cli` |
37
+ |---|---|---|---|
38
+ | GET | `/repos/:owner/:repo/issues` | 200 | `issue list` (supports `state`, `labels`, `assignee`) |
39
+ | GET | `/repos/:owner/:repo/issues/:number` | 200 | `issue view` |
40
+ | POST | `/repos/:owner/:repo/issues` | 201 | `issue create` (body: `title`, `body`, `assignee`, `milestone`, `labels` IDs) |
41
+ | PATCH | `/repos/:owner/:repo/issues/:number` | 200 | `issue edit` / `issue close` / `issue reopen` (body: `state`) |
42
+ | GET | `/repos/:owner/:repo/issues/:number/comments` | 200 | _(shown by `issue view`)_ |
43
+ | POST | `/repos/:owner/:repo/issues/:number/comments` | 201 | `issue comment` |
44
+
45
+ Notes:
46
+ - `labels` on create/edit takes label **IDs**, not names. `gogs-cli --label NAME`
47
+ resolves names → IDs via `GET /repos/:owner/:repo/labels` (gh-compatible UX).
48
+ - Closing = `PATCH /issues/:number {"state": "closed"}`; reopen = `{"state": "open"}`.
49
+
50
+ ## Labels (API)
51
+
52
+ | Method | Path | Status | `gogs-cli` |
53
+ |---|---|---|---|
54
+ | GET | `/repos/:owner/:repo/labels` | 200 | `label list` |
55
+
56
+ Only `label list` is wrapped (to support `--label` name resolution). Label
57
+ create/edit/delete are management ops — not wrapped.
58
+
59
+ ## Pull Requests (NO API — ego black box)
60
+
61
+ | Path | Status | Conclusion |
62
+ |---|---|---|
63
+ | `GET /repos/:owner/:repo/pulls` | **404** | No PR list endpoint |
64
+ | `GET /repos/:owner/:repo/pulls/:number` | **404** | No PR get endpoint |
65
+ | `POST /repos/:owner/:repo/pulls` | **absent** | No PR create endpoint |
66
+ | merge/close/reopen | **absent** | No PR mutation endpoints |
67
+
68
+ Gogs registers **no PR API routes**, and the issues list **excludes** PRs
69
+ entirely (a PR is only observable via `GET /issues/:number`, which then carries a
70
+ `pull_request` key). Therefore in `gogs-cli`:
71
+
72
+ - **`pr view N`** — served by the **issues endpoint**: `GET /issues/N`, asserting
73
+ `pull_request` is present. Authoritative (includes `merged`, `base`, `head`).
74
+ - **`pr list`** — **no API exists**; the issues list returns zero PRs. Served by
75
+ **ego-browser scraping the Web UI**: `/pulls?type=all&state=<open|closed>
76
+ &labels=0&milestone=0&assignee=0`. The ego profile auto-logs in via
77
+ `GOGS_USERNAME`/`GOGS_PASSWORD` when it hits `/user/login`. Limitation: the
78
+ closed tab groups merged PRs as "closed"; use `pr view N` for the precise
79
+ merged state. `--base/--head` are not filterable here.
80
+ - **PR write** (`pr create`, `pr merge`, `pr close`, `pr reopen`) is performed by
81
+ **ego-browser on the Gogs Web UI as a black box** (`/compare/...`, `/pulls/:n`,
82
+ `/pulls/:n/merge`). After the browser action, `gogs-cli` **verifies the real
83
+ state via the issues API** (e.g. `pull_request.merged === true`, or issue
84
+ `state`) before reporting success — never trusts a click alone.
85
+ - **`pr merge` nothing-to-merge detection**: when Gogs renders no merge button
86
+ (empty-diff / nothing-to-merge, conflict, or already-merged), `gogs-cli` reads
87
+ the page banner + the issues-API `merged` flag and returns a **non-zero warning
88
+ (exit 2)** carrying `reason` (`empty_diff` · `conflict` · `already_merged` ·
89
+ `not_mergeable`) and the raw banner text — distinct from a hard failure (exit 1).
90
+
91
+ ## Token setup
92
+
93
+ When `GOGS_TOKEN` is missing, `gogs-cli` stops and instructs:
94
+
95
+ 1. Log in to the Gogs Web UI.
96
+ 2. Open `<base>/user/settings/applications`.
97
+ 3. Generate a token (e.g. named `gogs-cli`).
98
+ 4. Save to `~/.codex/local/gogs-workflow/.env` as `GOGS_TOKEN=<token>` (chmod 600).
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python3
2
+ """Console entry for gogs-cli. Resolves the symlink to the real script dir so
3
+ the `gogs_cli` module can be imported, then delegates to gogs_cli.main()."""
4
+ import os
5
+ import sys
6
+
7
+ sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
8
+
9
+ import gogs_cli # noqa: E402
10
+
11
+ if __name__ == "__main__":
12
+ gogs_cli.main()