Build a terminal-based GitHub repository and issue browser using the GitHub REST API.
- Overview
- Prerequisites
- Complete Configuration
- Usage Instructions
- Features
- Security Notes
- Troubleshooting
- API Reference
This example demonstrates how to build a functional GitHub repository browser using TermStack and the GitHub REST API. It includes:
- Repository listing with sorting and styling
- Issue browser with status filtering
- Multi-page navigation with context passing
- Secure authentication using environment variables
What You'll Build:
Repositories (list) → Repository Details (YAML view)
→ Issues (list)
You need a GitHub personal access token to authenticate with the API.
Create Token:
- Visit https://github.com/settings/tokens
- Click "Generate new token" → "Generate new token (classic)"
- Give it a descriptive name (e.g., "TermStack Browser")
- Select scopes:
- ✅
repo(Full control of private repositories) - ✅
read:org(Read org and team membership)
- ✅
- Click "Generate token"
- Copy the token immediately (you won't see it again!)
Token Format: ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Linux/macOS
export GITHUB_TOKEN="ghp_your_token_here"
# Add to shell profile for persistence
echo 'export GITHUB_TOKEN="ghp_your_token_here"' >> ~/.bashrc
source ~/.bashrc
# Verify it's set
echo $GITHUB_TOKEN# Install TermStack if not already installed
cargo install termstack
# Or build from source
git clone https://github.com/pa/termstack.git
cd termstack
cargo build --releaseSave this as github-repos.yaml:
# =============================================================================
# GitHub Repository Browser
# =============================================================================
# Browse your GitHub repositories and issues using the GitHub REST API.
#
# Prerequisites:
# 1. GitHub personal access token with 'repo' and 'read:org' scopes
# 2. Set environment variable: export GITHUB_TOKEN="ghp_your_token"
#
# Usage: termstack github-repos.yaml
#
# This example demonstrates:
# - HTTP adapter with Bearer token authentication
# - Environment variable usage for secure token storage
# - Multi-page navigation with context passing
# - Conditional styling based on status
# - JSONPath data extraction
# - Template interpolation in URLs and headers
#
# API Documentation: https://docs.github.com/en/rest
# =============================================================================
version: v1
app:
name: "GitHub Repository Browser"
description: "Browse your GitHub repos and issues"
theme: "default"
globals:
# SECURITY: Token read from environment variable (not stored in file)
github_token: "{{ env.GITHUB_TOKEN }}"
# GitHub API base URL (can be overridden for GitHub Enterprise)
api_base: "{{ env.GITHUB_API_BASE | default(value='https://api.github.com') }}"
start: repos
pages:
# ==========================================================================
# REPOSITORIES - List all repositories for authenticated user
# ==========================================================================
repos:
title: "My Repositories"
description: "All repositories you have access to"
data:
adapter: http
url: "{{ api_base }}/user/repos"
method: GET
headers:
# Bearer token authentication
Authorization: "Bearer {{ github_token }}"
# GitHub API version (recommended)
Accept: "application/vnd.github.v3+json"
# User agent (required by GitHub)
User-Agent: "TermStack-GitHub-Browser"
# Query parameters
params:
sort: "updated" # Sort by last updated
direction: "desc" # Newest first
per_page: "100" # Max items per page
type: "all" # all, owner, public, private, member
# Extract array of repositories
items: "$[*]"
# Auto-refresh every 5 minutes
refresh_interval: "5m"
# Timeout for API request
timeout: "30s"
view:
type: table
columns:
# Repository name
- path: "$.name"
display: "Repository"
width: 40
style:
- default: true
color: cyan
bold: true
# Visibility (public/private)
- path: "$.visibility"
display: "Visibility"
width: 10
style:
- condition: "{{ value == 'public' }}"
color: green
- condition: "{{ value == 'private' }}"
color: yellow
- default: true
color: gray
# Star count
- path: "$.stargazers_count"
display: "⭐ Stars"
width: 10
style:
- default: true
color: yellow
# Fork count
- path: "$.forks_count"
display: "🔱 Forks"
width: 10
style:
- default: true
color: blue
# Open issues count
- path: "$.open_issues_count"
display: "Issues"
width: 8
style:
- condition: "{{ value | int > 0 }}"
color: red
- default: true
color: green
# Primary language
- path: "$.language"
display: "Language"
width: 15
style:
- default: true
color: magenta
# Last updated time
- path: "$.updated_at"
display: "Updated"
width: 20
transform: "{{ value | timeago }}"
style:
- default: true
color: gray
# Sort by name column
sort:
column: "$.name"
order: asc
# Navigation: Press Enter to view repository details
next:
page: repo_detail
context:
repo_name: "$.full_name"
repo_url: "$.url"
repo_owner: "$.owner.login"
# Actions: Use Shift+A menu or Ctrl+key shortcuts
actions:
- key: "ctrl+i"
name: "View Issues"
description: "Browse repository issues"
page: "repo_issues"
context:
repo_name: "$.full_name"
repo_owner: "$.owner.login"
# ==========================================================================
# REPOSITORY DETAILS - Detailed view of selected repository
# ==========================================================================
repo_detail:
title: "Repository: {{ repos.name }}"
description: "Detailed information about {{ repos.full_name }}"
data:
adapter: http
url: "{{ repos.url }}"
method: GET
headers:
Authorization: "Bearer {{ github_token }}"
Accept: "application/vnd.github.v3+json"
User-Agent: "TermStack-GitHub-Browser"
timeout: "30s"
view:
type: text
syntax: yaml # Display as formatted YAML
# Press Enter to go back to repository list
actions:
- key: "ctrl+i"
name: "View Issues"
description: "Browse issues for this repository"
page: "repo_issues"
context:
repo_name: "{{ repos.full_name }}"
# ==========================================================================
# REPOSITORY ISSUES - List issues for selected repository
# ==========================================================================
repo_issues:
title: "Issues - {{ repos.name }}"
description: "Open issues for {{ repos.full_name }}"
data:
adapter: http
url: "{{ api_base }}/repos/{{ repos.full_name }}/issues"
method: GET
headers:
Authorization: "Bearer {{ github_token }}"
Accept: "application/vnd.github.v3+json"
User-Agent: "TermStack-GitHub-Browser"
params:
state: "open" # open, closed, all
sort: "updated" # created, updated, comments
direction: "desc" # asc, desc
per_page: "50"
items: "$[*]"
refresh_interval: "2m"
timeout: "30s"
view:
type: table
columns:
# Issue number
- path: "$.number"
display: "#"
width: 6
style:
- default: true
color: cyan
# Issue title
- path: "$.title"
display: "Title"
width: 60
style:
- default: true
color: white
bold: true
# State (open/closed)
- path: "$.state"
display: "State"
width: 10
style:
- condition: "{{ value == 'open' }}"
color: green
bold: true
- condition: "{{ value == 'closed' }}"
color: red
- default: true
color: gray
# Comment count
- path: "$.comments"
display: "💬"
width: 5
style:
- condition: "{{ value | int > 0 }}"
color: cyan
- default: true
color: gray
# Author
- path: "$.user.login"
display: "Author"
width: 15
style:
- default: true
color: magenta
# Created time
- path: "$.created_at"
display: "Created"
width: 20
transform: "{{ value | timeago }}"
style:
- default: true
color: gray
sort:
column: "$.updated_at"
order: descSave the YAML configuration above as github-repos.yaml in your project directory.
export GITHUB_TOKEN="ghp_your_actual_token_here"Verify it's set:
echo $GITHUB_TOKEN
# Should output: ghp_xxxxx...termstack github-repos.yamlMain View (Repositories):
j/↓- Move downk/↑- Move upg- Jump to topG- Jump to bottomEnter- View repository detailsShift+Athen select, orCtrl+I- View repository issues/- Search repositoriesr- Refresh dataq- QuitEsc- Go back
Repository Details View:
- Scroll through YAML details
Shift+Athen select, orCtrl+I- View issuesEsc- Back to repository list
Issues View:
- Browse issues for the selected repository
Esc- Back to repository list
- ✅ Secure: Token stored in environment variable, not in file
- ✅ Bearer Token: Standard OAuth2 authentication
- ✅ API Versioning: Uses GitHub API v3
- ✅ User Agent: Required by GitHub API
- ✅ Sorting: By name, updated date
- ✅ Filtering: All repos, public, private
- ✅ Statistics: Stars, forks, issues
- ✅ Language: Primary programming language
- ✅ Styling: Color-coded visibility and issues
- ✅ Time: Human-readable "updated 2 hours ago"
- ✅ Full Info: Complete repository metadata
- ✅ YAML Format: Pretty-printed, syntax highlighted
- ✅ Scrollable: Navigate large responses
- ✅ Status: Open/closed with color coding
- ✅ Sorting: By created, updated, comments
- ✅ Metadata: Author, comment count, timestamps
- ✅ Navigation: Back to repository list
Use Environment Variables:
globals:
github_token: "{{ env.GITHUB_TOKEN }}"Add to .gitignore:
echo "*.secrets.yaml" >> .gitignore
echo ".env" >> .gitignoreUse Scoped Tokens:
- Only grant required scopes (repo, read:org)
- Don't use tokens with admin access
- Create separate tokens for different tools
Rotate Tokens:
# Regenerate tokens every 90 days
# Visit: https://github.com/settings/tokensHardcode Tokens:
# BAD - token visible in file
globals:
github_token: "ghp_visible_token_in_file"Commit Secrets:
# BAD - token in git history
git add github-repos.yaml # Contains token
git commitShare Config Files:
# BAD - exposes your token
slack upload github-repos.yamlProblem: API returns "Bad credentials" or 401 status
Solutions:
-
Verify token is set:
echo $GITHUB_TOKEN
-
Check token is valid:
curl -H "Authorization: Bearer $GITHUB_TOKEN" \ https://api.github.com/user -
Regenerate token if expired:
- Visit https://github.com/settings/tokens
- Delete old token
- Generate new token with same scopes
Problem: API returns "Resource not accessible by integration" or 403 status
Solutions:
-
Check token scopes:
- Visit https://github.com/settings/tokens
- Verify
repoandread:orgare checked
-
Wait for rate limit reset:
curl -H "Authorization: Bearer $GITHUB_TOKEN" \ https://api.github.com/rate_limit
Problem: No repositories shown
Solutions:
-
Check if you have any repositories:
curl -H "Authorization: Bearer $GITHUB_TOKEN" \ https://api.github.com/user/repos | jq length
-
Adjust
typeparameter:params: type: "all" # Try: all, owner, public, private, member
Problem: Error: "Variable 'env.GITHUB_TOKEN' not found"
Solutions:
-
Set environment variable:
export GITHUB_TOKEN="ghp_your_token"
-
Verify TermStack version supports
{{ env.VAR }}syntax -
Use shell wrapper as fallback:
#!/bin/bash export GITHUB_TOKEN="ghp_your_token" termstack github-repos.yaml
- REST API: https://docs.github.com/en/rest
- Authentication: https://docs.github.com/en/rest/authentication
- Rate Limits: https://docs.github.com/en/rest/rate-limit
| Endpoint | Purpose | Docs |
|---|---|---|
GET /user/repos |
List user repositories | Link |
GET /repos/{owner}/{repo} |
Get repository details | Link |
GET /repos/{owner}/{repo}/issues |
List repository issues | Link |
Authenticated requests:
- 5,000 requests per hour
- Check remaining:
curl -H "Authorization: Bearer $TOKEN" https://api.github.com/rate_limit
Unauthenticated requests:
- 60 requests per hour (don't do this)
Want to add more features? Try these:
pages:
repo_prs:
data:
adapter: http
url: "{{ api_base }}/repos/{{ repos.full_name }}/pulls"
headers:
Authorization: "Bearer {{ github_token }}"
params:
state: "open"pages:
repo_releases:
data:
adapter: http
url: "{{ api_base }}/repos/{{ repos.full_name }}/releases"
headers:
Authorization: "Bearer {{ github_token }}"pages:
orgs:
data:
adapter: http
url: "{{ api_base }}/user/orgs"
headers:
Authorization: "Bearer {{ github_token }}"- Authentication Guide - Comprehensive auth documentation
- Templates & Context Guide - Template syntax and navigation
- AWS CLI Integration - Similar example for AWS
- Documentation Hub - Central documentation index