phabfive 0.6.0__tar.gz → 0.7.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.
- phabfive-0.7.1/.github/workflows/release.yml +191 -0
- phabfive-0.7.1/AGENTS.md +89 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/CHANGELOG.md +37 -0
- phabfive-0.7.1/CLAUDE.md +1 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/PKG-INFO +3 -3
- {phabfive-0.6.0 → phabfive-0.7.1}/README.md +1 -1
- {phabfive-0.6.0 → phabfive-0.7.1}/docs/releasing.md +19 -3
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/__init__.py +0 -5
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/cli.py +82 -30
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/maniphest/resolvers.py +78 -12
- phabfive-0.7.1/phabfive/setup.py +359 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/pyproject.toml +2 -2
- {phabfive-0.6.0 → phabfive-0.7.1}/tests/test_maniphest.py +42 -4
- {phabfive-0.6.0 → phabfive-0.7.1}/tests/test_phabfive.py +4 -4
- phabfive-0.7.1/tests/test_setup.py +211 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/uv.lock +1 -1
- phabfive-0.6.0/.github/workflows/release.yml +0 -78
- {phabfive-0.6.0 → phabfive-0.7.1}/.editorconfig +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.flake8 +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.github/ISSUE_TEMPLATE/documentation_change.md +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.github/dependabot.yml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.github/workflows/gitleaks.yml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.github/workflows/tox.yml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.gitignore +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.gitleaksignore +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.mailmap +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.pre-commit-config.yaml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.readthedocs.yaml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.travis.yml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.vscode/settings.json +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/.yamllint.yml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/Dockerfile +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/Dockerfile.phorge +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/LICENSE +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/MANIFEST.in +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/Makefile +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/compose.yml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/docs/create-templates.md +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/docs/development.md +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/docs/index.md +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/docs/maniphest-cli.md +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/docs/maniphest-show.png +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/docs/phorge-setup.md +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/docs/search-templates.md +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/mkdocs.yml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/constants.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/core.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/diffusion/__init__.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/diffusion/core.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/diffusion/fetchers.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/diffusion/formatters.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/diffusion/resolvers.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/diffusion/validators.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/exceptions.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/maniphest/__init__.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/maniphest/core.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/maniphest/fetchers.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/maniphest/filters.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/maniphest/formatters.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/maniphest/utils.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/maniphest/validators.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/passphrase.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/paste.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/project_filters.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/repl.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/transitions/__init__.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/transitions/base.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/transitions/column.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/transitions/priority.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/transitions/status.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phabfive/user.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phorge/entrypoint.sh +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phorge/init-phorge.sh +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phorge/lib/common.sh +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phorge/lib/setup-auth.sh +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phorge/lib/setup-projects.sh +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phorge/lib/setup-tokens.sh +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phorge/lib/setup-users.sh +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phorge/phabfive +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phorge/phorge.conf +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/phorge/phorge.ini +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/templates/task-create/mega-2024-simulation.yml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/templates/task-create/test-template-v2.yml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/templates/task-create/test-template.yaml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/templates/task-search/blocked-tasks.yaml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/templates/task-search/development-workflow-audit.yaml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/templates/task-search/escalated-priorities.yaml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/templates/task-search/high-priority-stale-tasks.yaml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/templates/task-search/project-status-overview.yaml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/templates/task-search/recently-moved-to-review.yaml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/templates/task-search/tasks-resolved-but-not-in-done.yaml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/templates/task-search/team-productivity-report.yaml +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/tests/__init__.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/tests/test_arcrc.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/tests/test_constants.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/tests/test_priority_transitions.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/tests/test_project_filters.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/tests/test_status_transitions.py +0 -0
- {phabfive-0.6.0 → phabfive-0.7.1}/tox.ini +0 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Release
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
tags:
|
|
7
|
+
- "v*"
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: release-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: false
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
name: Build distribution
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout repository
|
|
19
|
+
uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v6
|
|
23
|
+
with:
|
|
24
|
+
enable-cache: true
|
|
25
|
+
|
|
26
|
+
- name: Set up Python
|
|
27
|
+
run: uv python install 3.13
|
|
28
|
+
|
|
29
|
+
- name: Build package
|
|
30
|
+
run: uv build
|
|
31
|
+
|
|
32
|
+
- name: Upload distribution artifacts
|
|
33
|
+
uses: actions/upload-artifact@v4
|
|
34
|
+
with:
|
|
35
|
+
name: python-package-distributions
|
|
36
|
+
path: dist/
|
|
37
|
+
|
|
38
|
+
build-executables:
|
|
39
|
+
name: Build ${{ matrix.os }} ${{ matrix.arch }} executable
|
|
40
|
+
runs-on: ${{ matrix.runner }}
|
|
41
|
+
permissions:
|
|
42
|
+
id-token: write
|
|
43
|
+
contents: read
|
|
44
|
+
strategy:
|
|
45
|
+
fail-fast: false
|
|
46
|
+
matrix:
|
|
47
|
+
include:
|
|
48
|
+
# AMD64 builds
|
|
49
|
+
- os: linux
|
|
50
|
+
arch: amd64
|
|
51
|
+
runner: ubuntu-latest
|
|
52
|
+
artifact_name: phabfive
|
|
53
|
+
asset_name: phabfive-linux-amd64
|
|
54
|
+
- os: macos
|
|
55
|
+
arch: amd64
|
|
56
|
+
runner: macos-14
|
|
57
|
+
artifact_name: phabfive
|
|
58
|
+
asset_name: phabfive-macos-amd64
|
|
59
|
+
- os: windows
|
|
60
|
+
arch: amd64
|
|
61
|
+
runner: windows-latest
|
|
62
|
+
artifact_name: phabfive.exe
|
|
63
|
+
asset_name: phabfive-windows-amd64.exe
|
|
64
|
+
# ARM64 builds
|
|
65
|
+
- os: linux
|
|
66
|
+
arch: arm64
|
|
67
|
+
runner: ubuntu-24.04-arm
|
|
68
|
+
artifact_name: phabfive
|
|
69
|
+
asset_name: phabfive-linux-arm64
|
|
70
|
+
- os: macos
|
|
71
|
+
arch: arm64
|
|
72
|
+
runner: macos-latest
|
|
73
|
+
artifact_name: phabfive
|
|
74
|
+
asset_name: phabfive-macos-arm64
|
|
75
|
+
- os: windows
|
|
76
|
+
arch: arm64
|
|
77
|
+
runner: windows-11-arm
|
|
78
|
+
artifact_name: phabfive.exe
|
|
79
|
+
asset_name: phabfive-windows-arm64.exe
|
|
80
|
+
skip_signing: true
|
|
81
|
+
steps:
|
|
82
|
+
- name: Checkout repository
|
|
83
|
+
uses: actions/checkout@v4
|
|
84
|
+
|
|
85
|
+
- name: Install Cosign
|
|
86
|
+
if: ${{ !matrix.skip_signing }}
|
|
87
|
+
uses: sigstore/cosign-installer@v3
|
|
88
|
+
|
|
89
|
+
- name: Set up Python
|
|
90
|
+
uses: actions/setup-python@v5
|
|
91
|
+
with:
|
|
92
|
+
python-version: "3.12"
|
|
93
|
+
|
|
94
|
+
- name: Install dependencies
|
|
95
|
+
run: |
|
|
96
|
+
python -m pip install --upgrade pip
|
|
97
|
+
pip install pyinstaller
|
|
98
|
+
pip install .
|
|
99
|
+
|
|
100
|
+
- name: Build executable
|
|
101
|
+
shell: bash
|
|
102
|
+
run: |
|
|
103
|
+
pyinstaller --onefile --name phabfive phabfive/cli.py \
|
|
104
|
+
--collect-data rich \
|
|
105
|
+
--copy-metadata rich \
|
|
106
|
+
--collect-data phabricator \
|
|
107
|
+
--hidden-import phabricator \
|
|
108
|
+
--hidden-import anyconfig.backend.yaml \
|
|
109
|
+
--exclude-module mkdocs
|
|
110
|
+
|
|
111
|
+
- name: Rename artifact
|
|
112
|
+
shell: bash
|
|
113
|
+
run: mv dist/${{ matrix.artifact_name }} dist/${{ matrix.asset_name }}
|
|
114
|
+
|
|
115
|
+
- name: Sign executable with Sigstore
|
|
116
|
+
if: ${{ !matrix.skip_signing }}
|
|
117
|
+
shell: bash
|
|
118
|
+
run: |
|
|
119
|
+
cosign sign-blob dist/${{ matrix.asset_name }} \
|
|
120
|
+
--bundle dist/${{ matrix.asset_name }}.sigstore.json \
|
|
121
|
+
--yes
|
|
122
|
+
|
|
123
|
+
- name: Upload executable and signature
|
|
124
|
+
if: ${{ !matrix.skip_signing }}
|
|
125
|
+
uses: actions/upload-artifact@v4
|
|
126
|
+
with:
|
|
127
|
+
name: ${{ matrix.asset_name }}
|
|
128
|
+
path: |
|
|
129
|
+
dist/${{ matrix.asset_name }}
|
|
130
|
+
dist/${{ matrix.asset_name }}.sigstore.json
|
|
131
|
+
|
|
132
|
+
- name: Upload executable (unsigned)
|
|
133
|
+
if: ${{ matrix.skip_signing }}
|
|
134
|
+
uses: actions/upload-artifact@v4
|
|
135
|
+
with:
|
|
136
|
+
name: ${{ matrix.asset_name }}
|
|
137
|
+
path: dist/${{ matrix.asset_name }}
|
|
138
|
+
|
|
139
|
+
publish-pypi:
|
|
140
|
+
name: Publish to PyPI
|
|
141
|
+
needs: build
|
|
142
|
+
if: ${{ !contains(github.ref_name, '-rc') }}
|
|
143
|
+
runs-on: ubuntu-latest
|
|
144
|
+
environment:
|
|
145
|
+
name: pypi
|
|
146
|
+
url: https://pypi.org/p/phabfive
|
|
147
|
+
permissions:
|
|
148
|
+
id-token: write
|
|
149
|
+
steps:
|
|
150
|
+
- name: Download distribution artifacts
|
|
151
|
+
uses: actions/download-artifact@v4
|
|
152
|
+
with:
|
|
153
|
+
name: python-package-distributions
|
|
154
|
+
path: dist/
|
|
155
|
+
|
|
156
|
+
- name: Publish to PyPI
|
|
157
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
158
|
+
|
|
159
|
+
github-release:
|
|
160
|
+
name: Create GitHub Release
|
|
161
|
+
needs: [build, publish-pypi, build-executables]
|
|
162
|
+
if: ${{ always() && needs.build.result == 'success' && needs.build-executables.result == 'success' && (needs.publish-pypi.result == 'success' || needs.publish-pypi.result == 'skipped') }}
|
|
163
|
+
runs-on: ubuntu-latest
|
|
164
|
+
permissions:
|
|
165
|
+
contents: write
|
|
166
|
+
steps:
|
|
167
|
+
- name: Checkout repository
|
|
168
|
+
uses: actions/checkout@v4
|
|
169
|
+
|
|
170
|
+
- name: Download all artifacts
|
|
171
|
+
uses: actions/download-artifact@v4
|
|
172
|
+
with:
|
|
173
|
+
path: artifacts/
|
|
174
|
+
|
|
175
|
+
- name: Prepare release assets
|
|
176
|
+
run: |
|
|
177
|
+
mkdir -p release-assets
|
|
178
|
+
# Copy Python distributions
|
|
179
|
+
cp artifacts/python-package-distributions/* release-assets/
|
|
180
|
+
# Copy executables
|
|
181
|
+
for dir in artifacts/phabfive-*/; do
|
|
182
|
+
cp "$dir"* release-assets/ 2>/dev/null || true
|
|
183
|
+
done
|
|
184
|
+
ls -la release-assets/
|
|
185
|
+
|
|
186
|
+
- name: Create GitHub Release
|
|
187
|
+
uses: softprops/action-gh-release@v2
|
|
188
|
+
with:
|
|
189
|
+
generate_release_notes: true
|
|
190
|
+
files: |
|
|
191
|
+
release-assets/*
|
phabfive-0.7.1/AGENTS.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to AI coding agents when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
phabfive is a CLI for Phabricator and Phorge. It provides commands for interacting with Passphrase, Diffusion, Paste, User, and Maniphest applications.
|
|
8
|
+
|
|
9
|
+
## Common Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install dependencies
|
|
13
|
+
uv sync --group dev
|
|
14
|
+
|
|
15
|
+
# Run the CLI
|
|
16
|
+
uv run phabfive --help
|
|
17
|
+
|
|
18
|
+
# Run tests
|
|
19
|
+
uv run pytest # quick test with current Python
|
|
20
|
+
uv run pytest tests/test_foo.py # run single test file
|
|
21
|
+
uv run pytest -k test_name # run specific test
|
|
22
|
+
uv run tox # test all Python versions (3.10-3.14)
|
|
23
|
+
|
|
24
|
+
# Lint
|
|
25
|
+
uv run flake8 phabfive/ tests/
|
|
26
|
+
|
|
27
|
+
# Local Phorge instance for testing
|
|
28
|
+
make up # start local Phorge
|
|
29
|
+
make down # stop containers
|
|
30
|
+
|
|
31
|
+
# Merge PRs (rebase only - merge and squash are disabled)
|
|
32
|
+
gh pr merge --rebase
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Architecture
|
|
36
|
+
|
|
37
|
+
### CLI Layer (`cli.py`)
|
|
38
|
+
- Uses `docopt-ng` for argument parsing with docstring-based CLI definitions
|
|
39
|
+
- Monogram shortcuts allow direct access: `phabfive T123` expands to `phabfive maniphest show T123`
|
|
40
|
+
- Entry point: `cli_entrypoint()` → `parse_cli()`
|
|
41
|
+
|
|
42
|
+
### Core Layer (`core.py`)
|
|
43
|
+
- `Phabfive` class: central configuration and API client management
|
|
44
|
+
- Loads config from environment variables or `~/.config/phabfive.yaml`
|
|
45
|
+
- Uses the `phabricator` library for Conduit API calls
|
|
46
|
+
- Output formatting: supports `rich` (terminal), `yaml`, and `strict` (machine-readable) modes
|
|
47
|
+
|
|
48
|
+
### Feature Modules
|
|
49
|
+
Complex features use a consistent subpackage structure:
|
|
50
|
+
- `diffusion/` and `maniphest/` follow this pattern:
|
|
51
|
+
- `core.py` - main class inheriting from `Phabfive`
|
|
52
|
+
- `fetchers.py` - API data fetching
|
|
53
|
+
- `resolvers.py` - ID/name resolution
|
|
54
|
+
- `formatters.py` - output formatting
|
|
55
|
+
- `validators.py` - input validation
|
|
56
|
+
|
|
57
|
+
### Simpler Modules
|
|
58
|
+
- `passphrase.py`, `paste.py`, `user.py` - single-file implementations
|
|
59
|
+
- `transitions/` - state machine for task status/priority/column changes
|
|
60
|
+
|
|
61
|
+
### Configuration
|
|
62
|
+
Required: `PHAB_TOKEN` and `PHAB_URL` (via environment or config file)
|
|
63
|
+
|
|
64
|
+
## Version Management
|
|
65
|
+
|
|
66
|
+
Version is defined only in `pyproject.toml`. Access it via:
|
|
67
|
+
```python
|
|
68
|
+
from importlib.metadata import version
|
|
69
|
+
version("phabfive")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Release Workflow
|
|
73
|
+
|
|
74
|
+
Releases are triggered by pushing a git tag matching `v*`:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git tag -a v0.7.0 -m "Release v0.7.0"
|
|
78
|
+
git push origin v0.7.0
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Artifacts produced:**
|
|
82
|
+
- Python wheel and sdist → PyPI
|
|
83
|
+
- Standalone executables for 6 platforms → GitHub Releases:
|
|
84
|
+
- `phabfive-linux-amd64`, `phabfive-linux-arm64`
|
|
85
|
+
- `phabfive-macos-amd64`, `phabfive-macos-arm64`
|
|
86
|
+
- `phabfive-windows-amd64.exe`, `phabfive-windows-arm64.exe`
|
|
87
|
+
- Sigstore signatures (`.sigstore.json`) for all executables except Windows ARM64
|
|
88
|
+
|
|
89
|
+
**RC tags** (containing `-rc`) skip PyPI but still build executables and create GitHub releases.
|
|
@@ -1,3 +1,40 @@
|
|
|
1
|
+
# 0.7.1 (2026-03-07)
|
|
2
|
+
|
|
3
|
+
## Bug Fixes
|
|
4
|
+
|
|
5
|
+
* Fix standalone executables exiting silently without output
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# 0.7.0 (2026-03-07)
|
|
9
|
+
|
|
10
|
+
## Prelude
|
|
11
|
+
|
|
12
|
+
This release adds interactive first-run configuration, standalone executables for all major platforms, and improved developer experience with Sigstore-signed releases.
|
|
13
|
+
|
|
14
|
+
## New Features
|
|
15
|
+
|
|
16
|
+
### Interactive Setup
|
|
17
|
+
* **First-run configuration** - New `phabfive user setup` command guides users through initial configuration with interactive prompts
|
|
18
|
+
* **Secure token input** - API token input is masked with dots for security
|
|
19
|
+
* **Smart reconfiguration** - Warns before overwriting existing working configuration
|
|
20
|
+
|
|
21
|
+
### Release Artifacts
|
|
22
|
+
* **Standalone executables** - Pre-built binaries for Linux, macOS, and Windows (AMD64 and ARM64)
|
|
23
|
+
* **Sigstore signing** - All executables are cryptographically signed for verification
|
|
24
|
+
* **RC tag support** - Release candidates skip PyPI for testing the release process
|
|
25
|
+
|
|
26
|
+
## Bug Fixes
|
|
27
|
+
|
|
28
|
+
* Fix Rich markup escaping for square brackets in user content
|
|
29
|
+
* Fix project lookup failing when more than 100 projects exist
|
|
30
|
+
|
|
31
|
+
## Other Notes
|
|
32
|
+
|
|
33
|
+
* Version now sourced solely from `pyproject.toml` using `importlib.metadata`
|
|
34
|
+
* Added `AGENTS.md` for AI coding agent guidance
|
|
35
|
+
* Removed unused `__author__`, `__email__`, `__url__` module attributes
|
|
36
|
+
|
|
37
|
+
|
|
1
38
|
# 0.6.0 (2026-03-07)
|
|
2
39
|
|
|
3
40
|
## Prelude
|
phabfive-0.7.1/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
AGENTS.md
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: phabfive
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.7.1
|
|
4
|
+
Summary: CLI for Phabricator and Phorge - built for humans and AI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/dynamist/phabfive
|
|
6
6
|
Project-URL: Download, https://github.com/dynamist/phabfive/releases
|
|
7
7
|
Author-email: Rickard Eriksson <rickard@dynamist.se>
|
|
@@ -41,7 +41,7 @@ Description-Content-Type: text/markdown
|
|
|
41
41
|
|
|
42
42
|
# phabfive
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
CLI for [Phabricator](https://www.phacility.com/phabricator/) and [Phorge](https://we.phorge.it/) - built for humans and AI agents.
|
|
45
45
|
|
|
46
46
|

|
|
47
47
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# phabfive
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for [Phabricator](https://www.phacility.com/phabricator/) and [Phorge](https://we.phorge.it/) - built for humans and AI agents.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
@@ -77,9 +77,25 @@ git push origin v0.6.0
|
|
|
77
77
|
|
|
78
78
|
This triggers the GitHub Actions workflow which will:
|
|
79
79
|
|
|
80
|
-
1. Build the package
|
|
81
|
-
2.
|
|
82
|
-
|
|
80
|
+
1. Build the Python package (wheel and sdist)
|
|
81
|
+
2. Build standalone executables for 6 platforms:
|
|
82
|
+
- Linux (AMD64, ARM64)
|
|
83
|
+
- macOS (AMD64, ARM64)
|
|
84
|
+
- Windows (AMD64, ARM64)
|
|
85
|
+
3. Sign executables with [Sigstore](https://www.sigstore.dev/) (keyless OIDC signing)
|
|
86
|
+
4. Publish to PyPI using trusted publishing
|
|
87
|
+
5. Create GitHub Release with auto-generated notes and all artifacts
|
|
88
|
+
|
|
89
|
+
**Testing with RC tags:** Tags containing `-rc` (e.g., `v0.7.0-rc.1`) will skip PyPI publishing but still build executables and create a GitHub Release. Useful for testing the release process.
|
|
90
|
+
|
|
91
|
+
**Verifying signatures:** Users can verify downloaded executables with [cosign](https://docs.sigstore.dev/):
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
cosign verify-blob phabfive-linux-amd64 \
|
|
95
|
+
--bundle phabfive-linux-amd64.sigstore.json \
|
|
96
|
+
--certificate-identity-regexp="https://github.com/dynamist/phabfive" \
|
|
97
|
+
--certificate-oidc-issuer="https://token.actions.githubusercontent.com"
|
|
98
|
+
```
|
|
83
99
|
|
|
84
100
|
**5. Bump to Dev Version**
|
|
85
101
|
|
|
@@ -5,10 +5,12 @@ import logging
|
|
|
5
5
|
import os
|
|
6
6
|
import re
|
|
7
7
|
import sys
|
|
8
|
+
from importlib.metadata import version
|
|
8
9
|
from io import StringIO
|
|
9
10
|
|
|
10
11
|
# 3rd party imports
|
|
11
12
|
from docopt import DocoptExit, Option, docopt, extras
|
|
13
|
+
from rich.markup import escape
|
|
12
14
|
from rich.text import Text
|
|
13
15
|
from rich.tree import Tree
|
|
14
16
|
from ruamel.yaml import YAML
|
|
@@ -25,6 +27,32 @@ log = logging.getLogger(__name__)
|
|
|
25
27
|
# =============================================================================
|
|
26
28
|
|
|
27
29
|
|
|
30
|
+
def _escape_for_rich(content):
|
|
31
|
+
"""Escape user content for safe Rich printing.
|
|
32
|
+
|
|
33
|
+
Rich interprets square brackets [...] as markup syntax.
|
|
34
|
+
This causes issues with user content containing:
|
|
35
|
+
- [/path] patterns -> MarkupError crash (interpreted as closing tag)
|
|
36
|
+
- [bug] tags -> Silently stripped (interpreted as invalid style)
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
content : any
|
|
41
|
+
User-provided content to escape
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
str or Text
|
|
46
|
+
Empty string for None, Text objects unchanged, escaped string otherwise
|
|
47
|
+
"""
|
|
48
|
+
if content is None:
|
|
49
|
+
return ""
|
|
50
|
+
if isinstance(content, Text):
|
|
51
|
+
# Text objects are already safe Rich objects (e.g., hyperlinks)
|
|
52
|
+
return content
|
|
53
|
+
return escape(str(content))
|
|
54
|
+
|
|
55
|
+
|
|
28
56
|
def _needs_yaml_quoting(value):
|
|
29
57
|
"""Check if a string value needs YAML quoting.
|
|
30
58
|
|
|
@@ -71,12 +99,12 @@ def display_task_rich(console, task_dict, phabfive_instance):
|
|
|
71
99
|
# Multi-line value
|
|
72
100
|
console.print(f" {key}: |-")
|
|
73
101
|
for line in str(value).splitlines():
|
|
74
|
-
console.print(f" {line}")
|
|
102
|
+
console.print(f" {_escape_for_rich(line)}")
|
|
75
103
|
elif _needs_yaml_quoting(value):
|
|
76
|
-
escaped = value.replace("'", "''")
|
|
77
|
-
console.print(f" {key}: '{escaped}'")
|
|
104
|
+
escaped = str(value).replace("'", "''")
|
|
105
|
+
console.print(f" {key}: '{_escape_for_rich(escaped)}'")
|
|
78
106
|
else:
|
|
79
|
-
console.print(f" {key}: {value}")
|
|
107
|
+
console.print(f" {key}: {_escape_for_rich(value)}")
|
|
80
108
|
|
|
81
109
|
# Print Assignee
|
|
82
110
|
if assignee:
|
|
@@ -126,10 +154,10 @@ def display_task_rich(console, task_dict, phabfive_instance):
|
|
|
126
154
|
)
|
|
127
155
|
continue
|
|
128
156
|
if _needs_yaml_quoting(value):
|
|
129
|
-
escaped = value.replace("'", "''")
|
|
130
|
-
console.print(f" {key}: '{escaped}'")
|
|
157
|
+
escaped = str(value).replace("'", "''")
|
|
158
|
+
console.print(f" {key}: '{_escape_for_rich(escaped)}'")
|
|
131
159
|
else:
|
|
132
|
-
console.print(f" {key}: {value}")
|
|
160
|
+
console.print(f" {key}: {_escape_for_rich(value)}")
|
|
133
161
|
|
|
134
162
|
# Print History section
|
|
135
163
|
if history:
|
|
@@ -138,13 +166,13 @@ def display_task_rich(console, task_dict, phabfive_instance):
|
|
|
138
166
|
if hist_key == "Boards" and isinstance(hist_value, dict):
|
|
139
167
|
console.print(" Boards:")
|
|
140
168
|
for board_name, transitions in hist_value.items():
|
|
141
|
-
console.print(f" {board_name}:")
|
|
169
|
+
console.print(f" {_escape_for_rich(board_name)}:")
|
|
142
170
|
for trans in transitions:
|
|
143
|
-
console.print(f" - {trans}")
|
|
171
|
+
console.print(f" - {_escape_for_rich(trans)}")
|
|
144
172
|
elif isinstance(hist_value, list):
|
|
145
173
|
console.print(f" {hist_key}:")
|
|
146
174
|
for trans in hist_value:
|
|
147
|
-
console.print(f" - {trans}")
|
|
175
|
+
console.print(f" - {_escape_for_rich(trans)}")
|
|
148
176
|
|
|
149
177
|
# Print Comments section
|
|
150
178
|
comments = task_dict.get("Comments", [])
|
|
@@ -154,11 +182,11 @@ def display_task_rich(console, task_dict, phabfive_instance):
|
|
|
154
182
|
if isinstance(comment, PreservedScalarString) or "\n" in str(comment):
|
|
155
183
|
# Multi-line comment
|
|
156
184
|
lines = str(comment).splitlines()
|
|
157
|
-
console.print(f" - {lines[0]}")
|
|
185
|
+
console.print(f" - {_escape_for_rich(lines[0])}")
|
|
158
186
|
for line in lines[1:]:
|
|
159
|
-
console.print(f" {line}")
|
|
187
|
+
console.print(f" {_escape_for_rich(line)}")
|
|
160
188
|
else:
|
|
161
|
-
console.print(f" - {comment}")
|
|
189
|
+
console.print(f" - {_escape_for_rich(comment)}")
|
|
162
190
|
|
|
163
191
|
# Print Metadata section
|
|
164
192
|
if metadata:
|
|
@@ -168,11 +196,11 @@ def display_task_rich(console, task_dict, phabfive_instance):
|
|
|
168
196
|
if meta_value:
|
|
169
197
|
console.print(f" {meta_key}:")
|
|
170
198
|
for item in meta_value:
|
|
171
|
-
console.print(f" - {item}")
|
|
199
|
+
console.print(f" - {_escape_for_rich(item)}")
|
|
172
200
|
else:
|
|
173
201
|
console.print(f" {meta_key}: []")
|
|
174
202
|
else:
|
|
175
|
-
console.print(f" {meta_key}: {meta_value}")
|
|
203
|
+
console.print(f" {meta_key}: {_escape_for_rich(meta_value)}")
|
|
176
204
|
|
|
177
205
|
|
|
178
206
|
def display_task_tree(console, task_dict, phabfive_instance):
|
|
@@ -207,9 +235,9 @@ def display_task_tree(console, task_dict, phabfive_instance):
|
|
|
207
235
|
first_line = str(value).split("\n")[0]
|
|
208
236
|
if len(first_line) > 60:
|
|
209
237
|
first_line = first_line[:57] + "..."
|
|
210
|
-
task_branch.add(f"{key}: {first_line}")
|
|
238
|
+
task_branch.add(f"{key}: {_escape_for_rich(first_line)}")
|
|
211
239
|
else:
|
|
212
|
-
task_branch.add(f"{key}: {value}")
|
|
240
|
+
task_branch.add(f"{key}: {_escape_for_rich(value)}")
|
|
213
241
|
|
|
214
242
|
# Add Assignee
|
|
215
243
|
if assignee:
|
|
@@ -243,7 +271,7 @@ def display_task_tree(console, task_dict, phabfive_instance):
|
|
|
243
271
|
)
|
|
244
272
|
board_branch.add(Text.assemble("Column: ", column_link))
|
|
245
273
|
continue
|
|
246
|
-
board_branch.add(f"{key}: {value}")
|
|
274
|
+
board_branch.add(f"{key}: {_escape_for_rich(value)}")
|
|
247
275
|
|
|
248
276
|
# Add History section
|
|
249
277
|
if history:
|
|
@@ -252,13 +280,13 @@ def display_task_tree(console, task_dict, phabfive_instance):
|
|
|
252
280
|
if hist_key == "Boards" and isinstance(hist_value, dict):
|
|
253
281
|
boards_hist = history_branch.add("Boards")
|
|
254
282
|
for board_name, transitions in hist_value.items():
|
|
255
|
-
board_hist = boards_hist.add(board_name)
|
|
283
|
+
board_hist = boards_hist.add(_escape_for_rich(board_name))
|
|
256
284
|
for trans in transitions:
|
|
257
|
-
board_hist.add(trans)
|
|
285
|
+
board_hist.add(_escape_for_rich(trans))
|
|
258
286
|
elif isinstance(hist_value, list):
|
|
259
287
|
hist_type_branch = history_branch.add(hist_key)
|
|
260
288
|
for trans in hist_value:
|
|
261
|
-
hist_type_branch.add(trans)
|
|
289
|
+
hist_type_branch.add(_escape_for_rich(trans))
|
|
262
290
|
|
|
263
291
|
# Add Comments section
|
|
264
292
|
comments = task_dict.get("Comments", [])
|
|
@@ -270,9 +298,9 @@ def display_task_tree(console, task_dict, phabfive_instance):
|
|
|
270
298
|
first_line = str(comment).split("\n")[0]
|
|
271
299
|
if len(first_line) > 60:
|
|
272
300
|
first_line = first_line[:57] + "..."
|
|
273
|
-
comments_branch.add(first_line)
|
|
301
|
+
comments_branch.add(_escape_for_rich(first_line))
|
|
274
302
|
else:
|
|
275
|
-
comments_branch.add(str(comment))
|
|
303
|
+
comments_branch.add(_escape_for_rich(str(comment)))
|
|
276
304
|
|
|
277
305
|
# Add Metadata section
|
|
278
306
|
if metadata:
|
|
@@ -282,11 +310,11 @@ def display_task_tree(console, task_dict, phabfive_instance):
|
|
|
282
310
|
if meta_value:
|
|
283
311
|
list_branch = meta_branch.add(meta_key)
|
|
284
312
|
for item in meta_value:
|
|
285
|
-
list_branch.add(str(item))
|
|
313
|
+
list_branch.add(_escape_for_rich(str(item)))
|
|
286
314
|
else:
|
|
287
315
|
meta_branch.add(f"{meta_key}: []")
|
|
288
316
|
else:
|
|
289
|
-
meta_branch.add(f"{meta_key}: {meta_value}")
|
|
317
|
+
meta_branch.add(f"{meta_key}: {_escape_for_rich(meta_value)}")
|
|
290
318
|
|
|
291
319
|
console.print(tree)
|
|
292
320
|
|
|
@@ -408,7 +436,7 @@ Available phabfive commands are:
|
|
|
408
436
|
maniphest The maniphest app
|
|
409
437
|
paste The paste app
|
|
410
438
|
repl Enter a REPL with API access
|
|
411
|
-
user Information on users
|
|
439
|
+
user Information on users, setup wizard
|
|
412
440
|
|
|
413
441
|
Shortcuts to view Phabricator monograms (example: phabfive T123):
|
|
414
442
|
|
|
@@ -489,9 +517,12 @@ Paste Create Options:
|
|
|
489
517
|
sub_user_args = """
|
|
490
518
|
Usage:
|
|
491
519
|
phabfive user whoami [options]
|
|
520
|
+
phabfive user setup [options]
|
|
492
521
|
|
|
493
522
|
Options:
|
|
494
523
|
-h, --help Show this help message and exit
|
|
524
|
+
|
|
525
|
+
The setup command configures phabfive with your Phabricator URL and API token.
|
|
495
526
|
"""
|
|
496
527
|
|
|
497
528
|
sub_repl_args = """
|
|
@@ -681,17 +712,19 @@ def parse_cli():
|
|
|
681
712
|
"""
|
|
682
713
|
import phabfive
|
|
683
714
|
|
|
715
|
+
pkg_version = version("phabfive")
|
|
716
|
+
|
|
684
717
|
try:
|
|
685
718
|
cli_args = docopt(
|
|
686
719
|
base_args,
|
|
687
720
|
options_first=True,
|
|
688
|
-
version=
|
|
721
|
+
version=pkg_version,
|
|
689
722
|
default_help=True,
|
|
690
723
|
)
|
|
691
724
|
except DocoptExit:
|
|
692
725
|
extras(
|
|
693
726
|
True,
|
|
694
|
-
|
|
727
|
+
pkg_version,
|
|
695
728
|
[Option("-h", "--help", 0, True)],
|
|
696
729
|
base_args,
|
|
697
730
|
)
|
|
@@ -778,7 +811,7 @@ def parse_cli():
|
|
|
778
811
|
else:
|
|
779
812
|
extras(
|
|
780
813
|
True,
|
|
781
|
-
|
|
814
|
+
pkg_version,
|
|
782
815
|
[Option("-h", "--help", 0, True)],
|
|
783
816
|
base_args,
|
|
784
817
|
)
|
|
@@ -798,7 +831,7 @@ def run(cli_args, sub_args):
|
|
|
798
831
|
from phabfive import diffusion, maniphest, passphrase, paste, repl, user
|
|
799
832
|
from phabfive.constants import REPO_STATUS_CHOICES
|
|
800
833
|
from phabfive.core import Phabfive
|
|
801
|
-
from phabfive.exceptions import PhabfiveException
|
|
834
|
+
from phabfive.exceptions import PhabfiveConfigException, PhabfiveException
|
|
802
835
|
from phabfive.transitions import parse_column_patterns, parse_priority_patterns
|
|
803
836
|
|
|
804
837
|
# Validate and process output options
|
|
@@ -961,6 +994,13 @@ def run(cli_args, sub_args):
|
|
|
961
994
|
print(f"{p['id']} {p['title']}")
|
|
962
995
|
|
|
963
996
|
if cli_args["<command>"] == "user":
|
|
997
|
+
# Handle setup first - it doesn't require existing configuration
|
|
998
|
+
if sub_args.get("setup"):
|
|
999
|
+
from phabfive.setup import SetupWizard
|
|
1000
|
+
|
|
1001
|
+
wizard = SetupWizard()
|
|
1002
|
+
return 0 if wizard.run() else 1
|
|
1003
|
+
|
|
964
1004
|
user_app = user.User()
|
|
965
1005
|
|
|
966
1006
|
if sub_args["whoami"]:
|
|
@@ -1232,6 +1272,14 @@ def run(cli_args, sub_args):
|
|
|
1232
1272
|
show_comments=show_comments,
|
|
1233
1273
|
)
|
|
1234
1274
|
display_tasks(result, output_format, maniphest_app)
|
|
1275
|
+
except PhabfiveConfigException as e:
|
|
1276
|
+
# Offer interactive setup for configuration errors
|
|
1277
|
+
from phabfive.setup import offer_setup_on_error
|
|
1278
|
+
|
|
1279
|
+
if offer_setup_on_error(str(e)):
|
|
1280
|
+
# Setup succeeded - connection verified
|
|
1281
|
+
return 0
|
|
1282
|
+
retcode = 1
|
|
1235
1283
|
except PhabfiveException as e:
|
|
1236
1284
|
# Catch all types of phabricator base exceptions
|
|
1237
1285
|
log.critical(str(e))
|
|
@@ -1264,3 +1312,7 @@ def cli_entrypoint():
|
|
|
1264
1312
|
sys.exit(130) # Standard Unix exit code for SIGINT (128 + 2)
|
|
1265
1313
|
except Exception:
|
|
1266
1314
|
raise
|
|
1315
|
+
|
|
1316
|
+
|
|
1317
|
+
if __name__ == "__main__":
|
|
1318
|
+
cli_entrypoint()
|