Skip to content

Latest commit

 

History

17 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

My GitHub Assignments

The PRs and issues assigned to you, in the menu bar.

A native macOS tray of your open GitHub assignments: they appear when assigned, disappear when closed or unassigned, and you get one toast the moment something new lands on your plate. A worklist, not an inbox — GitHub's assignment state IS the state.

Warning

Alpha, personal tool — provided as-is, without warranty or support. There is no release cadence, no security-patch SLA, and no compatibility promise. No packaged releases — build from source. The app is ad-hoc signed, not notarized. Use at your own risk.

Features

  • Your assigned work, always one click away. Polls one GitHub Search query — assignee:@me is:open — every 60 seconds and renders it as a menu-bar count plus a popover grouped org → repo, newest activity first. Click any row to open it on GitHub.
  • Live assignment toasts. One native notification per newly assigned item, within one poll; the banner's Open button (or a plain click) jumps straight to the PR/issue. Structurally storm-proof: first launch, relaunches, and re-authentication are always silent.
  • Reviewed marks. Flag any open item as reviewed with the row's check button — a local-only mark (never written to GitHub) — and flip the header filter between All and To review. The menu-bar count follows the filter, so it reads as "things still needing me". Marks clear themselves when an item leaves your list.
  • Filters that never refetch. Show/hide PRs, issues, and toasts from Settings; everything re-renders instantly from the retained snapshot.
  • Graceful degradation. Network blips and rate limits serve the last-good list with a "cached data" banner; rate limiting shows a live retry countdown and slows polling automatically; every error kind gets its own actionable banner (missing token, revoked token, org policy, and so on).
  • Native and tiny. Rust CLI + SwiftUI shell — no Electron, zero third-party Swift dependencies, an icon rendered from code at build time.
  • Security-first by construction.
    • 100% read-only: the codebase contains only GET requests — verifiable with one grep of crates/my-github-assignments-client/src/.
    • A pasted token lives in your login Keychain, handled exclusively by the Rust layer; never in argv, files, or logs (tested with a sentinel token). A token sourced from the gh CLI instead (see below) is resolved in memory per run and never written by this app — though it may live in gh's own storage and may originate from an inherited GH_TOKEN/GITHUB_TOKEN.
    • Only egress is api.github.com; an overridden endpoint can never receive the real token (exfil guard, tested). No telemetry.
    • Cache files (auth metadata + last-good list) are readable by your user only, written crash-safely (temp file + atomic rename), and symlink attacks are refused.
    • Launch-at-login uses the system's SMAppService state as the single source of truth.

Install

Prerequisites

macOS 13+, Rust, and the Xcode Command Line Tools (xcode-select --install).

Human path (three commands)

git clone https://github.com/randymorales/my-github-assignments.git my-github-assignments && cd my-github-assignments
cargo test --workspace           # everything should be green
macos/build-app.sh --install     # builds + installs + launches /Applications/MyGitHubAssignments.app

Then authenticate: right-click the menu-bar icon → Settings… → paste a token (see Authenticate).

Delegating the install to an AI agent

The build and install are safely delegable; the token is not. Hand your agent this prompt:

Install My GitHub Assignments from https://github.com/randymorales/my-github-assignments by following the "Agent install runbook" in its README exactly. Verify every checkpoint, report the outputs, and stop at the human-only step — never ask me for, accept, or handle a GitHub token.

Agent install runbook

  1. Verify prerequisites (install what's missing):
    • xcode-select -p prints a developer-dir path.
    • cargo --version ≥ 1.89 · swift --version reports Swift ≥ 5.9.
  2. Clone and test: git clone https://github.com/randymorales/my-github-assignments.git my-github-assignments && cd my-github-assignments, then cargo test --workspace. ✅ Checkpoint: every suite prints test result: ok. … 0 failed.
  3. Fixture integrity: dev/check-fixtures.sh. ✅ Checkpoint: fixtures OK (synthetic constraints hold).
  4. Build + install: macos/build-app.sh --install. ✅ Checkpoint: output contains built: and installed: /Applications/MyGitHubAssignments.app; the tray icon appears in the menu bar (the app auto-launches).
  5. Verify the artifact:
    • codesign -dv /Applications/MyGitHubAssignments.app → Identifier=dev.randymorales.MyGitHubAssignments.
    • /Applications/MyGitHubAssignments.app/Contents/Resources/my-github-assignments --version → {"schema_version": 1, "version": "…"}.
  6. STOP — hand back to the human. The remaining step is authentication, and it is human-only by design: the token must go from the human's clipboard directly into the app's Settings field (it travels stdin → Keychain and never touches disk). An AI agent must never request, receive, store, or paste the token — a credential shared with an agent ends up in conversation logs.

Troubleshooting (for humans and agents)

Symptom Fix
Gatekeeper blocks a downloaded copy Right-click the .app → Open (once), or xattr -dr com.apple.quarantine /Applications/MyGitHubAssignments.app
Keychain prompt after an update Expected — ad-hoc identity changes per build. Click "Always Allow".
No toasts System Settings → Notifications → allow My GitHub Assignments; if you use Focus/Do Not Disturb, add it to the allowed apps.
Empty list, "token was denied access" Org token policy — see Authenticate.
Updating the app Re-run macos/build-app.sh --install; it quits, replaces, and relaunches.

Authenticate

Right-click the menu-bar icon → Settings… → paste a token. Two accepted forms:

Form What one token can see Trade-off
Fine-grained PAT (github_pat_…) One resource owner only: pick the org, select "All repositories" explicitly, grant read-only Issues + Pull requests + Metadata → that org's private repos plus all public repos. Private assignments in personal repos or other orgs are invisible. Truly read-only, least privilege. Some orgs gate fine-grained PATs behind an approval flow.
Classic PAT (ghp_…) with repo All repos you can access, all owners. Broad read/write grant — the app never writes, but the token could.

The app probes any pasted token (the canonical query + GET /user) before storing it in your login Keychain; a failing probe stores nothing. The probe proves the token authenticates — it can NOT prove repo coverage: repos a token can't see are silently absent from results, never an error. After authenticating, compare the count against https://github.com/pulls/assigned once.

Already using the gh CLI? No paste needed.

If you're logged in with the gh CLI (gh auth login), the app uses that login automatically when the Keychain is empty — zero configuration. A pasted token always wins; the gh token is resolved fresh on each poll (gh auth token --hostname github.com) and never written to disk by this app. Re-running gh auth login heals the app.

Two things worth knowing:

  • Scopes come from your gh login. Coverage matches whatever your gh token can see. A gh web-flow login typically has broad repo scope; verify against https://github.com/pulls/assigned as above.
  • gh honors GH_TOKEN/GITHUB_TOKEN. If either is set in the app's environment, gh returns that value ahead of your stored login — the app does not read those variables itself.

Turn the fallback off in Settings → "Use the gh CLI login when no token is pasted" (this makes Clear token fully de-authenticate). Settings shows the active source, so you always know whether you're on a pasted token or the gh login. The gh binary is found at $MY_GITHUB_ASSIGNMENTS_GH_BIN, /opt/homebrew/bin/gh, /usr/local/bin/gh, or on PATH.

CLI

The app is a thin shell over a one-shot CLI you can use directly:

./target/release/my-github-assignments auth set     # token via stdin
./target/release/my-github-assignments list        # the JSON contract below
./target/release/my-github-assignments auth status # no network call
./target/release/my-github-assignments auth clear  # removes token + cached data

list and auth status fall back to the gh CLI when no token is stored; set MY_GITHUB_ASSIGNMENTS_DISABLE_GH_CLI=1 to skip it. token_source in the output reports which store was used (keychain / gh_cli / null).

Every subcommand prints JSON on every outcome (exit 0 ok/stale · 1 hard error · 2 usage). The committed contract golden, verbatim:

{
  "schema_version": 1,
  "user": {
    "login": "octocat",
    "id": 1
  },
  "token_source": "keychain",
  "fetched_at": "2026-07-24T15:04:00Z",
  "poll_interval_secs": 60,
  "items": [
    {
      "id": "1000000001",
      "type": "pr",
      "org": "acme",
      "repo": "widgets",
      "number": 42,
      "title": "fix(api): synthetic example title",
      "url": "https://github.com/acme/widgets/pull/42",
      "author": "hubot",
      "created_at": "2026-07-20T10:00:00Z",
      "updated_at": "2026-07-23T17:59:12Z"
    }
  ],
  "stale": null,
  "stale_error": null,
  "error": null
}

Architecture

    ┌──────────────────────────────────┐
    │ MyGitHubAssignments.app (Swift)  │   NEVER links Rust
    │ status item · grouped list       │   NEVER persists/reads back the token
    │ toasts · settings                │
    └────────┬─────────▲───────────────┘
      spawn: │         │ stdout: frozen JSON contract
      PAT via│         │ stderr: diagnostics · exit 0/1/2
      stdin ►▼         │
    ┌──────────────────────────────────┐
    │ my-github-assignments CLI (Rust) │   crates: core ← client ← cli
    └────┬──────────┬───────┬──────────┘
         ▼          ▼       ▼
     GitHub      Keychain  ~/.cache/my-github-assignments
     Search API  (token)   (state.json · snapshot.json)

my-github-assignments-core is pure logic (no I/O deps by construction); my-github-assignments-client is the only crate that touches the network or Keychain; my-github-assignments-cli owns the JSON contract. Details: AGENTS.md and docs/ADR/.

For an AI reviewer

Read files in this order to audit the tool:

  1. docs/ADR/001-two-process-architecture.md — the token boundary (I7).
  2. crates/my-github-assignments-core/src/error.rs + schema.rs — the vocabulary and status precedence.
  3. crates/my-github-assignments-cli/src/output.rs — the JSON contract everything hinges on.
  4. crates/my-github-assignments-client/src/auth.rs + gh.rs — token handling, Keychain, the gh CLI fallback + resolution chain, probe, storage lifecycle. Security-critical.
  5. crates/my-github-assignments-client/src/http.rs + search.rs — status mapping, exfil guard, canonical query, completeness rule, read-only verification (GET-only).
  6. macos/MyGitHubAssignments/Sources/MyGitHubAssignments/CLIRunner.swift + DataController.swift + Notifier.swift — subprocess seam, toast baseline, UN delegate flow.
  7. Tests: #[cfg(test)] modules, dev/fixtures/, the list goldens.

Build & test (development)

cargo fmt --all --check && cargo clippy --all-targets -- -D warnings
cargo test --workspace          # unit + goldens + wire (httpmock) + assert_cmd
dev/check-fixtures.sh           # synthetic-fixture constraints
macos/build-app.sh [--debug]    # stage the bundle only (what CI runs)

# Swift UI dev without Rust, network, or token:
MY_GITHUB_ASSIGNMENTS_BIN="$PWD/dev/mock-cli.sh" MY_GITHUB_ASSIGNMENTS_SCENARIO=list-stale \
  swift run --package-path macos/MyGitHubAssignments

MIT © Randy Morales 2026

About

Your assigned GitHub PRs and issues, in the macOS menu bar

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages