A terminal UI for the loop a developer actually runs all day: pick up a Jira issue, start a branch for it, open the pull or merge request, and tell the team in Slack, Teams or Discord — without leaving the keyboard or reconstructing the same context in three browser tabs.
The whole loop works, and is new: expect rough edges, and a configuration format that may still change before 1.0.
workflowopens the TUI. Pick up an assigned Jira issue, comment on it or change its status, branch for it, stage and commit through the repository's own hooks — opening a failure at its line in$EDITOR— push, open the pull or merge request from the repository's template, follow its CI, and announce it to your team.?lists the keys.workflow --dry-rundoes all of that with every write held back, saying what it would have done.workflow --webserves the same loop in a browser, on127.0.0.1alone — issues, branch, commit, push, the pull request and its announcement, your review queue and the settings — pushed live as the repository changes.- With no Jira configured, the Issues pane lists the issues assigned to you on your forge (GitHub or GitLab) instead — pick one up, branch for it, and the pull request closes it on merge.
- A repository with hooks in
.git/hooksand no lefthook configuration is offered alefthook.ymlthat runs them. workflow doctorreports the repository, tooling and configuration in effect;workflow doctor --onlineasks Jira, your messaging service and your forge whether each credential actually works.workflow config initwrites a starting configuration file, andworkflow config showprints the one in effect, credentials masked.workflow standupdrafts what you did — your recent commits, the issues you touched and the open pull requests on your branches — for you to edit and, optionally, post to your team.workflow reviewslists the pull requests on your forge that are waiting on your review — the longest-waiting first, with the author, how CI stands and how long each has waited.
With Go:
go install github.com/jacob-delgado/workflow/cmd/workflow@latestThe binary lands in $(go env GOPATH)/bin, which needs to be on your PATH.
For a reproducible install, name the version instead: @v0.0.5. Note that
@latest resolves to the newest release tag, and until the first tag exists, to
the most recent commit on main.
From a release — binaries are published for macOS (arm64), Linux (amd64) and Windows (amd64), each with a checksum and a build provenance attestation:
sha256sum -c SHA256SUMS --ignore-missing
gh attestation verify workflow_darwin_arm64 --repo jacob-delgado/workflowFrom source, using mise for the pinned toolchain and go-task as the runner:
git clone https://github.com/jacob-delgado/workflow.git
cd workflow
mise trust && mise install
task build # builds bin/workflowThere is also a devcontainer (VS Code, GoLand, Codespaces, or the devcontainer
CLI), and a build container for running the same checks without installing
anything: task container:check.
workflow config init # writes .workflow.json here, readable only by you
workflow doctor # says what is still missing.workflow.json looks like this:
{
"jira": {
"base_url": "https://jira.example.com",
"token": "",
"user": ""
},
"messaging": {
"kind": "slack",
"token": "",
"webhook_url": "",
"channel": "#dev-workflow"
},
"forge": {
"kind": "",
"host": "",
"token": ""
},
"ui": {
"mouse": true,
"ascii": false
}
}workflow reads .workflow.json from the current directory, and falls back to
your home directory. A file in the current directory replaces the one in your
home directory — they are never merged, so a repository-local configuration is
always the whole story.
workflow keeps a little state between sessions in an on-disk store — the commit
scope you last used, which pull requests you have announced, and the last issue
list it saw — under your platform's data directory, and never a secret. It is on
by default; set "store": { "disabled": true } to keep nothing on disk. See
Configuration for
where it lives and how it is keyed.
- Sign in to your Jira instance in a browser.
- Open your avatar menu → Profile → Personal Access Tokens.
- Create a token and copy the value into
jira.token, with your instance's URL injira.base_url.
Leave jira.user empty to authenticate with that token as a bearer token, which
is what Data Center expects. Set jira.user only if your instance requires HTTP
Basic authentication, in which case the token is used as the password.
Behind single sign-on (Azure AD / Office 365)? SSO usually guards only the
web UI, so a personal access token still reaches the REST API directly — no
special handling needed. When a gateway sits in front of the API too, the
configuration guide shows how to tell (a one-line curl), and how to
carry the gateway's own token or headers with jira.token_command and
jira.headers.
messaging.kind picks the service — slack (the default when empty), teams,
discord or webhook. Slack posts over a bot token or an incoming webhook; the
others post over an incoming webhook, rendered in that service's own markup.
Slack, either transport. Set a webhook or a bot token; if you set both, the bot token wins.
Incoming webhook, the two-minute option: create an app at
https://api.slack.com/apps, turn on Incoming Webhooks, add one to the
workspace, pick its channel, and put the URL in messaging.webhook_url. It is
bound to that channel, so messaging.channel does not apply. Treat the URL as a
password.
Bot token, to choose the channel at runtime:
- Create an app at https://api.slack.com/apps in your workspace.
- Under OAuth & Permissions, add the
chat:writebot token scope. - Install the app to the workspace and copy the Bot User OAuth Token — it
starts with
xoxb-— intomessaging.token. - Set
messaging.channelto the channel to post in, and invite the bot to it.
Teams, Discord or a plain webhook: create an incoming webhook in the service,
set messaging.kind accordingly, and put the URL in messaging.webhook_url.
A configuration written before this block was renamed still names it slack;
rename the key to messaging and add "kind": "slack". workflow --help
repeats all of this at the terminal.
If a bare token cannot reach your forge — an SSO gateway in front of it, say —
set forge.cli to true. workflow then routes its GitHub or GitLab API calls
through gh or glab, reusing the login those tools already hold, and falls
back to HTTP when the tool is not installed.
.workflow.json holds live credentials. config init writes it at mode 0600,
doctor fails while anyone else can read it, it is listed in .gitignore, and config show masks every credential —
including messaging.webhook_url, which is a password that happens to look like
an address. Nothing in this repo will print a credential in full.
Run workflow in a repository. Six panes run down the left — Issues, Branch,
Commits, Review and your messaging service, named for it, in the order the work
goes, then Reviews, the pull requests waiting on your review — and the one in
focus fills the right. The bottom row shows only the keys that do something
right now.
| Key | Where | Does |
|---|---|---|
tab / 1–6 |
anywhere | Move between panes |
t / c / b |
Issues | Change status, comment, branch for the issue |
space / a / c |
Commits | Stage a file, stage all, commit |
h |
Commits | Run the pre-commit hook |
P |
Branch | Push |
n |
Review | Open the pull or merge request |
p |
messaging | Preview the announcement; announce now or once CI passes |
? |
anywhere | Every key |
Comments, commit bodies, pull request descriptions and announcements are written
in $EDITOR and previewed before they send. workflow --dry-run holds every
write back. The
usage guide has the
whole loop.
workflow --web serves the loop in a browser instead, at
http://127.0.0.1:7000: six sections — Issues, Branch, Review, your messaging
service, Reviews and Settings — kept live by the server, in a light or a dark
theme. --web --dry-run makes it read-only. Re-running CI, merging, finishing
a branch and most issue writes stay in the terminal for now;
the web page says which.
task --list # every task, with a description
task run # run the TUI from source
task test # tests with the race detector
task cover:branch # condition coverage: which conditions went only one way
task check # the full gate: lint, tests, coverage floors, vuln, secretstask check is what CI runs. See ARCHITECTURE.md for how the
system fits together — the surfaces, the seams, and the two kinds of local state
— CONTRIBUTING.md for the setup and conventions, and
CLAUDE.md for the code standards this project holds itself to.
Full documentation is at
jacob-delgado.github.io/workflow
— install, usage, the web interface, configuration, and a command reference
generated from the code. The source is in docs/; task docs:serve
previews it locally.
Versioning is automated from the commit history with release-please: merging its release pull request tags the version and publishes binaries for macOS (arm64), Linux (amd64) and Windows (amd64), each with a SHA256 checksum and a build provenance attestation. There are no releases yet.
Found a vulnerability? Please report it privately through a security advisory rather than opening an issue — see SECURITY.md for what to include and what to expect.
.workflow.json holds live credentials. It is written 0600, gitignored, and
every path that surfaces a token masks it first.
Apache License 2.0. Contributions are accepted under the same terms; see CONTRIBUTING.md and the Code of Conduct.