saral

module
v0.4.5 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: MIT

README

Saral

Jira in your terminal, made simple.

Saral (सरल) is Hindi for simple, straightforward, plain — the opposite of what happens to a tool when every team bolts another field onto it. That is the whole idea: Jira's data, none of Jira's web app.

Demo. The demo is committed as a tape, not as a picture: demo.tape holds the sequence, the terminal size, the theme and the typing speeds, so anyone can re-record it the day the UI moves. docs/DEMO.md is how — vhs demo.tape — and what to check before committing the output. demo.gif is not in the repo yet.

Status. Batches 0 to 8 have merged: fifteen view packages, seven of them root views on the g-and-a-digit slots. What is left is Batch 9 — release engineering, the performance gate and this documentation pass — and then v0.1.0. docs/ROADMAP.md has every packet and links each one to its issue.

What it does

Fifteen views, and the seven that are places rather than panes sit on g1–g7: issues, board, backlog, sprints, releases, timeline and plans. The rest are reached from the thing they are about — an issue, the files on it, a transition, a filter, a move.

  • Tickets — JQL search, read, create, edit, transition. Forms are generated from your instance's own createmeta, so they are right on any site.
  • Comments — add, edit, delete, in $EDITOR, with faithful markdown ⇄ ADF conversion.
  • Attachments — upload, resumable ranged download with progress, delete, and inline image preview in terminals that speak the kitty or iTerm2 protocol, half-blocks where they do not.
  • Releases — manage versions and release them, with the unresolved-issue decision the web app makes you take: release anyway, move the open issues to another version, or strip the version off them.
  • Sprints and boards — columns from your board configuration, backlog, create/start/complete sprints, move issues between sprint and backlog, rank-aware reordering.
  • Cross-project move — a wizard over Jira's bulk-move API with status and field remapping, which polls the task and reports a move that stopped half way.
  • Timeline — built from real start and end dates through a seven-rule cascade that says which rule each bar came from, with sprint and version markers.
  • Plans — the real thing if your token has Administer Jira; otherwise plans built from the session's project and your saved queries, with the reason the site refused shown beside them.

What the web app does not do

  • The whole keyboard, and the whole mouse. Every action is reachable three ways — a key, the command palette (ctrl+k), and the pointer. Click a status cell to filter by it, drag the divider between the description and the fields, right-click for the menu of what applies to what is in front of you.
  • A footer that only shows keys that work right now. Hints come from the key registry and move with the state the view is actually in, so nothing on the row is a stroke that answers with a refusal.
  • First paint before the network. Views read the cache in their constructor, draw, and revalidate behind the frame. A refresh patches rows without moving your cursor, your scroll offset or your filter.
  • A 403 is an answer. Capabilities are probed once per site and project, kept between runs and re-asked on every start. A feature your token lacks is hidden or disabled with the site's own sentence saying why — never a crash, never a silently empty list.
  • Nothing about your Jira is assumed. No project keys, custom field IDs, statuses or permissions are baked in. Field IDs resolve by name against the catalogue, board columns come from board configuration, statuses come from the project's own workflows, and required fields from createmeta.
  • Nothing leaves the machine but Jira API calls. Frecency tables, JQL, saved queries and the fuzzy index over cached issues are local files. No telemetry, ever.
  • Budgets, not hopes. Cold start to first paint under 250 ms, failed in CI; a keystroke to a frame in a mean under 16 ms at 10,000 rows; one allocation a frame while scrolling — the frame string and nothing behind it; a stripped binary under 15 MiB, failed in CI. The numbers, and which of them a build actually fails on, are in docs/PERFORMANCE.md.

Install

Not released yet. v0.1.0 is the last thing in Batch 9. Once it ships, from the Homebrew tap or from source:

brew install varijkapil13/tap/saral
go install github.com/varijkapil13/saral/cmd/saral@latest

Each release also carries saral_<version>_<os>_<arch>.tar.gz for darwin and linux on amd64 and arm64, with a checksums.txt beside them. Building from a clone needs only Go:

git clone https://github.com/varijkapil13/saral && cd saral
make build && ./saral

Point it at a site

saral with nothing configured opens the setup view. It asks for four things — the site, the account email the API token belongs to, where the token lives, and a project to open in — checks them against the site before saving, and writes the profile. Re-running it over an existing profile replaces only those four and leaves your theme, timeline field names and saved queries alone. Get an API token here.

The file it writes is ~/.config/saral/config.toml — saral-dev in place of saral for a build from a checkout, so an installed copy and a development one never share a profile; saral --version prints the path. It is meant to stay safe to hand somebody: the token is never in it, only where to find it.

active = "work"

[profiles.work]
site    = "example.atlassian.net"
email   = "you@example.com"
project = "ENG"                          # several capabilities are answered per project
theme   = "dark"                         # or light, no-color; omit to follow the terminal
token   = { keychain = "saral:work" }    # or { env = "JIRA_TOKEN" } / { command = ["pass", "jira"] }

[profiles.work.timeline]
start = ["Target start", "Start date"]   # field names, resolved to IDs at runtime
end   = ["Target end", "Due date"]

[[profiles.work.queries]]
name = "Blockers"
jql  = "priority = Highest AND resolution = EMPTY ORDER BY updated DESC"
key  = 2                                 # the number key that runs it; omit for none

Then:

saral                     # the active profile
saral --profile personal  # another one
saral --project OPS       # scope this run to a project
saral ENG-142             # open an issue
saral https://example.atlassian.net/browse/ENG-142
saral board               # open a view by name
saral --poll 30s          # re-read the focused view, pausing when Jira rate-limits
saral --mouse=false       # off all the way down, for terminal text selection
saral version

SARAL_CONFIG_DIR and SARAL_CACHE_DIR move both directories, which is how a throwaway profile is kept out of the way of a real one.

Use as a library

The Jira client is a standalone, documented package with a semver promise — useful even if you never want a TUI:

import "github.com/varijkapil13/saral/pkg/jira"

pkg/jira is the port — 42 methods in domain terms, none of them shaped like the REST call underneath — plus a Cloud adapter and an in-memory fake. pkg/adf converts Atlassian Document Format to and from markdown without losing nodes it does not understand. Everything under internal/ is the application and carries no compatibility promise.

Documentation

Document What's in it
docs/ARCHITECTURE.md Layers, the port, the registries, caching, rendering, the error taxonomy
docs/UX.md Navigation, the keymap, mouse, progressive mastery, terminal rendering rules
docs/API-NOTES.md Every Jira API trap already found, and how each one is known
docs/TESTING.md The fake, the fixtures, golden files, conformance across adapters
docs/PERFORMANCE.md Budgets, and which of them a build fails on
docs/SCOPE.md What's in, what's out, and the non-negotiables
docs/ROADMAP.md Batches and packets, in value order
docs/PARALLEL.md How several people or agents work at once without conflicts
docs/DEMO.md How to record demo.tape, and what to check first
docs/BOOTSTRAP.md Verifying a token and capturing fixtures, from a fresh clone
AGENTS.md Working agreement for coding agents

Contributing

Read CONTRIBUTING.md and docs/PARALLEL.md. Pick an unassigned packet from the current batch's milestone, claim it on the issue, and open one PR for it.

You do not need a Jira account to build or test. The whole suite runs against an in-memory fake and recorded fixtures, with no network: CI runs the race suite inside a namespace with only loopback up, so a test that reaches a real host fails the build.

Built with

Bubble Tea v2 · Lip Gloss v2 · Bubbles v2 · bubblezone v2 · bbolt

License

MIT

Directories

Path Synopsis
cmd
saral command
Command saral is a terminal client for Jira Cloud.
Command saral is a terminal client for Jira Cloud.
internal
app
Package app holds Saral's use cases: the orchestration between a view and the jira.Client port.
Package app holds Saral's use cases: the orchestration between a view and the jira.Client port.
arch
Package arch holds the tests that keep docs/ARCHITECTURE.md and docs/TESTING.md honest: the import boundaries between the layers, the roles each adapter states it satisfies, whether every port method has a conformance table, and the CI workflow's network namespace.
Package arch holds the tests that keep docs/ARCHITECTURE.md and docs/TESTING.md honest: the import boundaries between the layers, the roles each adapter states it satisfies, whether every port method has a conformance table, and the CI workflow's network namespace.
config
Package config reads and writes Saral's profiles.
Package config reads and writes Saral's profiles.
store
Package store holds Saral's cache file.
Package store holds Saral's cache file.
ui
Package ui pulls in every view package so that its init() runs and it appears in the kernel's registries.
Package ui pulls in every view package so that its init() runs and it appears in the kernel's registries.
ui/attach
Package attach is the attachment pane: the files on one issue, and a look at the one under the cursor without leaving the terminal.
Package attach is the attachment pane: the files on one issue, and a look at the one under the cursor without leaving the terminal.
ui/backlog
Package backlog is a board's backlog: the issues waiting to be scheduled, the open sprints they can go into, and the moves between the two.
Package backlog is a board's backlog: the issues waiting to be scheduled, the open sprints they can go into, and the moves between the two.
ui/board
Package board is the board: the columns a board's own configuration defines, the issues that fall into them, and moving one from one column to another.
Package board is the board: the columns a board's own configuration defines, the issues that fall into them, and moving one from one column to another.
ui/comment
Package comment is the comment thread on one issue: reading it, writing a comment, editing one and deleting one.
Package comment is the comment thread on one issue: reading it, writing a comment, editing one and deleting one.
ui/filter
Package filter is the filter picker: choose a facet, then toggle any number of the values this site actually holds for it.
Package filter is the filter picker: choose a facet, then toggle any number of the values this site actually holds for it.
ui/form
Package form is the create screen.
Package form is the create screen.
ui/issue
Package issue is the read-only issue detail: the description rendered out of ADF beside the fields it belongs to and the thread that belongs to the same issue.
Package issue is the read-only issue detail: the description rendered out of ADF beside the fields it belongs to and the thread that belongs to the same issue.
ui/list
Package list is the issue list: a virtualized table over a JQL search that pages as the cursor approaches the end of what it has.
Package list is the issue list: a virtualized table over a JQL search that pages as the cursor approaches the end of what it has.
ui/move
Package move is the wizard that moves issues to another project: the target project and issue type, what each status becomes over there, whatever the target insists on, and then a confirm screen naming the whole resolved mapping before anything is submitted.
Package move is the wizard that moves issues to another project: the target project and issue type, what each status becomes over there, whatever the target insists on, and then a confirm screen naming the whole resolved mapping before anything is submitted.
ui/onboarding
Package onboarding is the first-run view: it collects a site, an account email and an API token, checks them against Jira before anything is written, asks where the token should live, picks a project, and then reports what the capability probe found in the probe's own words.
Package onboarding is the first-run view: it collects a site, an account email and an API token, checks them against Jira before anything is written, asks where the token should live, picks a project, and then reports what the capability probe found in the probe's own words.
ui/palette
Package palette is the command palette: ctrl+k, a fuzzy filter over every command the build registered — ranked by what this machine actually runs — and over every issue the cache already holds.
Package palette is the command palette: ctrl+k, a fuzzy filter over every command the build registered — ranked by what this machine actually runs — and over every issue the cache already holds.
ui/plan
Package plan is the plans view: the site's Advanced Roadmaps plans where the token may read them, and the plans this profile defines itself where it may not — which is the ordinary case rather than the edge one.
Package plan is the plans view: the site's Advanced Roadmaps plans where the token may read them, and the plans this profile defines itself where it may not — which is the ordinary case rather than the edge one.
ui/release
Package release is the two screens a project's versions need: the list of them, which creates, edits and archives one, and the flow that actually ships one.
Package release is the two screens a project's versions need: the list of them, which creates, edits and archives one, and the flow that actually ships one.
ui/richtext
Package richtext renders an ADF document as styled terminal lines.
Package richtext renders an ADF document as styled terminal lines.
ui/settings
Package settings is the settings screen: every registered kernel.Setting, grouped into the sections it registered under and drawn as the row shape its Kind calls for.
Package settings is the settings screen: every registered kernel.Setting, grouped into the sections it registered under and drawn as the row shape its Kind calls for.
ui/sprint
Package sprint is the sprints view: a board's sprints by state, and the moves the port allows on one.
Package sprint is the sprints view: a board's sprints by state, and the moves the port allows on one.
ui/timeline
Package timeline draws a project as horizontal bars over a calendar axis, each bar carrying which rule of the date cascade produced it.
Package timeline draws a project as horizontal bars over a calendar axis, each bar carrying which rule of the date cascade produced it.
ui/uitest
Package uitest holds what a view's tests need to drive a frame through the pointer.
Package uitest holds what a view's tests need to drive a frame through the pointer.
ui/widget
Package widget holds the interaction pieces more than one view needs: the zone marking every clickable element does, the double-click Bubble Tea does not report, the drag a two-pane view will need, and the window a scrolled pane draws through.
Package widget holds the interaction pieces more than one view needs: the zone marking every clickable element does, the double-click Bubble Tea does not report, the drag a two-pane view will need, and the window a scrolled pane draws through.
ui/widget/filterbar
Package filterbar draws the strip of chips a list-shaped view puts under its rows to say what it is narrowed by: one chip per facet naming its values, not one per value — the grouping filter.Terms already promises.
Package filterbar draws the strip of chips a list-shaped view puts under its rows to say what it is narrowed by: one chip per facet naming its values, not one per value — the grouping filter.Terms already promises.
pkg
adf
Package adf models the Atlassian Document Format, the JSON document shape that Jira Cloud's REST API v3 requires for descriptions, comments, worklog comments and multi-line custom fields.
Package adf models the Atlassian Document Format, the JSON document shape that Jira Cloud's REST API v3 requires for descriptions, comments, worklog comments and multi-line custom fields.
jira
Package jira is the port every part of Saral talks to instead of talking to Jira.
Package jira is the port every part of Saral talks to instead of talking to Jira.
jira/cloud
Package cloud is the jira.Client adapter for a Jira Cloud site: the HTTP transport underneath every call, basic auth with an API token, the mapping from a refused response to the typed errors in pkg/jira, retries that honour Retry-After, and both of Jira's pagination models.
Package cloud is the jira.Client adapter for a Jira Cloud site: the HTTP transport underneath every call, basic auth with an API token, the mapping from a refused response to the typed errors in pkg/jira, retries that honour Retry-After, and both of Jira's pagination models.
jira/jiratest
Package jiratest is the in-memory adapter for the jira port: a complete jira.Client backed by maps, so that everything above the adapter can be exercised with no Jira instance, no credentials and no network.
Package jiratest is the in-memory adapter for the jira port: a complete jira.Client backed by maps, so that everything above the adapter can be exercised with no Jira instance, no credentials and no network.

Jump to

Keyboard shortcuts

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