A lazygit-style terminal dashboard for GitHub Issues. Keyboard-driven, single-line rows, no context switching.
hog board --live opens a five-panel TUI that shows all your GitHub Project issues across repos. Navigate with j/k, pick up issues, change statuses, comment, search, and open details — without ever touching a browser.
The layout is inspired by lazygit: a narrow left column for repos and statuses, a wide issues list in the middle, a detail panel on the right, and an activity feed at the bottom.
npm install -g @ondrej-svec/hog
hog init # interactive setup (picks up gh auth automatically)
hog board --liveRequirements: Node.js 22+ and the GitHub CLI authenticated via gh auth login.
┌──────────────┬─────────────────────────────────┬──────────────────┐
│ [1] Repos │ [3] Issues │ [0] Detail │
│ [2] Statuses │ (main panel, full issue list) │ (selected item) │
└──────────────┴─────────────────────────────────┴──────────────────┘
│ [4] Activity │
└────────────────────────────────────────────────────────────────────┘
Switch between panels with 0–4. The detail panel and activity feed only appear on wider terminals (≥120 cols and ≥140 cols respectively).
Each issue fits on a single line with fixed-width columns:
▶ #199 Frontend ↔ Backend integration spec… [M] [p:H] unassigned 3d
#205 UI elements displaying in Cz… [M] [p:H] unassigned 1d
| Column | Width | Content |
|---|---|---|
| Cursor | 2 | ▶ when selected |
| Number | 7 | #1234 |
| Title | dynamic | truncated to fit panel width |
| Labels | 13 | up to 2 compact abbreviations: [bug], [p:H], [M], [WIP] |
| Assignee | 10 | login or unassigned |
| Date | 10 | target date (today, in 4d, 3d overdue) or age (2h, 5m) |
Labels are abbreviated automatically: size:M → [M], priority:high → [p:H], work:* → [WIP], enhancement → [enh].
| Key | Action |
|---|---|
j / k |
Down / up |
↓ / ↑ |
Down / up |
Tab / Shift+Tab |
Next / previous section |
0–4 |
Jump to panel |
Space |
Toggle section collapse (on header) or enter multi-select (on issue) |
Enter |
Open in browser (issue) or toggle collapse (section) |
0 |
Detail panel — full-screen overlay on narrow terminals, focus side panel on wide |
C |
Collapse all sections |
? |
Toggle help overlay |
q |
Quit |
| Key | Action |
|---|---|
p |
Pick up issue — assign to yourself + optional TickTick task |
a |
Assign issue to yourself (no-op if already assigned to anyone) |
u |
Undo last reversible action |
e |
Edit issue in $EDITOR — change assignee, title, status, labels, body |
m |
Change project status |
l |
Add / remove labels |
c |
Add comment |
ctrl+e |
Open $EDITOR for a multi-line comment |
y |
Copy issue URL to clipboard |
n |
Create issue (form wizard) |
I |
Create issue from natural language |
f |
Focus mode (Pomodoro timer) |
| Key | Action |
|---|---|
/ |
Search issues by title |
r / R |
Refresh now |
Press Space on any issue to enter multi-select, then:
| Key | Action |
|---|---|
Space |
Toggle item |
Enter / m |
Bulk action menu (status, assign, label) |
Escape |
Exit multi-select |
Multi-select is constrained to a single repo (GitHub API requirement).
Press I to open the natural language input. Type a plain-English description:
fix auth timeout on mobile #backend #bug @alice due friday
hog extracts:
| Field | Token | Example |
|---|---|---|
| Labels | #word |
#backend, #bug |
| Assignee | @user |
@alice, @me |
| Due date | due <expr> |
due friday, due end of month, due 2026-03-01 |
| Title | everything else | fix auth timeout on mobile |
A live preview shows the parsed fields before you confirm with Enter. Labels are validated against the repo's actual label list.
With an OpenRouter API key, hog sends ambiguous input to an LLM for richer title cleanup. Heuristic tokens always take priority — LLM only fills gaps.
hog config ai:set-key sk-or-... # store key
hog config ai:clear-key # remove
hog config ai:status # show active providerOr use environment variables (take priority over stored key):
export OPENROUTER_API_KEY=sk-or-...
# or
export ANTHROPIC_API_KEY=sk-ant-...hog board --live # interactive TUI
hog board --json # full board as JSON
hog board --mine --json # only my assigned issues
hog board --backlog --json # only unassigned issues
hog board --repo owner/repo --json # filter by repo
hog board --profile work --live # use a named profilehog issue create "fix login bug #backend due friday" --repo owner/repo
hog issue create "add dark mode" --repo owner/repo --dry-run # preview
hog issue create "add dark mode" --repo owner/repo --json # structured outputAssign an issue to yourself and optionally create a linked TickTick task.
hog pick owner/repo/145Manage TickTick tasks (requires TickTick to be enabled).
hog task list
hog task add "Ship the feature"
hog task add "Bug fix" -p high -t "urgent"
hog task complete <taskId>
hog task update <taskId> --title "New title" -p medium
hog task delete <taskId>
hog task projects # list TickTick projects
hog task use-project <projectId> # set default projecthog config show
# Repos
hog config repos
hog config repos:add owner/repo --project-number 1 --status-field-id PVTSSF_xxx --completion-type closeIssue
hog config repos:rm reponame
# TickTick
hog config ticktick:enable
hog config ticktick:disable
# AI
hog config ai:set-key sk-or-...
hog config ai:clear-key
hog config ai:status
# Profiles
hog config profile:create work
hog config profile:delete work
hog config profile:default workInteractive setup. Detects your GitHub user, discovers your repos and project IDs, and optionally configures TickTick and an AI key.
hog init # first-time setup
hog init --force # overwrite existing configSync GitHub issue status with TickTick task completion.
hog sync run # run sync
hog sync run --dry-run # preview
hog sync status # show current mappingsConfig: ~/.config/hog/config.json (created by hog init, schema version 3).
Credentials (TickTick OAuth token, OpenRouter key) live in ~/.config/hog/auth.json with 0600 permissions.
hog auto-detects status columns from your GitHub Project. Override per-repo with statusGroups:
"statusGroups": ["In Progress", "In Review", "Todo,Backlog"]Each entry becomes a board section. Comma-separated values merge into one section (header = first value). Terminal statuses (Done, Shipped, Closed, etc.) are always hidden.
Switch board configs for different contexts:
hog config profile:create work
hog config profile:default work
hog board --profile personal --liveEvery command supports --json for structured output. This makes hog scriptable and usable by AI agents:
hog board --mine --json | jq '.issues[] | select(.labels[] | .name == "bug")'
hog issue create "fix login bug #backend @alice due friday" --repo owner/repo --json- GitHub data — always synchronous via
execFileSync("gh", ...). No GitHub tokens or REST API calls;ghCLI handles auth. - TickTick data — async HTTP via the TickTick Open API. OAuth token stored in
auth.json. - Rendering — Ink (React for CLIs). Each panel is an Ink component; rows are pre-truncated in JS so Ink never wraps text.
- Auto-refresh —
setIntervalre-fetches in the background; age indicator turns yellow (>2 min) then red (>5 min).
git clone https://github.com/ondrej-svec/hog
cd hog
npm install
npm run dev -- board --live # run from source
npm run test # vitest
npm run ci # typecheck + lint + tests (what CI runs)Toolchain: TypeScript (strict), Biome for lint/format, tsup for bundling, Vitest for tests. 80% coverage threshold enforced.
Filenames must be kebab-case. noExplicitAny is an error. Use import type for type-only imports.
Run a single test file:
npx vitest run src/board/components/issue-row.test.tsx- Node.js 22+
- GitHub CLI (
gh) — authenticated viagh auth login - TickTick account — optional
- OpenRouter API key — optional, for AI-enhanced issue creation
MIT

{ "version": 3, "repos": [ { "name": "owner/repo", "shortName": "repo", "projectNumber": 1, "statusFieldId": "PVTSSF_xxx", "completionAction": { "type": "closeIssue" }, "statusGroups": ["In Progress", "In Review", "Todo,Backlog"] // optional } ], "board": { "refreshInterval": 60, // seconds (min 10) "backlogLimit": 20, "assignee": "your-github-login", "focusDuration": 1500 // seconds (25 min default) }, "ticktick": { "enabled": false // set true to enable TickTick sync }, "profiles": {}, "defaultProfile": "" }