A Go CLI for viewing and updating your Pixela activity records in the terminal. It supports colorful contribution graphs and Retina views, adding quantities from piped input, and recording work sessions and command execution times.
Install from the a-know Homebrew tap:
brew install a-know/tap/pi2
pi2 configThe initial Formula builds from source and installs Go as a build dependency. On macOS,
Xcode Command Line Tools (or Xcode) are required and menubar is included; Linux builds
provide terminal commands only. Quit a running menu bar instance before an upgrade.
brew update
brew upgrade a-know/tap/pi2See the Homebrew release procedure for publishing instructions.
You need Go 1.23 or later and an existing Pixela user, USER_TOKEN, and graph. pi2 does not create users or graphs. See the Pixela getting started guide.
Build from the directory containing the source:
go mod download
go build -o pi2 .
./pi2 config
./pi2 showTo make the command available on your PATH, run go install . in the same directory.
The installation directory is GOBIN, or usually $(go env GOPATH)/bin if unset.
Add that directory to your PATH. The examples below assume that pi2 is on your PATH.
Rebuild or reinstall after updating the source.
pi2 config prompts for username, graph_id, and USER_TOKEN, in that order.
Token input is hidden when entered in a terminal.
The file is saved to ~/.config/p2/settings.yaml.
The command is named pi2, but the configuration directory is named p2.
username: your-pixela-username
graph_id: your-graph-id
USER_TOKEN: your-secret-tokenThe command creates any required directories and saves the file with permissions 0600.
Running it again prompts for all three values and replaces the existing configuration.
Leaving a field blank does not preserve its previous value. Input errors prevent saving.
Do not commit configuration files containing tokens to Git.
- An explicit
--config PATH - The complete set of three environment variables below
~/.config/p2/settings.yaml
| Environment variable | YAML key |
|---|---|
PIXELA_USERNAME |
username |
PIXELA_GRAPH_ID |
graph_id |
PIXELA_USER_TOKEN |
USER_TOKEN |
If all three variables are unset or empty, pi2 falls back to the file.
If only some are set, it reports the missing variables. Settings are not merged field by field.
An explicit --config takes precedence over environment variables and produces an error if the file does not exist.
pi2 --config ./work-settings.yaml showThe config command always saves to a file; it does not change environment variables.
If environment settings are active, subsequent regular commands will still prefer them.
Changing the configuration directory through XDG_CONFIG_HOME is not supported.
pi2 [--config PATH] <command> [arguments]
Place the global option --config PATH (or --config=PATH) before the subcommand.
--help, -h, and invoking pi2 without arguments print usage to standard error and currently exit with status 1.
--graph and --version are not supported. Color selection is available only for prompt.
| Command | Arguments | Behavior |
|---|---|---|
config |
None | Prompt for and save configuration |
show |
None | Display a contribution graph with month labels |
retina |
[yyyyMMdd] |
Display an hourly graph; defaults to today's date on the local machine |
prompt |
--color, --ttl, --refresh, --no-refresh, --help |
Display a cached, single-line hourly graph for shell prompts |
menubar |
--foreground, --help |
macOS menu bar icon with a public Retina SVG popover |
stats |
None | Display statistics as formatted JSON |
add |
One number on standard input | Add to today's Pixel and display the JSON response |
today |
None | Display today's Pixel as formatted JSON |
stopwatch |
None | Start or stop Pixela's timer and display the JSON response |
run |
-- COMMAND [ARGUMENTS...] |
Add command execution time in minutes |
pi2 show
pi2 retina
pi2 retina 20260922Zero quantities appear as □; nonzero quantities, including negative values, appear as ■.
A single space separates Pixels. Colors come from the SVG cells.
Month labels appear above the graph. Retina hour labels appear at
0 (top), 6 (right), 12 (bottom), and 18 (left).
When no Retina date is supplied, pi2 uses the local machine's date. Specify the date explicitly if the graph uses a different time zone. Viewing Retina for dates other than today requires Pixela Supporter access. Retina API documentation
The layout currently treats ■ and □ as one terminal column each.
Labels may be misaligned if your terminal treats them as double-width characters.
Widen the terminal window if the yearly graph wraps.
Options for the SVG date range or mode are not supported.
pi2 stats
pi2 today
pi2 today | jq -r '.quantity'jq is an optional external tool.
today sends returnEmpty=true, so it retrieves an empty Pixel with quantity "0"
if no Pixel has been recorded today.
Today API documentation
printf '5\n' | pi2 add
printf -- '-2.5\n' | pi2 addAdds one number after trimming leading and trailing whitespace and newlines.
Running the command again adds the quantity again.
Decimal values require a float graph. Multiple input lines are not aggregated.
Without piped or redirected input, the command waits for standard input to end.
The implementation reads at most 1024 bytes, so use a short, ordinary integer or decimal.
If today's Pixel does not exist, it is created automatically.
“Today” follows the graph's time zone, or UTC if none is configured.
Add API documentation
pi2 stopwatch # Start
# Do your work
pi2 stopwatch # Stop and recordPixela manages the timer state, so pi2 does not need to keep running.
Time is recorded in minutes. Measurements cannot span multiple days.
For int graphs, the recorded value is rounded to an integer.
Stopwatch API documentation
pi2 run -- go test ./...
pi2 run -- make build
pi2 run -- sh -c 'make build && make test'Configure a graph with type=float and a unit of minutes.
pi2 does not automatically change the graph's type or unit.
--is required. The command and arguments that follow are executed directly.- To use shell syntax, explicitly invoke a shell as shown above.
- Records elapsed wall-clock time from start to finish, not CPU time, in minutes with six decimal places.
- Passes through the child command's standard input, standard output, and standard error. Recording diagnostics go to standard error.
- Records time even when the child exits normally with a nonzero status, and returns that status. If the command cannot start, no time is added and pi2 exits with status 1.
- A recording failure does not change the child's normal exit status. Requests are not retried automatically.
- API submission time is excluded from the measurement. API requests have a 15-second timeout.
- Time spanning multiple days is not split; it is added to the Pixel for the day handled by the add API.
- Signal termination is handled differently from normal exit statuses. Recording is not guaranteed if pi2 is forcibly terminated or a CI job is canceled.
run measures time locally and then calls the add API. It does not change stopwatch state.
pi2 prompt --refresh # Optional initial fetch; reports failures
pi2 prompt # Display cached data; refresh asynchronously if needed
pi2 prompt --color never # Plain text
pi2 prompt --no-refresh # Cache-only, without starting a processExample (each square represents one hour, from 00 through 23):
00 □□□□□□ 06 □□■■■■ 12 ■■□■■■ 18 ■■■□□□
The four labels start six-hour groups. Nonzero quantities are ■, zero quantities are □.
Cells retain their SVG colors. The output has no trailing newline; the circular retina
command is unchanged. Dates use the local machine's timezone, as with retina.
| Option | Default | Behavior |
|---|---|---|
--color auto|never|16|256|truecolor |
auto |
auto uses TERM/COLORTERM even when stdout is a pipe. NO_COLOR and TERM=dumb override all palettes. |
--ttl DURATION |
5m |
Positive Go duration such as 30s or 10m; applies to successful and failed refresh attempts. |
--refresh |
off | Explicit synchronous refresh, with a 3-second request timeout. Writes no output on success; reports errors with a nonzero exit status. |
--no-refresh |
off | Never launch a background updater. Cannot be combined with --refresh. |
--help |
off | Print prompt-specific help. |
Normal display never waits for a network request. An expired cache is displayed while a detached updater refreshes it; new data appears on the next prompt redraw. The first call without a cache displays nothing and starts an updater. Missing configuration, corrupt caches, network errors, or a failed process launch do not print operational errors or fail the normal display command. Invalid command-line options still report errors.
Failed updates preserve the last good same-day data and back off for the TTL. Previous-day
data is not displayed. A lock prevents simultaneous requests, with recovery after one minute
if an updater crashes. No permanent daemon or timer is installed: refreshes are triggered by
prompt invocations. --refresh bypasses the TTL but does not duplicate an in-progress refresh.
Caches are private files (0600) under os.UserCacheDir()/pi2: typically
~/Library/Caches/pi2 on macOS, $XDG_CACHE_HOME/pi2 or ~/.cache/pi2 on Linux, and
%LocalAppData%/pi2 on Windows. Cache keys separate credentials/graphs; tokens are not stored
in cache contents or subprocess arguments. Cached SVG data can contain private activity data.
Old cache files are not automatically pruned; remove this pi2 cache directory to clear them.
With Starship already enabled in your shell, first make sure pi2 is available on your
PATH and that pi2 config or the environment variables above have been configured.
Use pi2, not ./pi2, in the module command so it works outside the build directory.
Add the following to ~/.config/starship.toml (or the file selected by STARSHIP_CONFIG).
If [custom.pi2] already exists, edit that section instead of adding it twice:
[custom.pi2]
description = 'Pixela hourly activity'
command = 'pi2 prompt --color auto'
when = true
format = '$output '
unsafe_no_escape = trueThe default Starship format includes custom modules. If you already customize the top-level
format, insert ${custom.pi2} at the desired position (or retain $custom/$all).
Do not replace your existing Starship configuration. To place the graph on its own line
above the usual prompt, for example, set the top-level format as follows. Place this before
any [section] headers; if format already exists, edit it rather than adding another:
format = '${custom.pi2}$line_break$all'On the first invocation, there is no cache, so the module displays nothing while fetching
data in the background. Wait a few seconds, then press Enter to redraw the prompt. Completion
of the fetch does not itself redraw the prompt. Subsequent invocations display the cache and
trigger background refreshes when needed; you do not need to run --refresh each time.
To populate the cache before the first prompt, or diagnose a missing graph, run this manually:
pi2 prompt --refresh
pi2 prompt --color never--refresh is optional. Do not put it in the Starship module command: it waits for the API
and produces no graph output. If the graph remains absent, check that pi2 is on your PATH
and use the manual refresh to see configuration/API errors. Failed background attempts back
off for five minutes by default; manual refresh bypasses this interval.
unsafe_no_escape preserves per-cell ANSI colors. Use it only for this pi2 command:
the renderer emits fixed labels, square glyphs and generated color sequences, never raw SVG
text or API errors. Do not replace this command with untrusted/raw output while keeping the
setting. For a plain-text alternative, use --color never and omit unsafe_no_escape.
See Starship custom commands.
This is a prompt segment, not a fixed terminal pane; it does not update during command execution.
go build -o pi2 .
./pi2 menubarRequires macOS 11 or later, a graphical login session, and a binary built on macOS with
CGO_ENABLED=1 and Xcode Command Line Tools (or Xcode). The GUI uses the system AppKit and
WebKit frameworks; no additional Go GUI library or browser installation is needed.
If the compiler tools are missing, install them with xcode-select --install.
Click the small grid icon in the menu bar to open a popover showing the original Retina SVG,
including its colors and hour labels. It does not reconstruct the graph from terminal output.
Use Refresh to fetch again and Quit to stop the process. Clicking outside closes the
popover but leaves the menu bar process running. By default, the command starts a detached
background process, prints its PID, and immediately returns control to the terminal.
Closing the launching terminal does not stop it. Use Quit in the popover to stop it.
Login startup and a signed/notarized .app bundle are not included. Multiple launches
create separate icons; quit the old instance before launching another.
For troubleshooting, keep the process attached and show diagnostics:
./pi2 menubar --foregroundIn this mode, Quit or Ctrl-C stops it. Configuration and platform requirements are checked
before a background process is launched. The PID message confirms process creation, not
that the GUI has finished initializing; if no icon appears, use --foreground to diagnose it.
Background standard input/output/error are disconnected from the terminal; no log file is created.
Only public graphs are supported: no authentication token is required or sent. The configuration precedence for this command is:
- An explicit
pi2 --config PATH menubarfile. - Both
PIXELA_USERNAMEandPIXELA_GRAPH_ID, if either is set (partial settings are an error). ~/.config/p2/settings.yaml.
USER_TOKEN / PIXELA_USER_TOKEN are ignored by this subcommand. A token-only environment
setting does not override the file. A menubar-only configuration file can contain just:
username: your-username
graph_id: your-graph-idAlternatively, no configuration file is needed with:
PIXELA_USERNAME=your-username PIXELA_GRAPH_ID=your-graph-id pi2 menubarExisting configuration files containing a token also work. Other subcommands and pi2 config
retain their existing token requirements.
The first click starts a background request (10-second timeout). The same-day SVG is cached
in memory only, separately from pi2 prompt. Opening the popover reuses it and refreshes
when five minutes have elapsed since the last attempt. While open, the popover checks every
30 seconds; while closed, it does not poll the API. Refresh bypasses this interval. A failed
request preserves the same-day image and displays a failure notice; a new local date clears
the old image on the next check. Exiting clears the cache. The target date uses the computer's
local timezone. In macOS dark mode, requests include ?appearance=dark and the SVG container
uses a dark background; light mode uses the default API appearance and a white background.
Appearance changes are detected when opening the popover, clicking Refresh, or at the next
30-second check while open. A change invalidates the previous appearance's cache and bypasses
the five-minute interval, including after a failed request.
SVGs are embedded as images in a nonpersistent WebView with JavaScript disabled and a restrictive content policy. External resources, links, and page navigation are disabled. The original SVG's interactive links/tooltips are therefore not enabled.
On other platforms, or when built with CGO_ENABLED=0, the regular CLI remains available;
menubar reports that a macOS/cgo build is required. To build a CLI-only binary on macOS:
CGO_ENABLED=0 go build -o pi2-cli .The following automatic TTY rules apply to show and retina; prompt has the rules above.
| Condition (first match wins) | Output |
|---|---|
Not a TTY, NO_COLOR exists, or TERM=dumb |
No color |
COLORTERM contains truecolor or 24bit |
24-bit color |
TERM contains 256color |
Nearest 256-color palette match |
| Other TTYs | Nearest 16-color palette match |
NO_COLOR=1 pi2 show
pi2 show > graph.txtFor show and retina, pipes, files, and typical CI logs receive no color escape sequences.
Colors may render incorrectly if environment variables do not reflect the terminal's actual capabilities.
The appearance of 16-color output also depends on the terminal theme.
Regular API commands write results to standard output, errors to standard error, and exit with status 1 on error.
For run, the exit status rules above apply.
Additive and timer operations are not retried automatically.
A disconnected request may already have been recorded, so check with today or another suitable command before rerunning it.
Register these Actions secrets:
PIXELA_USERNAMEPIXELA_GRAPH_IDPIXELA_USER_TOKEN
The following example records test duration in pi2's own repository.
Save it as a file such as .github/workflows/record-test-duration.yml.
Create a float graph with a unit of minutes beforehand.
name: Record test duration
on:
push:
workflow_dispatch:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: stable
- name: Build pi2
run: go build -o "$RUNNER_TEMP/pi2" .
- name: Test and record duration
env:
PIXELA_USERNAME: ${{ secrets.PIXELA_USERNAME }}
PIXELA_GRAPH_ID: ${{ secrets.PIXELA_GRAPH_ID }}
PIXELA_USER_TOKEN: ${{ secrets.PIXELA_USER_TOKEN }}
run: '"$RUNNER_TEMP/pi2" run -- go test ./...'No configuration file is needed. A test failure fails the step, but a recording failure alone does not turn a successful test run into a failed step. Rerunning the workflow adds another measurement.
For another project, obtain and build pi2's source in a separate directory or provide a prebuilt binary,
then replace the measured command with the project's command, such as make build.
The example's go build ... . assumes that the current directory contains pi2's source.
Secrets are inherited by the child command as environment variables. Use this in jobs that execute trusted code. Secrets are normally unavailable to pull requests from forks, so this example runs on pushes and manual dispatches. See the official documentation for details on the actions and secrets:
go test ./...
go test -race -cover ./...
go vet ./...Tests mock HTTP communication and do not write to real Pixela graphs. They cover commands, configuration precedence, JSON/SVG output, color fallbacks, labels, and child process input/output. The race detector requires a supported platform and an appropriate environment, including a C compiler.
When reporting a bug, include your OS, Go version, command, expected result, and actual result.
For display issues, the terminal name and TERM / COLORTERM values are also useful.
Do not include tokens.