{"id":36982674,"url":"https://github.com/cesarferreira/needle","last_synced_at":"2026-01-13T22:52:52.818Z","repository":{"id":328732774,"uuid":"1115250281","full_name":"cesarferreira/needle","owner":"cesarferreira","description":"TUI that highlights the GitHub PRs that need you","archived":false,"fork":false,"pushed_at":"2025-12-19T02:20:40.000Z","size":4233,"stargazers_count":100,"open_issues_count":1,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-13T22:52:47.282Z","etag":null,"topics":["pr","prs","pull-requests","rust"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/needle-cli","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cesarferreira.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-12T15:06:48.000Z","updated_at":"2026-01-13T11:18:16.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/cesarferreira/needle","commit_stats":null,"previous_names":["cesarferreira/needle"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cesarferreira/needle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesarferreira%2Fneedle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesarferreira%2Fneedle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesarferreira%2Fneedle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesarferreira%2Fneedle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cesarferreira","download_url":"https://codeload.github.com/cesarferreira/needle/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesarferreira%2Fneedle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28405133,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T21:51:37.118Z","status":"ssl_error","status_checked_at":"2026-01-13T21:45:14.585Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["pr","prs","pull-requests","rust"],"created_at":"2026-01-13T22:52:52.722Z","updated_at":"2026-01-13T22:52:52.810Z","avatar_url":"https://github.com/cesarferreira.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ch1\u003eneedle\u003c/h1\u003e\n  \u003cp\u003e\n    TUI that shows the few PRs that need you: review requests, failing CI, and long-running checks.\n  \u003c/p\u003e\n\n  \u003cp\u003e\n    \u003ca href=\"https://github.com/cesarferreira/needle/actions/workflows/ci.yml\"\u003e\u003cimg alt=\"CI\" src=\"https://github.com/cesarferreira/needle/actions/workflows/ci.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://crates.io/crates/needle-cli\"\u003e\u003cimg alt=\"Crates.io\" src=\"https://img.shields.io/crates/v/needle-cli\"\u003e\u003c/a\u003e\n    \u003cimg alt=\"TUI\" src=\"https://img.shields.io/badge/TUI-ratatui-5f5fff\"\u003e\n    \u003cimg alt=\"Async\" src=\"https://img.shields.io/badge/async-tokio-2f74c0\"\u003e\n    \u003cimg alt=\"Storage\" src=\"https://img.shields.io/badge/cache-sqlite-3b7ddd\"\u003e\n    \u003cimg alt=\"License\" src=\"https://img.shields.io/badge/license-MIT-green\"\u003e\n  \u003c/p\u003e\n\n  \u003cimg src=\"public/screenshot.png\" width=\"900\" alt=\"needle screenshot (demo mode)\"\u003e\n\u003c/div\u003e\n\n\n## Install\n\n```bash\ncargo install needle-cli\n```\n\nThis installs the `needle` binary.\n\n## Quick start\n\nDemo mode (no GitHub token required):\n\n```bash\nneedle --demo\n```\n\nReal mode (requires `NEEDLE_GITHUB_TOKEN` or `GITHUB_TOKEN`):\n\n```bash\nexport NEEDLE_GITHUB_TOKEN=ghp_...\n# or\nexport GITHUB_TOKEN=ghp_...\nneedle\n```\n\n## Options\n\n- `--days \u003cN\u003e`: only include PRs updated in the last `N` days (default: `30`)\n- `--demo`: run with diverse fake data\n- `--org \u003cORG\u003e`: only show PRs in these orgs/users (repeatable or comma-delimited)\n- `--include \u003cowner/repo\u003e`: only show these repos (repeatable or comma-delimited)\n- `--exclude \u003cowner/repo\u003e`: hide these repos (repeatable or comma-delimited)\n- `--include-team-requests`: include PRs requested to teams you are in (default: user-only)\n- `--bell`: ring terminal bell when a PR enters **NEEDS YOU** or when CI fails\n- `--no-notifications`: disable desktop notifications (enabled by default)\n- `--hide-pr-numbers`: hide PR numbers column\n- `--hide-repo`: hide repository column\n- `--hide-author`: hide author column\n- `--no-cache`: start empty (skip cached PRs) and rely on fresh refresh\n- `--purge-cache`: delete the cache DB before starting (also works with `--demo`)\n\n```bash\nneedle --days 7\n```\n\n## Configuration File\n\nNeedle automatically creates a config file at `~/.config/needle/config.toml` on first run. All CLI options can be set as defaults in this file:\n\n```toml\n# Only include PRs updated in the last N days\ndays = 14\n\n# Only show PRs from these orgs/users\norg = [\"my-company\", \"my-username\"]\n\n# Exclude noisy repos\nexclude = [\"my-company/legacy-repo\"]\n\n# Ring terminal bell on important events\nbell = true\n\n# Auto-refresh intervals (seconds)\nrefresh_interval_list_secs = 120\nrefresh_interval_details_secs = 15\n```\n\nCLI arguments always override config file values.\n\n## Pinned PRs\n\nPress `p` in list view to pin/unpin a PR. Pinned PRs appear in a dedicated **📌 PINNED** section at the top of the list, regardless of their score. Pin state persists across sessions.\n\n## Desktop Notifications\n\nNeedle shows desktop notifications for important events like CI failures, review requests, and PRs ready to merge.\n\n\u003cimg src=\"public/notifications.png\" width=\"100%\" alt=\"needle notifications\"\u003e\n\nOn macOS, install [terminal-notifier](https://github.com/julienXX/terminal-notifier) for click-to-open support:\n\n```bash\nbrew install terminal-notifier\n```\n\nWith `terminal-notifier` installed, clicking a notification opens the PR in your browser. Without it, notifications still work but won't be clickable.\n\n## Requirements\n\n- Rust (stable)\n- A GitHub Personal Access Token in `NEEDLE_GITHUB_TOKEN` or `GITHUB_TOKEN`\n\n## Get a GitHub token\n\nNeedle uses the GitHub API, so you’ll need a Personal Access Token.\n\n1. Go to https://github.com/settings/tokens?type=beta and create a **Fine-grained** token.\n2. Choose the account/organization that owns the repos you want to scan.\n3. Set **Repository access** to “All repositories” (or select specific repos).\n4. Add **Pull requests: Read-only** permissions.\n5. Copy the token and export it:\n\n```bash\nexport NEEDLE_GITHUB_TOKEN=\"ghp_...\"\n# or\nexport GITHUB_TOKEN=\"ghp_...\"\n```\n\n\u003e [!NOTE]\n\u003e If both are set, `NEEDLE_GITHUB_TOKEN` takes priority.\n\nAdd it to your bashrc/zshrc for future usage.\n\n## What it shows\n\nIncluded PRs:\n- PRs **authored by you**\n- PRs where **you are explicitly requested as a reviewer (User)**  \n  (team review requests are ignored unless `--include-team-requests`)\n\nFor each PR it computes:\n- Latest commit SHA\n- CI state (success/failure/running/none)\n- Review request state (requested/approved/none)\n- A hard-coded score → sorted desc → grouped into categories\n\n## Controls\n\nList view:\n- `↑ / ↓`: move selection\n- `Enter`: open selected PR in default browser\n- `Tab`: open details view\n- `p`: pin/unpin selected PR\n- `/`: filter mode (type to filter by repo/title/author/#)\n  - `Esc`: exit filter mode + clear filter text\n  - `Ctrl+n`: toggle \"only NEEDS YOU\"\n  - `Ctrl+c`: toggle \"only failing CI\"\n  - `Ctrl+v`: toggle \"only review requested\"\n  - `Ctrl+x`: clear all filters\n- `?`: help (explains what each section means)\n- `r`: refresh now (shows shimmer while refreshing)\n- `q`: quit\n\nDetails view:\n- `↑ / ↓`: select CI check\n- `Enter`: open selected CI check page (falls back to PR URL)\n- `f`: open first failing CI check (falls back to PR URL)\n- `Tab`: back to list\n- `r`: refresh now\n- `q`: quit\n\n### Details view CI checks\n\nIn details view you get a list of CI steps (check runs / status contexts):\n- ✅ success\n- ❌ failed\n- 🟡 running (shows “running for …” when `startedAt` is available)\n\n## Refresh behavior\n\n- Fetches on startup **in the background** (UI shows cached data immediately).\n- Manual refresh: `r`\n- Auto refresh in list view: every **3 minutes**\n- Auto refresh in details view: every **30s**\n- Manual refresh resets the auto-refresh timer.\n- No background async tasks beyond the single refresh worker thread.\n\n## Scoring\n\n- Single-screen list, visually grouped by derived category:\n  - **NEEDS YOU** (score \u003e= 40)\n  - **READY TO MERGE** (your PR, CI green, no blockers)\n  - **DRAFT** (draft PRs; shown separately and dimmed)\n  - **NO ACTION NEEDED** (0..39)\n  - **WAITING ON OTHERS** (\u003c 0)\n- Empty sections are hidden.\n- Draft rows are dimmed.\n- No scrolling beyond terminal height (truncates to fit).\n- Uses cached SQLite data to render immediately, then refreshes in the background.\n\nEach PR gets a score (higher = more urgent):\n\n```\n+50  review requested from user\n+40  CI failed AND state changed since last_seen\n+20  CI running longer than 10 minutes\n+15  approved but unmerged for \u003e24h\n-20  waiting on others (no review requested, CI green, not approved)\n-30  CI failed but unchanged since last_seen\n```\n\nSort:\n- Score desc\n- Then by updated timestamp desc\n\n## Troubleshooting\n\n- **Missing token**: set `GITHUB_TOKEN`.\n- **Not a TTY**: run in an interactive terminal (not a non-tty runner).\n\n## License\n\nMIT. See `LICENSE`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcesarferreira%2Fneedle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcesarferreira%2Fneedle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcesarferreira%2Fneedle/lists"}