Help us shape the future of Go! Take the 2026 Go Developer Survey

workflow

module
v0.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: Apache-2.0

README

workflow

CI CodeQL Scorecard Docs site Release OpenSSF Scorecard Latest release Go reference Go Bubble Tea License Docs Conventional Commits Code of Conduct Security policy

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, or through a plain webhook — without leaving the keyboard or reconstructing the same context in three browser tabs.

Status

What works today, and what may still change before 1.0, is the Status on the documentation site.

Install

With Go:

go install github.com/jacob-delgado/workflow/cmd/workflow@v0.12.0

Install says what workflow needs beside it, where that binary lands, and the other ways to get it: with Homebrew, from a release or from source. To build and check it in a container or a devcontainer instead, see Development setup.

Configure

First run says how to set it up. .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 or the nearest directory above it, no higher than the repository root, and falls back to your home directory. A file found there is layered over the one in your home directory, setting by setting, so a repository's file holds only what that repository changes — {"jira": {"project": "OSS"}}, say — and inherits the rest, tokens included, without a copy of them. A token goes only where it was written for: a repository's file that points Jira, the forge or the messaging service somewhere else inherits none of your home file's credentials for it.

workflow keeps a little state between sessions on disk, never a secret; What is kept between sessions says what, where, and how to turn it off.

Jira token (on-premises / Data Center)
  1. Sign in to your Jira instance in a browser.
  2. Open your avatar menu → Profile → Personal Access Tokens.
  3. Create a token and copy the value into jira.token, with your instance's URL in jira.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: Slack, Teams, Discord or a plain webhook

messaging.kind picks the service — slack (the default when empty), teams, discord or webhook. Slack posts with a rotating user token or over an incoming webhook; the others post over an incoming webhook, rendered in that service's own markup.

Slack, one transport or the other. Set up a user token or a webhook; a file that sets up both is refused.

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.

User token, to post as you and choose the channel at runtime:

  1. Create an app at https://api.slack.com/apps in your workspace.
  2. Under OAuth & Permissions, add the chat:write user token scope and turn on token rotation. Tagging reviewers and user groups in an announcement is optional and also needs users:read, channels:read, groups:read and usergroups:read; without them the announcement posts untagged.
  3. Install the app, and note the refresh token (xoxe-1-…) and the app's client ID and secret.
  4. Set messaging.channel to a channel you are in, then run workflow slack login.

workflow refreshes the token before its twelve hours run out, and keeps it in the macOS keychain, or in the configuration file on Linux and Windows.

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.

Reaching a forge behind SSO

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.

Keeping the tokens safe

.workflow.json holds live credentials. config init writes it at mode 0600, doctor fails while anyone else can read it, config init warns when the file is not ignored by git (add it to .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.

Use

Run workflow in a repository. Nine 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, Tasks, your Taskwarrior list, Summary, what you did yesterday or over any range of days, and Repositories, where you work and the directories you keep as favorites — 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–9 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 a hook lefthook configures
P Branch Push
n Review Open the pull or merge request, announced once its CI passes
p messaging Preview the announcement; announce now or once CI passes
? anywhere Every key

Commit bodies, pull request descriptions and announcements are written in $EDITOR, comments in a vim-style box beside the issue, and each is previewed before it sends. 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:13579: nine sections — Issues, Branch, Review, your messaging service, Reviews, Tasks, Summary, Repositories and Settings — kept live by the server, in a light or a dark theme. --web --dry-run makes it read-only. Only the offers to change a Taskwarrior task at the loop's moments stay in the terminal, where the web has its Tasks buttons instead; the web page says so.

Development

CONTRIBUTING.md has the setup, the tasks and the gate a change must pass; ARCHITECTURE.md how the system fits together — the surfaces, the seams, and the two kinds of local state — and CLAUDE.md the code standards this project holds itself to.

Documentation

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.

Releases

Versioning is automated from the commit history with release-please. CONTRIBUTING.md says how a release is cut, and Install what each one carries.

Security

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.

License

Apache License 2.0. Contributions are accepted under the same terms; see CONTRIBUTING.md and the Code of Conduct.

Directories

Path Synopsis
Package apispec embeds the OpenAPI contract so the binary carries its own copy of api/openapi.yaml — the same document the Go types and the TypeScript client are generated from.
Package apispec embeds the OpenAPI contract so the binary carries its own copy of api/openapi.yaml — the same document the Go types and the TypeScript client are generated from.
cmd
docsgen command
Command docsgen writes the command reference for the documentation site from the Cobra command tree into the directory it is given:
Command docsgen writes the command reference for the documentation site from the Cobra command tree into the directory it is given:
testshape command
Command testshape checks that every test marks its Arrange, Act and Assert, and that each Assert reaches a failure.
Command testshape checks that every test marks its Arrange, Act and Assert, and that each Assert reaches a failure.
workflow command
Command workflow runs your Jira, Git forge and messaging workflow from the terminal.
Command workflow runs your Jira, Git forge and messaging workflow from the terminal.
internal
activity
Package activity is what was done over a period, read back from the places the work left a trace — git, Taskwarrior, Jira and the forge — and grouped by year, month, day and hour.
Package activity is what was done over a period, read back from the places the work left a trace — git, Taskwarrior, Jira and the forge — and grouped by year, month, day and hour.
api
Package api provides primitives to interact with the openapi HTTP API.
Package api provides primitives to interact with the openapi HTTP API.
buildinfo
Package buildinfo reports which build of workflow is running, from the module version when it was installed from a release, or the commit it was built from otherwise.
Package buildinfo reports which build of workflow is running, from the module version when it was installed from a release, or the commit it was built from otherwise.
cli
Package cli builds the workflow command tree.
Package cli builds the workflow command tree.
cli/clitest
Package clitest runs the workflow command line for a test, as a user or a script would type it: each run in a world of its own — a working directory, a home and none of the developer's environment but what the test set — with a Jira, a gh or glab, and a Slack or webhook that answer as the test scripts them, the prompts a guided command reads, the repositories the commands run in, made with git itself, and the process readied for the tests by Main.
Package clitest runs the workflow command line for a test, as a user or a script would type it: each run in a world of its own — a working directory, a home and none of the developer's environment but what the test set — with a Jira, a gh or glab, and a Slack or webhook that answer as the test scripts them, the prompts a guided command reads, the repositories the commands run in, made with git itself, and the process readied for the tests by Main.
cli/cmdkit
Package cmdkit is what every workflow command is built from, beneath the command tree internal/cli spells: the scriptable contract — the artifact on a checked stdout, what is said about it on stderr, --yes and --dry-run around a write, a mistake in the call as a usage error — and the connection to where a command runs, the environment it reads the process through, the flags every command shares and the prompt that asks the person at the terminal.
Package cmdkit is what every workflow command is built from, beneath the command tree internal/cli spells: the scriptable contract — the artifact on a checked stdout, what is said about it on stderr, --yes and --dry-run around a write, a mistake in the call as a usage error — and the connection to where a command runs, the environment it reads the process through, the flags every command shares and the prompt that asks the person at the terminal.
cli/console
Package console is what a command says on stderr: its notes, warnings and errors, each an entry of one log; with --verbose, a debug line for each request; and, while a command waits on a terminal, the live line of the wait.
Package console is what a command says on stderr: its notes, warnings and errors, each an entry of one log; with --verbose, a debug line for each request; and, while a command waits on a terminal, the live line of the wait.
cli/doctor
Package doctor is `workflow doctor`: it reports the build, the git repository a directory is in, the external programs workflow uses, where the store keeps its files and the configuration in effect, as prose or as JSON, and, asked to, puts each credential to its service.
Package doctor is `workflow doctor`: it reports the build, the git repository a directory is in, the external programs workflow uses, where the store keeps its files and the configuration in effect, as prose or as JSON, and, asked to, puts each credential to its service.
cli/issues
Package issues is the commands over the issue tracker, Jira or the forge's issues: `issues` and `issue`, which read, and `transition`, `assign`, `worklog` and `comment`, which write an issue once its preview is confirmed.
Package issues is the commands over the issue tracker, Jira or the forge's issues: `issues` and `issue`, which read, and `transition`, `assign`, `worklog` and `comment`, which write an issue once its preview is confirmed.
cli/pr
Package pr is the pull or merge request's flow on the command line: `workflow pr`, which opens one for the checked-out branch and makes the offers that follow, and `workflow announce`, which tells the team about it, at once or once its CI passes.
Package pr is the pull or merge request's flow on the command line: `workflow pr`, which opens one for the checked-out branch and makes the offers that follow, and `workflow announce`, which tells the team about it, at once or once its CI passes.
cli/status
Package status is `workflow status`: where the work in a repository stands along the loop, read from its branch, its forge, its tracker and the store, printed on one line or as JSON, for the current repository or for each directory named.
Package status is `workflow status`: where the work in a repository stands along the loop, read from its branch, its forge, its tracker and the store, printed on one line or as JSON, for the current repository or for each directory named.
codeowners
Package codeowners reads a CODEOWNERS file the way GitHub or GitLab reads it and answers who owns a set of paths.
Package codeowners reads a CODEOWNERS file the way GitHub or GitLab reads it and answers who owns a set of paths.
config
Package config owns the workflow configuration file, which holds the credentials for the services workflow talks to.
Package config owns the workflow configuration file, which holds the credentials for the services workflow talks to.
convention
Package convention holds the naming and text rules for the workflow's branches, commits and pull requests.
Package convention holds the naming and text rules for the workflow's branches, commits and pull requests.
docsgen
Package docsgen writes the command reference for the documentation site from the Cobra command tree, so the published flags are the flags the binary actually has.
Package docsgen writes the command reference for the documentation site from the Cobra command tree, so the published flags are the flags the binary actually has.
editor
Package editor hands text and files to the user's own editor.
Package editor hands text and files to the user's own editor.
filelock
Package filelock takes an advisory lock on an open file, which the system lets go when the file is closed, whether by the process or by its end, so no lock outlives its holder.
Package filelock takes an advisory lock on an open file, which the system lets go when the file is closed, whether by the process or by its end, so no lock outlives its holder.
fileowner
Package fileowner says who owns a file, where the operating system keeps that as a user and a group id.
Package fileowner says who owns a file, where the operating system keeps that as a user and a group id.
forge
Package forge talks to the Git forge a repository lives on — GitHub or GitLab, hosted or on-premises.
Package forge talks to the Git forge a repository lives on — GitHub or GitLab, hosted or on-premises.
gitrepo
Package gitrepo reads and changes the git repository a session is running in.
Package gitrepo reads and changes the git repository a session is running in.
gittest
Package gittest keeps a package's tests on the repositories they make and off every other: it clears the variables a git hook exports, which would point every git a test starts at the hook's repository; it has git read a configuration of the tests' own, the developer's kept out and git's automatic maintenance off, in the git it runs and, through a TestMain, in every git a process starts; and it gives the tests that prove a hook's environment changes nothing, and that no git outlives its test, what they compare.
Package gittest keeps a package's tests on the repositories they make and off every other: it clears the variables a git hook exports, which would point every git a test starts at the hook's repository; it has git read a configuration of the tests' own, the developer's kept out and git's automatic maintenance off, in the git it runs and, through a TestMain, in every git a process starts; and it gives the tests that prove a hook's environment changes nothing, and that no git outlives its test, what they compare.
hooks
Package hooks works with lefthook: reading what a run of it printed, reading its configuration, and writing a configuration for a repository whose hooks predate it.
Package hooks works with lefthook: reading what a run of it printed, reading its configuration, and writing a configuration for a repository whose hooks predate it.
httpx
Package httpx is the HTTP transport the Jira, forge and messaging clients share: the one-method Doer seam they accept, and a client that refuses redirects so a credential never follows one.
Package httpx is the HTTP transport the Jira, forge and messaging clients share: the one-method Doer seam they accept, and a client that refuses redirects so a credential never follows one.
jira
Package jira talks to a Jira Data Center instance over its REST v2 API.
Package jira talks to a Jira Data Center instance over its REST v2 API.
keychain
Package keychain stores a secret in the operating system's keychain and reads it back, so a token need never sit in the configuration file.
Package keychain stores a secret in the operating system's keychain and reads it back, so a token need never sit in the configuration file.
lifetime
Package lifetime carries, in a context, the end of what a piece of work is done for: the web server serving it, or the command running it.
Package lifetime carries, in a context, the end of what a piece of work is done for: the web server serving it, or the command running it.
loop
Package loop composes the developer loop once, for every surface: the pull request a branch proposes and the push it needs first, the review status it moves the issue to, the announcement of where it stands and the memory of what was announced before, what "stage all" stages, the scope a commit remembers, and the guards a switch or a commit respects.
Package loop composes the developer loop once, for every surface: the pull request a branch proposes and the push it needs first, the review status it moves the issue to, the announcement of where it stands and the memory of what was announced before, what "stage all" stages, the scope a commit remembers, and the guards a switch or a commit respects.
messaging
Package messaging posts team announcements — to Slack with a rotating user token or through an incoming webhook, and to Teams, Discord or a plain webhook through theirs — and asks whether a Slack user token works.
Package messaging posts team announcements — to Slack with a rotating user token or through an incoming webhook, and to Teams, Discord or a plain webhook through theirs — and asks whether a Slack user token works.
messaging/directory
Package directory reads the Slack directory a session tags people from: a channel's members, the workspace's users and user groups, and which workspace the token is for, each read once however many ask at a time and held for a while, waiting out Slack's rate limits within its patience.
Package directory reads the Slack directory a session tags people from: a channel's members, the workspace's users and user groups, and which workspace the token is for, each read once however many ask at a time and held for a while, waiting out Slack's rate limits within its patience.
places
Package places is where an issue can be, for narrowing the Issues list: one of its tracker's status names, or one of workflow's own marks.
Package places is where an issue can be, for narrowing the Issues list: one of its tracker's status names, or one of workflow's own marks.
proc
Package proc runs external programs.
Package proc runs external programs.
proc/pgroup
Package pgroup runs a streamed child in its own process group, so canceling its context kills the whole tree rather than the child alone.
Package pgroup runs a streamed child in its own process group, so canceling its context kills the whole tree rather than the child alone.
progress
Package progress works out how far along the developer loop a piece of work is — pick an issue, branch, commit, open a review, announce it — from what the repository and the services report.
Package progress works out how far along the developer loop a piece of work is — pick an issue, branch, commit, open a review, announce it — from what the repository and the services report.
ptytest
Package ptytest opens a pseudo-terminal for a test: the side the test types into, and the terminal the code under test reads from, which only a pseudo-terminal can stand in for.
Package ptytest opens a pseudo-terminal for a test: the side the test types into, and the terminal the code under test reads from, which only a pseudo-terminal can stand in for.
report
Package report builds what every surface reports in the API's own shapes, internal/api's: the Summary of a period, the directories you work in, the review queue, and the words for a failure.
Package report builds what every surface reports in the API's own shapes, internal/api's: the Summary of a period, the directories you work in, the review queue, and the words for a failure.
rlimit
Package rlimit lowers one of the process's resource limits around a single call in a test and puts it back afterward.
Package rlimit lowers one of the process's resource limits around a single call in a test and puts it back afterward.
sanitize
Package sanitize keeps text that a server or another program chose from reaching the terminal as control sequences.
Package sanitize keeps text that a server or another program chose from reaching the terminal as control sequences.
seams
Package seams declares what a surface asks of each system outside the process, one bundle per system.
Package seams declares what a surface asks of each system outside the process, one bundle per system.
setup
Package setup is the first run every surface shares: where the configuration file goes, Jira's address and token checked against Jira before they are kept, the OS keychain offered for the token, a messaging webhook, and the file written.
Package setup is the first run every surface shares: where the configuration file goes, Jira's address and token checked against Jira before they are kept, the OS keychain offered for the token, a messaging webhook, and the file written.
slackauth
Package slackauth keeps a Slack user token posting past the twelve hours a rotating token lasts: it refreshes the token through Slack's oauth.v2.access, keeps each new pair where the configuration says — the macOS keychain or the configuration file — and hands out a token with time left on it, refreshing under a lock so two processes do not spend the same single-use refresh token.
Package slackauth keeps a Slack user token posting past the twelve hours a rotating token lasts: it refreshes the token through Slack's oauth.v2.access, keeps each new pair where the configuration says — the macOS keychain or the configuration file — and hands out a token with time left on it, refreshing under a lock so two processes do not spend the same single-use refresh token.
sqlitefile
Package sqlitefile opens a SQLite database file through the pure-Go driver itself, so CGO stays off and no lookup of a driver by name stands between a caller and its file.
Package sqlitefile opens a SQLite database file through the pure-Go driver itself, so CGO stays off and no lookup of a driver by name stands between a caller and its file.
store
Package store keeps a little workflow state on disk between sessions, in two SQLite databases under the OS-native data directory.
Package store keeps a little workflow state on disk between sessions, in two SQLite databases under the OS-native data directory.
taskwarrior
Package taskwarrior drives the task program: finds it, reads its tasks as the JSON its export writes, and changes them through its own commands.
Package taskwarrior drives the task program: finds it, reads its tasks as the JSON its export writes, and changes them through its own commands.
testshape
Package testshape checks that every test marks its Arrange, Act and Assert, and that each Assert reaches a failure.
Package testshape checks that every test marks its Arrange, Act and Assert, and that each Assert reaches a failure.
tui
Package tui renders workflow's terminal interface: a rail of panes down the left, a detail pane beside it, the loop's stages across the top and the keys along the bottom.
Package tui renders workflow's terminal interface: a rail of panes down the left, a detail pane beside it, the loop's stages across the top and the keys along the bottom.
tui/frame
Package frame draws a titled, bordered box of an exact size.
Package frame draws a titled, bordered box of an exact size.
tui/layout
Package layout decides where each region of the terminal interface goes for a given terminal size.
Package layout decides where each region of the terminal interface goes for a given terminal size.
tui/quietinput
Package quietinput is a Huh field typing one line of text, as huh.Input does, over an input whose cursor never blinks: what every form of the terminal's takes typed text through.
Package quietinput is a Huh field typing one line of text, as huh.Input does, over an input whose cursor never blinks: what every form of the terminal's takes typed text through.
web
Package web carries the built single-page app so `workflow --web` serves it from the one static binary.
Package web carries the built single-page app so `workflow --web` serves it from the one static binary.
webserver
Package webserver serves the local web API behind `workflow --web`: the same information the terminal interface shows, and the configuration file, over the REST surface described by api/openapi.yaml.
Package webserver serves the local web API behind `workflow --web`: the same information the terminal interface shows, and the configuration file, over the REST surface described by api/openapi.yaml.
webserver/edge
Package edge is the request boundary of `workflow --web`: what every request passes before the server's own handlers take it.
Package edge is the request boundary of `workflow --web`: what every request passes before the server's own handlers take it.
webserver/problem
Package problem is the RFC 9457 problem details the --web API answers a failure with: the status, title and type each code fixes, the default answer a strict handler returns with one, and one written by the hand-written middleware that answers before a strict handler runs.
Package problem is the RFC 9457 problem details the --web API answers a failure with: the status, title and type each code fixes, the default answer a strict handler returns with one, and one written by the hand-written middleware that answers before a strict handler runs.
webserver/runs
Package runs serves the git runs of `workflow --web` and lefthook's hooks: POST /api/runs, which starts a hook, a rebase, an amend or a fixup and streams it while it goes, DELETE /api/runs/current, which stops it, and the hooks lefthook configures and the offer to set it up.
Package runs serves the git runs of `workflow --web` and lefthook's hooks: POST /api/runs, which starts a hook, a rebase, an amend or a fixup and streams it while it goes, DELETE /api/runs/current, which stops it, and the hooks lefthook configures and the offer to set it up.
webserver/tasks
Package tasks serves the Taskwarrior operations of the web API behind `workflow --web`: the task list, the event stream's summary of your tasks, and the writes on them, over the seam the terminal's Tasks pane asks.
Package tasks serves the Taskwarrior operations of the web API behind `workflow --web`: the task list, the event stream's summary of your tasks, and the writes on them, over the seam the terminal's Tasks pane asks.
webserver/webtest
Package webtest is what the tests of internal/webserver and of the packages that leave it share: the harness that serves the API handler under a session and sends it requests, the canned seams and fixture constants, the event stream's reader, a seam call held on its context, the configuration's edits, the Taskwarrior fake and lefthook's fixtures.
Package webtest is what the tests of internal/webserver and of the packages that leave it share: the harness that serves the API handler under a session and sends it requests, the canned seams and fixture constants, the event stream's reader, a seam call held on its context, the configuration's edits, the Taskwarrior fake and lefthook's fixtures.
wiring
Package wiring connects the seams every surface shares — the command line's, the terminal's and the web's — to the real clients that answer them.
Package wiring connects the seams every surface shares — the command line's, the terminal's and the web's — to the real clients that answer them.
workdirs
Package workdirs reads the directories workflow can be pointed at: what is in one, whether it is there, and how a typed path or a directory is written.
Package workdirs reads the directories workflow can be pointed at: what is in one, whether it is there, and how a typed path or a directory is written.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL