Personal command center for GitHub — one Kanban board that stays in sync with every project you contribute to, plus a full project management CLI.
Project description
Focal
Your personal command center for GitHub — one Kanban board that stays in sync with every project you contribute to, plus a full project management CLI for running delivery end-to-end without leaving the terminal.
Built with AI. Best used with AI. Focal ships with
AGENTS.mdso any capable AI agent (Claude Code, Cursor, Codex) can set it up, run PM commands, and manage your backlog on your behalf — no manual steps required.
The problem
If you contribute to many GitHub repositories, each with its own project board, planning your day means opening every repo board one by one. By the time you've done the rounds, you've lost 20 minutes and still don't have a single prioritized view of your work.
And when it comes to planning a release — creating epics, estimating stories, building an iteration schedule, logging retros — you're either doing it in Jira (context switch) or in your head (no record).
Focal solves both. One personal Kanban board that syncs everywhere, plus a PM CLI that manages your entire delivery lifecycle in GitHub and markdown — no external tools, no context switching.
What it does
Board sync
- Pull — Open issues assigned to you are automatically added to your personal board. New items inherit their status from the origin project.
- Push — When you move a card on your personal board, the status change is pushed to all origin projects the issue belongs to.
- Stale — When an issue is closed or unassigned from you, it is moved to your Done column automatically.
- Conflict resolution — Your personal board wins. If both sides change between syncs, your board's status is pushed to origin.
PM CLI
focal pm init— bootstrap any repo with epics tracker, iteration planning doc, retro log, and design doc templates; auto-registers the repo for cache refreshfocal pm epic-create/story-create— create GitHub issues, link sub-issues, set SP on the board — all in one commandfocal pm plan— build an iteration schedule from your backlog: team capacity, PTO reduction, greedy SP assignment, risk identificationfocal pm retro— close out an iteration: delivered vs carry-over, slip reason codes, goal met?, what went well, action itemsfocal pm status— live terminal dashboard: progress bar, blocked stories, projected deliveryfocal pm remove-repo— unregister a repo from PM tracking (removes it fromrefresh-all)focal cache refresh/refresh-all— pull latest GitHub state into the local cache;refresh-allhits every registered repo in one pass
Set up with an AI agent (recommended)
Focal is AI-native. It ships with AGENTS.md — a detailed guide
that AI coding agents read automatically on startup. This means your agent
already knows how to install, configure, and operate Focal before you say a word.
Supported agents: Claude Code, OpenAI Codex, Cursor, or any agent that
reads AGENTS.md from the project root and can run shell commands.
Option 1 — Let your agent do everything
Open your AI agent and paste a single prompt:
Set up Focal from https://github.com/leninmehedy/focal
The agent will:
- Install Focal (
pipx install focal-cli) - Check prerequisites (
ghCLI, Python 3) - Run the interactive setup wizard (asking you only for your board URL and repos)
- Verify the first sync works
- Install the hourly scheduler (launchd on macOS, cron on Linux)
Option 2 — Install first, then hand off to your agent
pipx install focal-cli # installs the `focal` command globally
claude # Claude Code CLI
# or: open in VS Code / Cursor with the AI extension active
Then just say:
"Set up Focal for me"
Ongoing use — things you can ask your agent
Once set up, your agent can manage both board sync and PM workflows in plain language:
Board sync
- "Add leninmehedy/focal to my sync"
- "Why is issue #42 still showing as New?"
- "Show me warnings from the last sync"
Project management
- "Read our design doc and create the epics and stories"
- "Plan I1 — 2-week sprint, me and @bob at 8 SP each, starting Monday"
- "What's our iteration status?"
- "Log the I1 retro — we hit our goal, no blockers, estimates were a bit off"
Manual setup
Prefer to do it yourself? No problem.
Prerequisites
- GitHub CLI (
gh) — authenticated withrepoandprojectscopes - Python 3.10+
- A personal GitHub Projects v2 board with a Status single-select field
Install and configure
Recommended — install via pipx (no repo clone needed):
pipx install focal-cli
focal board setup
Alternative — clone and install in editable mode:
git clone https://github.com/leninmehedy/focal.git
cd focal
pip3 install -e .
focal board setup
The setup wizard guides you through everything interactively and writes
~/.focal/config.json. Then run a sync manually to verify:
python3 focal.py board sync
Selecting repos
During setup you can choose one of three modes:
| Mode | Description |
|---|---|
| Manual list | Type repos one by one (owner/repo) |
| Interactive select | Browse and pick from your accessible repos |
| Full scan | Scans ALL repos you have access to (slow — may take minutes) |
You can edit the repos array in ~/.focal/config.json at any time to add or remove repos.
To re-run the wizard (e.g. to add repos or fully reconfigure), run python3 focal.py board setup again — it will detect your existing config and offer three choices: Add repos, Edit repo list, or Full reconfigure.
Status column alignment
The setup wizard inspects the Status columns of every origin project and compares them to your personal board. It will:
- Report any mismatches (missing options, different names)
- Generate
status_map.jsonto translate incompatible status names at sync time
Recommended personal board Status columns:
🆕 New · 📋 Backlog · 🔖 Ready · 🏗 In progress · ✋ Blocked · 👀 In review · ✅ Done
Status matching is emoji-normalized — 🏗 In progress and In progress are
treated as the same status, so minor cosmetic differences don't break the sync.
Schedule recurring sync
macOS (launchd — recommended):
cp launchd/com.your-username.focal.plist ~/Library/LaunchAgents/com.YOUR_USERNAME.focal.plist
# Edit the plist: replace YOUR_USERNAME and /path/to/focal
launchctl load ~/Library/LaunchAgents/com.YOUR_USERNAME.focal.plist
Useful commands:
launchctl list | grep focal # check status and last exit code
launchctl start com.YOUR_USERNAME.focal # trigger an immediate run
launchctl unload ~/Library/LaunchAgents/... # disable
Linux / alternative (cron):
(crontab -l 2>/dev/null; echo "0 * * * * /path/to/focal/sync.sh") | crontab -
Schedule PM cache refresh
The PM state cache (docs/focal/.cache/focal-state.json) drifts when issues are
closed or updated on GitHub outside Focal. A twice-daily refresh-all keeps
focal pm status accurate without manual runs.
macOS (launchd):
cp launchd/com.your-username.focal-cache.plist ~/Library/LaunchAgents/com.YOUR_USERNAME.focal-cache.plist
# Edit the plist: replace YOUR_USERNAME and /path/to/focal
launchctl load ~/Library/LaunchAgents/com.YOUR_USERNAME.focal-cache.plist
The template runs at 08:00 and 14:00 daily. Edit StartCalendarInterval to adjust.
Linux / alternative (cron):
(crontab -l 2>/dev/null; echo "0 8,14 * * * python3 /path/to/focal/focal.py cache refresh-all >> ~/.focal/logs/cache-refresh.log 2>&1") | crontab -
refresh-all reads pm_repos from ~/.focal/config.json — no repo arguments needed.
Run focal pm init owner/repo for each repo to register it automatically.
Scaling controls — add these keys to ~/.focal/config.json as needed:
"auto_cache_refresh": false, // disable the scheduler; refresh manually with --force
"max_tracked_issues": 500 // skip repos with more tracked epics+stories than this
Check cache health across all repos at any time:
python3 focal.py cache status
Logging
Logs are written to ~/.focal/logs/YYYY-MM-DD.log (one file per day, naturally
self-rotating). Override via log_dir in config.json.
[2026-05-15 17:09:10] [INFO ] Board: #3 (PVT_kwHOAAxhrc4BXwuQ)
[2026-05-15 17:09:14] [INFO ] Adding: https://github.com/some-org/some-repo/issues/42
[2026-05-15 17:09:47] [WARNING] 'In Progress' not found in "some-org's project" — skipping
[2026-05-15 17:31:22] [INFO ] Sync complete — added: 3 inherited: 3 pushed: 1 stale: 0
Every run ends with a summary line showing counts: added, inherited, pushed, stale.
tail -f ~/.focal/logs/$(date '+%Y-%m-%d').log # follow live
grep 'WARN' ~/.focal/logs/*.log # see all warnings
PM CLI — manage delivery end-to-end
The PM commands work on any target repo, not just Focal itself. Point them at whatever repo you're managing.
Quick start
# Bootstrap a repo
python3 focal.py pm init owner/repo --repo-root /path/to/repo
# Create backlog
python3 focal.py pm epic-create owner/repo --title "OAuth support" --sp 21
python3 focal.py pm story-create owner/repo --epic E1 --title "GitHub OAuth flow" --sp 5
# Plan iterations
python3 focal.py pm plan owner/repo --weeks 2 --team "alice:8,bob:6"
# During delivery
python3 focal.py pm status owner/repo
# End of iteration
python3 focal.py pm retro owner/repo --iteration I1 --goal-met
# Check cache health across all registered repos
python3 focal.py cache status
# Refresh all registered repos (or one specific repo)
python3 focal.py cache refresh-all
python3 focal.py cache refresh owner/repo
All commands work interactively (prompts) if you omit the flags, or fully non-interactively (flags only) for scripting and AI agent use.
AI-native workflow
A project manager can describe work to Claude Code and Claude will run the focal commands:
"Read our design doc and create the epics and stories" "Plan I1 for me and @bob, 2-week sprint starting Monday" "Log the I1 retro — we hit our goal, estimates were a bit off"
Claude reads AGENTS.md automatically, so it already knows the
full command surface and non-interactive flags before you ask.
For the full PM workflow — design docs, breakdown hints, Impact tables, and the
delivery cycle — see docs/pm-guide.md.
File reference
| File | Purpose |
|---|---|
pyproject.toml |
Package metadata and focal CLI entry point |
focal.py |
CLI entry point — all commands |
focal/ |
Python package — sync, wizard, PM modules |
focal/pm/ |
PM command modules (epic, story, plan, retro, status) |
templates/ |
Markdown templates copied by focal pm init |
docs/pm-guide.md |
Full PM workflow guide |
sync.sh / setup.sh |
Thin shell wrappers (for launchd / cron) |
launchd/com.your-username.focal.plist |
macOS scheduler template — board sync (hourly) |
launchd/com.your-username.focal-cache.plist |
macOS scheduler template — PM cache refresh (twice daily) |
~/.focal/config.json |
Your personal config — gitignored, never commit |
config.example.json |
Template showing all config keys |
~/.focal/status_map.json |
Auto-generated status name mappings — gitignored |
AGENTS.md |
AI agent guide — read automatically by Claude Code, Codex, etc. |
State file
Sync state is stored at ~/.focal/state.json (configurable in config.json).
Delete it to reset the baseline — the next sync will re-inherit all statuses
from origin.
Limitations
- Sync is poll-based (no webhooks). GitHub does not emit events for personal project board card moves. Frequency is controlled by your scheduler interval.
- Status pushes to origin are best-effort: if an origin project doesn't have a matching status option, that project is skipped with a warning in the log.
- The
ghtoken must haveprojectscope for both your personal board and any origin org projects you want to write to.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file focal_cli-1.10.2.tar.gz.
File metadata
- Download URL: focal_cli-1.10.2.tar.gz
- Upload date:
- Size: 70.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
519c9c95873b9451dced37c01949f7a9298b256b0a31d4dd91611ddab9a176ff
|
|
| MD5 |
b390c44a4238d153b894ebd97ed6a537
|
|
| BLAKE2b-256 |
d44fb20282c34b467ce23ec6b779b0e7e3183031e6ac8e6a16185d9d07e178cb
|
Provenance
The following attestation bundles were made for focal_cli-1.10.2.tar.gz:
Publisher:
publish.yml on leninmehedy/focal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
focal_cli-1.10.2.tar.gz -
Subject digest:
519c9c95873b9451dced37c01949f7a9298b256b0a31d4dd91611ddab9a176ff - Sigstore transparency entry: 1571922942
- Sigstore integration time:
-
Permalink:
leninmehedy/focal@b53f80f1c2c98a10f931a5f0bd463b4fe8b3e254 -
Branch / Tag:
refs/tags/v1.10.2 - Owner: https://github.com/leninmehedy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b53f80f1c2c98a10f931a5f0bd463b4fe8b3e254 -
Trigger Event:
release
-
Statement type:
File details
Details for the file focal_cli-1.10.2-py3-none-any.whl.
File metadata
- Download URL: focal_cli-1.10.2-py3-none-any.whl
- Upload date:
- Size: 48.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2491fab57e96276ce712d17e36d89eccd7e29ba5850f73ab11833500e274dbef
|
|
| MD5 |
cf6c20b626402a2b13ebf1129f79a06b
|
|
| BLAKE2b-256 |
72503f05a102820f7099acbc787f8d34427b7efd763b0c0886b67b9f1f35303a
|
Provenance
The following attestation bundles were made for focal_cli-1.10.2-py3-none-any.whl:
Publisher:
publish.yml on leninmehedy/focal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
focal_cli-1.10.2-py3-none-any.whl -
Subject digest:
2491fab57e96276ce712d17e36d89eccd7e29ba5850f73ab11833500e274dbef - Sigstore transparency entry: 1571922976
- Sigstore integration time:
-
Permalink:
leninmehedy/focal@b53f80f1c2c98a10f931a5f0bd463b4fe8b3e254 -
Branch / Tag:
refs/tags/v1.10.2 - Owner: https://github.com/leninmehedy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b53f80f1c2c98a10f931a5f0bd463b4fe8b3e254 -
Trigger Event:
release
-
Statement type: