codeberg-cli 0.3.0__tar.gz → 0.4.1__tar.gz
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.
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/PKG-INFO +23 -22
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/README.md +21 -20
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/pyproject.toml +2 -5
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/client.py +19 -5
- codeberg_cli-0.4.1/src/codeberg_cli/routes/actions/_format.py +36 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/actions/run.py +15 -10
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/actions/runs.py +12 -14
- codeberg_cli-0.4.1/src/codeberg_cli/routes/actions/workflows.py +69 -0
- codeberg_cli-0.4.1/tests/test_actions.py +145 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/tests/test_client.py +12 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/uv.lock +6 -26
- codeberg_cli-0.3.0/src/codeberg_cli/routes/actions/workflows.py +0 -46
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/.github/workflows/ci.yml +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/.github/workflows/release.yml +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/.gitignore +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/.python-version +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/Justfile +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/LICENSE +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/__main__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/config.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/git.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/helpers.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/actions/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/actions/dispatch.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/api.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/auth/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/auth/login.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/auth/logout.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/auth/status.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/auth/whoami.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/close.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/comment.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/create.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/delete.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/edit.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/labels/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/labels/add.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/labels/list.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/labels/remove.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/list.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/pin.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/reopen.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/search.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/subscribe.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/time/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/time/add.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/time/list.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/unpin.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/unsubscribe.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/issue/view.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/label/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/label/create.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/label/delete.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/label/edit.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/label/list.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/milestone/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/milestone/create.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/milestone/delete.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/milestone/edit.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/milestone/list.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/milestone/view.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/notifications.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/org/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/org/list.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/org/members.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/org/teams.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/org/view.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/check_merge.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/checkout.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/close.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/comment.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/commits.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/create.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/diff.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/edit.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/files.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/list.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/merge.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/reopen.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/update.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/pr/view.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/release/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/release/create.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/release/delete.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/release/edit.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/release/list.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/release/upload.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/release/view.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/archive.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/branch/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/branch/create.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/branch/delete.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/branch/list.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/branch/view.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/clone.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/collaborator/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/collaborator/add.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/collaborator/delete.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/collaborator/list.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/commits.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/contents.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/create.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/delete.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/edit.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/fork.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/forks/sync.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/forks.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/languages.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/list.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/migrate.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/search.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/star.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/stargazers.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/tag/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/tag/create.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/tag/delete.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/tag/list.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/topics/__init__.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/topics/list.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/topics/set.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/transfer.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/unstar.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/unwatch.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/view.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/watch.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/repo/watchers.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/src/codeberg_cli/routes/user.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/tests/test_api.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/tests/test_auth.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/tests/test_config.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/tests/test_git.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/tests/test_issue.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/tests/test_pr.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/tests/test_release.py +0 -0
- {codeberg_cli-0.3.0 → codeberg_cli-0.4.1}/tests/test_repo.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeberg-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: A Forgejo CLI — works with Codeberg and any Forgejo instance
|
|
5
5
|
Project-URL: Homepage, https://codeberg.org/ThatXliner/codeberg-cli
|
|
6
6
|
Project-URL: Repository, https://codeberg.org/ThatXliner/codeberg-cli
|
|
@@ -20,7 +20,7 @@ Requires-Python: >=3.12
|
|
|
20
20
|
Requires-Dist: httpx
|
|
21
21
|
Requires-Dist: platformdirs
|
|
22
22
|
Requires-Dist: tomlkit
|
|
23
|
-
Requires-Dist: xclif>=0.
|
|
23
|
+
Requires-Dist: xclif>=0.5.1
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
|
|
26
26
|
# cb — A Codeberg CLI
|
|
@@ -95,10 +95,11 @@ cb config set base_url "https://git.example.com/api/v1" # Self-hosted Forgejo
|
|
|
95
95
|
|
|
96
96
|
Here's how `cb` stacks up against other Forgejo CLI tools. *Last updated: May 2026.*
|
|
97
97
|
|
|
98
|
+
TL;DR: **`cb` is the most feature-complete Forgejo CLI** — by a wide margin.
|
|
99
|
+
|
|
98
100
|
| | **cb** (this) | **fj** (forgejo-cli) | **berg** (codeberg-cli) | **tea** (gitea/tea) | **gcli** |
|
|
99
101
|
|---|---|---|---|---|---|
|
|
100
102
|
| Language | Python | Rust | Rust | Go | C |
|
|
101
|
-
| Version | v0.2.0 | v0.5.0 | v0.5.1 | v0.13.0 | v2.11.0 |
|
|
102
103
|
| Install | `pip install codeberg-cli` | prebuilt binaries/Cargo | `cargo install codeberg-cli` | `brew install tea` | `brew install gcli` |
|
|
103
104
|
| Multi-instance | `cb config set base_url` | `-H <instance>` | `BERG_BASE_URL` | `tea login add` | `-t forgejo` |
|
|
104
105
|
|
|
@@ -108,18 +109,18 @@ Here's how `cb` stacks up against other Forgejo CLI tools. *Last updated: May 20
|
|
|
108
109
|
| Create | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
109
110
|
| List | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
110
111
|
| View | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
111
|
-
| Close | ✅ | ✅ | ✅ | ✅ |
|
|
112
|
-
| Reopen | ✅ | — | ✅ | ✅ |
|
|
112
|
+
| Close | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
113
|
+
| Reopen | ✅ | — | ✅ | ✅ | ✅ |
|
|
113
114
|
| Comment | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
114
|
-
| Edit | ✅ |
|
|
115
|
+
| Edit | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
115
116
|
| Delete | ✅ | — | — | — | — |
|
|
116
117
|
| Pin/Unpin | ✅ | — | — | ✅ | — |
|
|
117
118
|
| Search | ✅ | ✅ | — | ✅ | — |
|
|
118
119
|
| Attachments | — | — | — | ✅ | — |
|
|
119
|
-
| Labels (manage on issue) | ✅ | — | — | ✅ |
|
|
120
|
+
| Labels (manage on issue) | ✅ | — | — | ✅ | ✅ |
|
|
120
121
|
| Reactions | — | — | — | — | — |
|
|
121
122
|
| Subscriptions | ✅ | — | — | ✅ | — |
|
|
122
|
-
| Tracked times | ✅ | — | — |
|
|
123
|
+
| Tracked times | ✅ | — | — | ✅ | ✅ |
|
|
123
124
|
| Dependencies | — | — | — | — | — |
|
|
124
125
|
| Deadline | — | — | — | ✅ | — |
|
|
125
126
|
| Templates | — | ✅ | — | — | — |
|
|
@@ -131,16 +132,16 @@ Here's how `cb` stacks up against other Forgejo CLI tools. *Last updated: May 20
|
|
|
131
132
|
| List | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
132
133
|
| View | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
133
134
|
| Merge | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
134
|
-
| Close | ✅ |
|
|
135
|
-
| Reopen | ✅ | — | ✅ | ✅ |
|
|
135
|
+
| Close | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
136
|
+
| Reopen | ✅ | — | ✅ | ✅ | ✅ |
|
|
136
137
|
| Comment | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
137
|
-
| Edit | ✅ |
|
|
138
|
-
| Checkout | ✅ |
|
|
139
|
-
| Commits | ✅ |
|
|
140
|
-
| Files/changed | ✅ |
|
|
138
|
+
| Edit | ✅ | ✅ | ✅ | ✅ | — |
|
|
139
|
+
| Checkout | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
140
|
+
| Commits | ✅ | ✅ | — | — | ✅ |
|
|
141
|
+
| Files/changed | ✅ | ✅ | — | — | — |
|
|
141
142
|
| Reviews | — | ✅ | — | ✅ | ✅ |
|
|
142
|
-
| Diff/Patch | ✅ |
|
|
143
|
-
| Update branch | ✅ | — | — |
|
|
143
|
+
| Diff/Patch | ✅ | ✅ | — | — | ✅ |
|
|
144
|
+
| Update branch | ✅ | — | — | — | — |
|
|
144
145
|
| AGit (no-fork) | — | ✅ | — | — | — |
|
|
145
146
|
| CI status | — | ✅ | — | — | — |
|
|
146
147
|
| Templates | — | ✅ | — | — | — |
|
|
@@ -151,7 +152,7 @@ Here's how `cb` stacks up against other Forgejo CLI tools. *Last updated: May 20
|
|
|
151
152
|
|---|---|---|---|---|---|
|
|
152
153
|
| Create | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
153
154
|
| List | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
154
|
-
| View | ✅ | ✅ | ✅ | ✅ |
|
|
155
|
+
| View | ✅ | ✅ | ✅ | ✅ | — |
|
|
155
156
|
| Upload assets | ✅ | — | ✅ | — | ✅ |
|
|
156
157
|
| Delete | ✅ | — | — | ✅ | ✅ |
|
|
157
158
|
| Edit | ✅ | — | — | ✅ | — |
|
|
@@ -164,12 +165,12 @@ Here's how `cb` stacks up against other Forgejo CLI tools. *Last updated: May 20
|
|
|
164
165
|
| Create | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
165
166
|
| List | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
166
167
|
| View | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
167
|
-
| Clone | ✅ |
|
|
168
|
+
| Clone | ✅ | ✅ | ✅ | ✅ | — |
|
|
168
169
|
| Fork | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
169
170
|
| Delete | ✅ | — | ✅ | ✅ | — |
|
|
170
171
|
| Star | ✅ | ✅ | ✅ | ✅ | — |
|
|
171
|
-
| Unstar | ✅ |
|
|
172
|
-
| Watch/Unwatch | ✅ |
|
|
172
|
+
| Unstar | ✅ | ✅ | ✅ | ✅ | — |
|
|
173
|
+
| Watch/Unwatch | ✅ | — | — | ✅ | — |
|
|
173
174
|
| Edit | ✅ | ✅ | — | ✅ | — |
|
|
174
175
|
| Migrate/Mirror | ✅ | ✅ | — | ✅ | — |
|
|
175
176
|
| Branches | ✅ | — | — | ✅ | — |
|
|
@@ -207,7 +208,7 @@ Here's how `cb` stacks up against other Forgejo CLI tools. *Last updated: May 20
|
|
|
207
208
|
|---|---|---|---|---|---|
|
|
208
209
|
| List | ✅ | — | ✅ | ✅ | ✅ |
|
|
209
210
|
| Mark read | — | — | — | ✅ | — |
|
|
210
|
-
| Thread details | — | — | — |
|
|
211
|
+
| Thread details | — | — | — | — | — |
|
|
211
212
|
| Per-repo | — | — | — | ✅ | — |
|
|
212
213
|
|
|
213
214
|
### Extra
|
|
@@ -225,7 +226,7 @@ Here's how `cb` stacks up against other Forgejo CLI tools. *Last updated: May 20
|
|
|
225
226
|
| Config management | ✅ | — | ✅ | — | — |
|
|
226
227
|
| Web browser flag | on view commands | — | — | ✅ | — |
|
|
227
228
|
|
|
228
|
-
|
|
229
|
+
Built in Python with [Xclif](https://xclif.readthedocs.io) for a clean, hackable codebase. If something's missing, open an issue!
|
|
229
230
|
|
|
230
231
|
## License
|
|
231
232
|
|
|
@@ -70,10 +70,11 @@ cb config set base_url "https://git.example.com/api/v1" # Self-hosted Forgejo
|
|
|
70
70
|
|
|
71
71
|
Here's how `cb` stacks up against other Forgejo CLI tools. *Last updated: May 2026.*
|
|
72
72
|
|
|
73
|
+
TL;DR: **`cb` is the most feature-complete Forgejo CLI** — by a wide margin.
|
|
74
|
+
|
|
73
75
|
| | **cb** (this) | **fj** (forgejo-cli) | **berg** (codeberg-cli) | **tea** (gitea/tea) | **gcli** |
|
|
74
76
|
|---|---|---|---|---|---|
|
|
75
77
|
| Language | Python | Rust | Rust | Go | C |
|
|
76
|
-
| Version | v0.2.0 | v0.5.0 | v0.5.1 | v0.13.0 | v2.11.0 |
|
|
77
78
|
| Install | `pip install codeberg-cli` | prebuilt binaries/Cargo | `cargo install codeberg-cli` | `brew install tea` | `brew install gcli` |
|
|
78
79
|
| Multi-instance | `cb config set base_url` | `-H <instance>` | `BERG_BASE_URL` | `tea login add` | `-t forgejo` |
|
|
79
80
|
|
|
@@ -83,18 +84,18 @@ Here's how `cb` stacks up against other Forgejo CLI tools. *Last updated: May 20
|
|
|
83
84
|
| Create | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
84
85
|
| List | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
85
86
|
| View | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
86
|
-
| Close | ✅ | ✅ | ✅ | ✅ |
|
|
87
|
-
| Reopen | ✅ | — | ✅ | ✅ |
|
|
87
|
+
| Close | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
88
|
+
| Reopen | ✅ | — | ✅ | ✅ | ✅ |
|
|
88
89
|
| Comment | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
89
|
-
| Edit | ✅ |
|
|
90
|
+
| Edit | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
90
91
|
| Delete | ✅ | — | — | — | — |
|
|
91
92
|
| Pin/Unpin | ✅ | — | — | ✅ | — |
|
|
92
93
|
| Search | ✅ | ✅ | — | ✅ | — |
|
|
93
94
|
| Attachments | — | — | — | ✅ | — |
|
|
94
|
-
| Labels (manage on issue) | ✅ | — | — | ✅ |
|
|
95
|
+
| Labels (manage on issue) | ✅ | — | — | ✅ | ✅ |
|
|
95
96
|
| Reactions | — | — | — | — | — |
|
|
96
97
|
| Subscriptions | ✅ | — | — | ✅ | — |
|
|
97
|
-
| Tracked times | ✅ | — | — |
|
|
98
|
+
| Tracked times | ✅ | — | — | ✅ | ✅ |
|
|
98
99
|
| Dependencies | — | — | — | — | — |
|
|
99
100
|
| Deadline | — | — | — | ✅ | — |
|
|
100
101
|
| Templates | — | ✅ | — | — | — |
|
|
@@ -106,16 +107,16 @@ Here's how `cb` stacks up against other Forgejo CLI tools. *Last updated: May 20
|
|
|
106
107
|
| List | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
107
108
|
| View | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
108
109
|
| Merge | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
109
|
-
| Close | ✅ |
|
|
110
|
-
| Reopen | ✅ | — | ✅ | ✅ |
|
|
110
|
+
| Close | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
111
|
+
| Reopen | ✅ | — | ✅ | ✅ | ✅ |
|
|
111
112
|
| Comment | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
112
|
-
| Edit | ✅ |
|
|
113
|
-
| Checkout | ✅ |
|
|
114
|
-
| Commits | ✅ |
|
|
115
|
-
| Files/changed | ✅ |
|
|
113
|
+
| Edit | ✅ | ✅ | ✅ | ✅ | — |
|
|
114
|
+
| Checkout | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
115
|
+
| Commits | ✅ | ✅ | — | — | ✅ |
|
|
116
|
+
| Files/changed | ✅ | ✅ | — | — | — |
|
|
116
117
|
| Reviews | — | ✅ | — | ✅ | ✅ |
|
|
117
|
-
| Diff/Patch | ✅ |
|
|
118
|
-
| Update branch | ✅ | — | — |
|
|
118
|
+
| Diff/Patch | ✅ | ✅ | — | — | ✅ |
|
|
119
|
+
| Update branch | ✅ | — | — | — | — |
|
|
119
120
|
| AGit (no-fork) | — | ✅ | — | — | — |
|
|
120
121
|
| CI status | — | ✅ | — | — | — |
|
|
121
122
|
| Templates | — | ✅ | — | — | — |
|
|
@@ -126,7 +127,7 @@ Here's how `cb` stacks up against other Forgejo CLI tools. *Last updated: May 20
|
|
|
126
127
|
|---|---|---|---|---|---|
|
|
127
128
|
| Create | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
128
129
|
| List | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
129
|
-
| View | ✅ | ✅ | ✅ | ✅ |
|
|
130
|
+
| View | ✅ | ✅ | ✅ | ✅ | — |
|
|
130
131
|
| Upload assets | ✅ | — | ✅ | — | ✅ |
|
|
131
132
|
| Delete | ✅ | — | — | ✅ | ✅ |
|
|
132
133
|
| Edit | ✅ | — | — | ✅ | — |
|
|
@@ -139,12 +140,12 @@ Here's how `cb` stacks up against other Forgejo CLI tools. *Last updated: May 20
|
|
|
139
140
|
| Create | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
140
141
|
| List | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
141
142
|
| View | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
142
|
-
| Clone | ✅ |
|
|
143
|
+
| Clone | ✅ | ✅ | ✅ | ✅ | — |
|
|
143
144
|
| Fork | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
144
145
|
| Delete | ✅ | — | ✅ | ✅ | — |
|
|
145
146
|
| Star | ✅ | ✅ | ✅ | ✅ | — |
|
|
146
|
-
| Unstar | ✅ |
|
|
147
|
-
| Watch/Unwatch | ✅ |
|
|
147
|
+
| Unstar | ✅ | ✅ | ✅ | ✅ | — |
|
|
148
|
+
| Watch/Unwatch | ✅ | — | — | ✅ | — |
|
|
148
149
|
| Edit | ✅ | ✅ | — | ✅ | — |
|
|
149
150
|
| Migrate/Mirror | ✅ | ✅ | — | ✅ | — |
|
|
150
151
|
| Branches | ✅ | — | — | ✅ | — |
|
|
@@ -182,7 +183,7 @@ Here's how `cb` stacks up against other Forgejo CLI tools. *Last updated: May 20
|
|
|
182
183
|
|---|---|---|---|---|---|
|
|
183
184
|
| List | ✅ | — | ✅ | ✅ | ✅ |
|
|
184
185
|
| Mark read | — | — | — | ✅ | — |
|
|
185
|
-
| Thread details | — | — | — |
|
|
186
|
+
| Thread details | — | — | — | — | — |
|
|
186
187
|
| Per-repo | — | — | — | ✅ | — |
|
|
187
188
|
|
|
188
189
|
### Extra
|
|
@@ -200,7 +201,7 @@ Here's how `cb` stacks up against other Forgejo CLI tools. *Last updated: May 20
|
|
|
200
201
|
| Config management | ✅ | — | ✅ | — | — |
|
|
201
202
|
| Web browser flag | on view commands | — | — | ✅ | — |
|
|
202
203
|
|
|
203
|
-
|
|
204
|
+
Built in Python with [Xclif](https://xclif.readthedocs.io) for a clean, hackable codebase. If something's missing, open an issue!
|
|
204
205
|
|
|
205
206
|
## License
|
|
206
207
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codeberg-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.1"
|
|
4
4
|
description = "A Forgejo CLI — works with Codeberg and any Forgejo instance"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -19,7 +19,7 @@ classifiers = [
|
|
|
19
19
|
"Topic :: Software Development :: Version Control :: Git",
|
|
20
20
|
]
|
|
21
21
|
dependencies = [
|
|
22
|
-
"xclif>=0.
|
|
22
|
+
"xclif>=0.5.1",
|
|
23
23
|
"httpx",
|
|
24
24
|
"tomlkit",
|
|
25
25
|
"platformdirs",
|
|
@@ -37,9 +37,6 @@ cb = "codeberg_cli.__main__:cli"
|
|
|
37
37
|
requires = ["hatchling"]
|
|
38
38
|
build-backend = "hatchling.build"
|
|
39
39
|
|
|
40
|
-
[tool.uv.sources]
|
|
41
|
-
xclif = { path = "/Users/bryanhu/Developer/current/XClif/xclif" }
|
|
42
|
-
|
|
43
40
|
[tool.hatch.build.targets.wheel]
|
|
44
41
|
packages = ["src/codeberg_cli"]
|
|
45
42
|
|
|
@@ -17,6 +17,24 @@ class ClientError(RuntimeError):
|
|
|
17
17
|
"""Raised when the API returns a non-2xx status."""
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def _response_error_message(response: httpx.Response) -> str:
|
|
21
|
+
if not response.text:
|
|
22
|
+
return response.reason_phrase or "unknown error"
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
payload = response.json()
|
|
26
|
+
except ValueError:
|
|
27
|
+
return response.text.strip() or response.reason_phrase or "unknown error"
|
|
28
|
+
|
|
29
|
+
if isinstance(payload, dict):
|
|
30
|
+
for key in ("message", "error", "detail"):
|
|
31
|
+
message = payload.get(key)
|
|
32
|
+
if message:
|
|
33
|
+
return str(message)
|
|
34
|
+
|
|
35
|
+
return response.text.strip() or response.reason_phrase or "unknown error"
|
|
36
|
+
|
|
37
|
+
|
|
20
38
|
class Client:
|
|
21
39
|
"""HTTP client for the Codeberg API."""
|
|
22
40
|
|
|
@@ -80,11 +98,7 @@ class Client:
|
|
|
80
98
|
with Status(f"{label}..."):
|
|
81
99
|
response = self._client.request(method, path, **kwargs)
|
|
82
100
|
if not response.is_success:
|
|
83
|
-
msg = (
|
|
84
|
-
response.json().get("message", "unknown error")
|
|
85
|
-
if response.text
|
|
86
|
-
else "unknown error"
|
|
87
|
-
)
|
|
101
|
+
msg = _response_error_message(response)
|
|
88
102
|
raise ClientError(f"{response.status_code} {msg}")
|
|
89
103
|
if response.status_code == 204:
|
|
90
104
|
return None
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _first(run: dict[str, Any], *keys: str, default: str = "") -> Any:
|
|
5
|
+
for key in keys:
|
|
6
|
+
value = run.get(key)
|
|
7
|
+
if value is not None and value != "":
|
|
8
|
+
return value
|
|
9
|
+
return default
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def normalize_run(run: dict[str, Any]) -> dict[str, Any]:
|
|
13
|
+
return {
|
|
14
|
+
"id": run.get("id"),
|
|
15
|
+
"index_in_repo": run.get("index_in_repo"),
|
|
16
|
+
"title": _first(run, "title", "display_title", default=""),
|
|
17
|
+
"status": _first(run, "conclusion", "status", default="?"),
|
|
18
|
+
"event": _first(run, "trigger_event", "event", default="?"),
|
|
19
|
+
"workflow_id": run.get("workflow_id"),
|
|
20
|
+
"created": _first(run, "created", "created_at", default=""),
|
|
21
|
+
"updated": _first(run, "updated", "updated_at", default=""),
|
|
22
|
+
"started": run.get("started"),
|
|
23
|
+
"stopped": run.get("stopped"),
|
|
24
|
+
"ref": _first(run, "prettyref", "head_branch", default=""),
|
|
25
|
+
"commit": _first(run, "commit_sha", "head_sha", default=""),
|
|
26
|
+
"html_url": _first(run, "html_url", "url", default=""),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def list_runs(response: dict[str, Any] | list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
31
|
+
if isinstance(response, dict):
|
|
32
|
+
runs = response.get("workflow_runs", [])
|
|
33
|
+
if isinstance(runs, list):
|
|
34
|
+
return runs
|
|
35
|
+
return []
|
|
36
|
+
return response
|
|
@@ -6,6 +6,8 @@ from codeberg_cli.git import infer_repo
|
|
|
6
6
|
from codeberg_cli.helpers import is_json_mode, output, require_client
|
|
7
7
|
from xclif import Arg, Option, command
|
|
8
8
|
|
|
9
|
+
from codeberg_cli.routes.actions._format import normalize_run
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
@command("run")
|
|
11
13
|
def _(
|
|
@@ -28,13 +30,16 @@ def _(
|
|
|
28
30
|
output(run)
|
|
29
31
|
return
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
rich.print(f"
|
|
35
|
-
rich.print(f"
|
|
36
|
-
rich.print(f"
|
|
37
|
-
rich.print(f"
|
|
38
|
-
rich.print(f"
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
normalized = normalize_run(run)
|
|
34
|
+
commit = normalized["commit"] or "?"
|
|
35
|
+
|
|
36
|
+
rich.print(f"[bold]Run #{normalized['id']}:[/bold] {normalized['title']}")
|
|
37
|
+
rich.print(f" Status: {normalized['status']}")
|
|
38
|
+
rich.print(f" Event: {normalized['event']}")
|
|
39
|
+
rich.print(f" Workflow: {normalized['workflow_id'] or '?'}")
|
|
40
|
+
rich.print(f" Ref: {normalized['ref'] or '?'}")
|
|
41
|
+
rich.print(f" Commit: {commit[:12]}")
|
|
42
|
+
rich.print(f" Created: {normalized['created'] or '?'}")
|
|
43
|
+
rich.print(f" Updated: {normalized['updated'] or '?'}")
|
|
44
|
+
if normalized["html_url"]:
|
|
45
|
+
rich.print(f" URL: {normalized['html_url']}")
|
|
@@ -6,6 +6,8 @@ from codeberg_cli.git import infer_repo
|
|
|
6
6
|
from codeberg_cli.helpers import print_table, require_client
|
|
7
7
|
from xclif import Option, command
|
|
8
8
|
|
|
9
|
+
from codeberg_cli.routes.actions._format import list_runs, normalize_run
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
@command("runs")
|
|
11
13
|
def _(
|
|
@@ -22,7 +24,8 @@ def _(
|
|
|
22
24
|
return 1
|
|
23
25
|
repo = inferred
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
response = client.get(f"/repos/{repo}/actions/runs", params={"limit": limit, "page": 1})
|
|
28
|
+
runs = list_runs(response)
|
|
26
29
|
|
|
27
30
|
if not runs:
|
|
28
31
|
rich.print(f"[dim]No action runs in {repo}.[/dim]")
|
|
@@ -31,19 +34,14 @@ def _(
|
|
|
31
34
|
rows = []
|
|
32
35
|
json_rows = []
|
|
33
36
|
for run in runs:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"conclusion": conclusion,
|
|
43
|
-
"event": run["event"],
|
|
44
|
-
"created_at": run.get("created_at", ""),
|
|
45
|
-
"head_branch": run.get("head_branch", ""),
|
|
46
|
-
})
|
|
37
|
+
normalized = normalize_run(run)
|
|
38
|
+
rows.append((
|
|
39
|
+
str(normalized["id"]),
|
|
40
|
+
normalized["title"][:72],
|
|
41
|
+
normalized["status"],
|
|
42
|
+
normalized["event"],
|
|
43
|
+
))
|
|
44
|
+
json_rows.append(normalized)
|
|
47
45
|
|
|
48
46
|
print_table(
|
|
49
47
|
["ID", "Title", "Status", "Event"],
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
import rich
|
|
4
|
+
|
|
5
|
+
from codeberg_cli.client import ClientError
|
|
6
|
+
from codeberg_cli.git import infer_repo
|
|
7
|
+
from codeberg_cli.helpers import print_table, require_client
|
|
8
|
+
from xclif import Option, command
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@command("workflows")
|
|
12
|
+
def _(
|
|
13
|
+
repo: Annotated[str, Option(description="Repository (owner/repo)", name="repo")] = "",
|
|
14
|
+
ref: Annotated[str, Option(description="Branch or ref", name="ref")] = "",
|
|
15
|
+
) -> None:
|
|
16
|
+
"""List workflows for a repository."""
|
|
17
|
+
client = require_client()
|
|
18
|
+
|
|
19
|
+
if not repo:
|
|
20
|
+
inferred = infer_repo()
|
|
21
|
+
if not inferred:
|
|
22
|
+
rich.print("[bold red]Error:[/bold red] No repo specified and not in a git directory")
|
|
23
|
+
return 1
|
|
24
|
+
repo = inferred
|
|
25
|
+
|
|
26
|
+
params = {}
|
|
27
|
+
if ref:
|
|
28
|
+
params["ref"] = ref
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
workflows = client.get(
|
|
32
|
+
f"/repos/{repo}/contents/.forgejo/workflows",
|
|
33
|
+
params=params,
|
|
34
|
+
action="Fetching workflow files",
|
|
35
|
+
)
|
|
36
|
+
except ClientError as exc:
|
|
37
|
+
if str(exc).startswith("404 "):
|
|
38
|
+
rich.print(f"[dim]No workflows in {repo}.[/dim]")
|
|
39
|
+
return
|
|
40
|
+
raise
|
|
41
|
+
|
|
42
|
+
if not workflows:
|
|
43
|
+
rich.print(f"[dim]No workflows in {repo}.[/dim]")
|
|
44
|
+
return
|
|
45
|
+
if not isinstance(workflows, list):
|
|
46
|
+
rich.print(f"[dim]No workflows in {repo}.[/dim]")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
rows = []
|
|
50
|
+
json_rows = []
|
|
51
|
+
for wf in workflows:
|
|
52
|
+
if wf.get("type") != "file":
|
|
53
|
+
continue
|
|
54
|
+
rows.append((wf["name"], wf.get("path", ""), str(wf.get("size", ""))))
|
|
55
|
+
json_rows.append({
|
|
56
|
+
"filename": wf["name"],
|
|
57
|
+
"path": wf.get("path", ""),
|
|
58
|
+
"size": wf.get("size"),
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
if not rows:
|
|
62
|
+
rich.print(f"[dim]No workflows in {repo}.[/dim]")
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
print_table(
|
|
66
|
+
["Filename", "Path", "Size"],
|
|
67
|
+
rows,
|
|
68
|
+
json_data=json_rows,
|
|
69
|
+
)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from codeberg_cli.config import save_config
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _login(monkeypatch, tmp_path):
|
|
5
|
+
monkeypatch.setattr("codeberg_cli.config.get_config_path", lambda: tmp_path / "codeberg-cli")
|
|
6
|
+
save_config({"token": "test_token"})
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_actions_runs_handles_forgejo_envelope(httpx_mock, capsys, tmp_path, monkeypatch):
|
|
10
|
+
_login(monkeypatch, tmp_path)
|
|
11
|
+
httpx_mock.add_response(
|
|
12
|
+
url="https://codeberg.org/api/v1/repos/owner/repo/actions/runs?limit=2&page=1",
|
|
13
|
+
json={
|
|
14
|
+
"workflow_runs": [
|
|
15
|
+
{
|
|
16
|
+
"id": 123,
|
|
17
|
+
"title": "Build firmware",
|
|
18
|
+
"status": "success",
|
|
19
|
+
"trigger_event": "push",
|
|
20
|
+
"workflow_id": "build.yaml",
|
|
21
|
+
"created": "2026-06-14T00:00:00Z",
|
|
22
|
+
"prettyref": "main",
|
|
23
|
+
"commit_sha": "abcdef1234567890",
|
|
24
|
+
"html_url": "https://codeberg.org/owner/repo/actions/runs/1",
|
|
25
|
+
"index_in_repo": 1,
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"total_count": 1,
|
|
29
|
+
},
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
from codeberg_cli.routes.actions.runs import _ as cmd
|
|
33
|
+
|
|
34
|
+
cmd.execute(["--repo", "owner/repo", "--limit", "2"])
|
|
35
|
+
captured = capsys.readouterr()
|
|
36
|
+
assert "Build firmware" in captured.out
|
|
37
|
+
assert "success" in captured.out
|
|
38
|
+
assert "push" in captured.out
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_actions_runs_handles_raw_list_response(httpx_mock, capsys, tmp_path, monkeypatch):
|
|
42
|
+
_login(monkeypatch, tmp_path)
|
|
43
|
+
httpx_mock.add_response(
|
|
44
|
+
url="https://codeberg.org/api/v1/repos/owner/repo/actions/runs?limit=20&page=1",
|
|
45
|
+
json=[
|
|
46
|
+
{
|
|
47
|
+
"id": 123,
|
|
48
|
+
"display_title": "Legacy build",
|
|
49
|
+
"conclusion": "success",
|
|
50
|
+
"event": "workflow_dispatch",
|
|
51
|
+
"created_at": "2026-06-14T00:00:00Z",
|
|
52
|
+
"head_branch": "main",
|
|
53
|
+
"head_sha": "abcdef1234567890",
|
|
54
|
+
"url": "https://codeberg.org/owner/repo/actions/runs/1",
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
from codeberg_cli.routes.actions.runs import _ as cmd
|
|
60
|
+
|
|
61
|
+
cmd.execute(["--repo", "owner/repo"])
|
|
62
|
+
captured = capsys.readouterr()
|
|
63
|
+
assert "Legacy build" in captured.out
|
|
64
|
+
assert "success" in captured.out
|
|
65
|
+
assert "workflow_dispatch" in captured.out
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_actions_runs_handles_empty_envelope(httpx_mock, capsys, tmp_path, monkeypatch):
|
|
69
|
+
_login(monkeypatch, tmp_path)
|
|
70
|
+
httpx_mock.add_response(
|
|
71
|
+
url="https://codeberg.org/api/v1/repos/owner/repo/actions/runs?limit=20&page=1",
|
|
72
|
+
json={"workflow_runs": [], "total_count": 0},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
from codeberg_cli.routes.actions.runs import _ as cmd
|
|
76
|
+
|
|
77
|
+
cmd.execute(["--repo", "owner/repo"])
|
|
78
|
+
captured = capsys.readouterr()
|
|
79
|
+
assert "No action runs in owner/repo." in captured.out
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_actions_run_displays_forgejo_fields(httpx_mock, capsys, tmp_path, monkeypatch):
|
|
83
|
+
_login(monkeypatch, tmp_path)
|
|
84
|
+
httpx_mock.add_response(
|
|
85
|
+
url="https://codeberg.org/api/v1/repos/owner/repo/actions/runs/123",
|
|
86
|
+
json={
|
|
87
|
+
"id": 123,
|
|
88
|
+
"title": "Build firmware",
|
|
89
|
+
"status": "success",
|
|
90
|
+
"trigger_event": "push",
|
|
91
|
+
"workflow_id": "build.yaml",
|
|
92
|
+
"prettyref": "main",
|
|
93
|
+
"commit_sha": "abcdef1234567890",
|
|
94
|
+
"created": "2026-06-14T00:00:00Z",
|
|
95
|
+
"updated": "2026-06-14T00:01:00Z",
|
|
96
|
+
"html_url": "https://codeberg.org/owner/repo/actions/runs/1",
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
from codeberg_cli.routes.actions.run import _ as cmd
|
|
101
|
+
|
|
102
|
+
cmd.execute(["123", "--repo", "owner/repo"])
|
|
103
|
+
captured = capsys.readouterr()
|
|
104
|
+
assert "Build firmware" in captured.out
|
|
105
|
+
assert "Workflow: build.yaml" in captured.out
|
|
106
|
+
assert "Ref: main" in captured.out
|
|
107
|
+
assert "Commit: abcdef123456" in captured.out
|
|
108
|
+
assert "https://codeberg.org/owner/repo/actions/runs/1" in captured.out
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_actions_workflows_lists_forgejo_workflow_files(httpx_mock, capsys, tmp_path, monkeypatch):
|
|
112
|
+
_login(monkeypatch, tmp_path)
|
|
113
|
+
httpx_mock.add_response(
|
|
114
|
+
url="https://codeberg.org/api/v1/repos/owner/repo/contents/.forgejo/workflows",
|
|
115
|
+
json=[
|
|
116
|
+
{
|
|
117
|
+
"name": "build.yaml",
|
|
118
|
+
"path": ".forgejo/workflows/build.yaml",
|
|
119
|
+
"type": "file",
|
|
120
|
+
"size": 983,
|
|
121
|
+
}
|
|
122
|
+
],
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
from codeberg_cli.routes.actions.workflows import _ as cmd
|
|
126
|
+
|
|
127
|
+
cmd.execute(["--repo", "owner/repo"])
|
|
128
|
+
captured = capsys.readouterr()
|
|
129
|
+
assert "build.yaml" in captured.out
|
|
130
|
+
assert ".forgejo/workflows/build.yaml" in captured.out
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_actions_workflows_handles_missing_workflow_dir(httpx_mock, capsys, tmp_path, monkeypatch):
|
|
134
|
+
_login(monkeypatch, tmp_path)
|
|
135
|
+
httpx_mock.add_response(
|
|
136
|
+
url="https://codeberg.org/api/v1/repos/owner/repo/contents/.forgejo/workflows",
|
|
137
|
+
status_code=404,
|
|
138
|
+
json={"message": "GetContentsOrList"},
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
from codeberg_cli.routes.actions.workflows import _ as cmd
|
|
142
|
+
|
|
143
|
+
cmd.execute(["--repo", "owner/repo"])
|
|
144
|
+
captured = capsys.readouterr()
|
|
145
|
+
assert "No workflows in owner/repo." in captured.out
|
|
@@ -37,6 +37,18 @@ def test_client_error(httpx_mock):
|
|
|
37
37
|
assert "401" in str(exc.value)
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
def test_client_error_with_non_json_body(httpx_mock):
|
|
41
|
+
httpx_mock.add_response(
|
|
42
|
+
url="https://codeberg.org/api/v1/user",
|
|
43
|
+
status_code=502,
|
|
44
|
+
text="Bad Gateway",
|
|
45
|
+
)
|
|
46
|
+
client = Client(token="bad")
|
|
47
|
+
with pytest.raises(ClientError) as exc:
|
|
48
|
+
client.get("/user")
|
|
49
|
+
assert str(exc.value) == "502 Bad Gateway"
|
|
50
|
+
|
|
51
|
+
|
|
40
52
|
def test_client_no_auth(httpx_mock):
|
|
41
53
|
"""Client without token can still be created."""
|
|
42
54
|
httpx_mock.add_response(url="https://codeberg.org/api/v1/version", json={"version": "1.22"})
|