Skip to content

3rdflr/github-mobile-reader

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

52 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“– github-mobile-reader

Transform GitHub PR diffs into mobile-friendly Markdown β€” understand what changed per function without reading long code.

npm version License: MIT Node.js β‰₯ 18

ν•œκ΅­μ–΄ λ¬Έμ„œ β†’


The Problem

GitHub's mobile web renders code in a fixed-width monospace block. Long lines require horizontal scrolling, and deeply nested logic is impossible to read on a commute.

The Solution

github-mobile-reader parses a git diff and produces a per-function summary β€” showing what each function/component added, removed, or changed rather than raw line diffs.


Example Output

## πŸ“„ `src/components/TodoList.tsx`

> πŸ’‘ Previously fetched all tasks unconditionally. Now accepts filter and sortOrder
> params β€” fetchTasks is called conditionally and filter state drives re-fetching.

**Import changes**
+ `FilterBar`
+ `useSortedTasks`
- `LegacyLoader` (removed)

**✏️ `TodoList`** _(Component)_ β€” changed
  λ³€μˆ˜: `filter`, `sortOrder`

**✏️ `fetchTasks`** _(Function)_ β€” changed
  νŒŒλΌλ―Έν„°+ `filter`
  νŒŒλΌλ―Έν„°+ `sortOrder`
  + (API) `fetchTasks(filter)` β†’ `tasks`

**βœ… `handleFilterChange`** _(Function)_ β€” added
  νŒŒλΌλ―Έν„°+ `field`
  νŒŒλΌλ―Έν„°+ `value`
  + (setState) `setFilter({...filter, [field]: value})`

Features

  • Per-function summaries β€” each function/component gets its own line with status (added / removed / changed); no headings, normal font size on mobile
  • Side-effect labels β€” behavior lines are prefixed with (API), (setState), (state), (cond), (catch), (guard) so you can tell at a glance what kind of change it is
  • Guard clause detection β€” if (!x) return patterns surfaced as (guard) early return entries
  • Import changes β€” newly added or removed imports at the file level
  • Parameter changes β€” added or removed function parameters
  • Variables β€” simple variable assignments attached to the nearest function shown inline
  • UI changes β€” added/removed JSX components (generic tags like div, span are filtered); map (πŸ”„) and conditional (⚑) patterns
  • Props changes β€” TypeScript interface/type member changes (long string values abbreviated to '...')
  • Gemini AI summaries (optional) β€” focuses on business logic change and side effects, not raw lines (> πŸ’‘ ...)
  • Secure by default β€” tokens are injected via environment variables only; no flag that leaks to shell history

Table of Contents

  1. CLI Usage
  2. GitHub Action
  3. Gemini AI Summaries (optional)
  4. Output Format
  5. npm Library Usage
  6. Language Support
  7. Project Structure
  8. Contributing

CLI Usage

Run directly with npx β€” no setup or config file needed.

Authentication

export GITHUB_TOKEN=ghp_xxxx

Security note: The CLI does not accept a --token flag. Passing secrets as CLI arguments exposes them in shell history and ps output.

Single PR

npx github-mobile-reader --repo owner/repo --pr 42

Single PR with Gemini AI summaries

GEMINI_API_KEY=AIzaSy... npx github-mobile-reader --repo owner/repo --pr 42

All recent PRs

npx github-mobile-reader --repo owner/repo --all --limit 20

Options

Flag Default Description
--repo (required) Repository in owner/repo format
--pr β€” Process a single PR by number
--all β€” Process all recent PRs (use with --limit)
--out ./reader-output Output directory β€” relative paths only, no ..
--limit 10 Max number of PRs to fetch when using --all
--gemini-key β€” Gemini API key (or set GEMINI_API_KEY env var)

Token: read from $GITHUB_TOKEN (60 req/hr unauthenticated, 5,000 req/hr authenticated).

Each PR produces one file: reader-output/pr-<number>.md


GitHub Action

Automatically generates a Reader document and posts a comment on every PR.

Step 1 β€” Add the workflow file

Create .github/workflows/mobile-reader.yml:

name: πŸ“– Mobile Reader

on:
  pull_request:
    types: [opened, synchronize, reopened]

permissions:
  contents: write       # commit the generated .md file
  pull-requests: write  # post the PR comment

jobs:
  generate-reader:
    name: Generate Mobile Reader View
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          base_branch: ${{ github.base_ref }}
          output_dir: docs/reader
          gemini_api_key: ${{ secrets.GEMINI_API_KEY }}  # optional
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Post PR Comment
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const path = './reader-output/pr-${{ github.event.pull_request.number }}.md';
            if (!fs.existsSync(path)) { console.log('No reader file generated.'); return; }
            const body = fs.readFileSync(path, 'utf8');
            const comments = await github.rest.issues.listComments({
              owner: context.repo.owner, repo: context.repo.repo,
              issue_number: ${{ github.event.pull_request.number }},
            });
            const prev = comments.data.find(c =>
              c.user.login === 'github-actions[bot]' && c.body.startsWith('# πŸ“– PR #')
            );
            if (prev) await github.rest.issues.deleteComment({
              owner: context.repo.owner, repo: context.repo.repo, comment_id: prev.id,
            });
            await github.rest.issues.createComment({
              owner: context.repo.owner, repo: context.repo.repo,
              issue_number: ${{ github.event.pull_request.number }}, body,
            });

Step 2 β€” Open a PR

Every subsequent PR will automatically receive:

  • A Reader Markdown file at docs/reader/pr-<number>.md
  • A summary comment on the PR

Action Inputs

Input Required Default Description
github_token βœ… β€” Use ${{ secrets.GITHUB_TOKEN }}
base_branch ❌ main Base branch the PR is merging into
output_dir ❌ docs/reader Directory for generated .md files
gemini_api_key ❌ β€” Gemini API key β€” omit to disable AI summaries

Gemini AI Summaries (optional)

Even complex hooks like useCanvasRenderer (200+ lines) get summarized in 1–3 sentences. Without an API key, behavior is identical β€” no errors, no fallback output.

Uses Gemini 2.5 Flash Lite β€” fast, low-cost, no thinking overhead.

Get a free API key

aistudio.google.com/apikey

CLI

# via environment variable (recommended)
GEMINI_API_KEY=AIzaSy... npx github-mobile-reader --repo owner/repo --pr 42

# via flag
npx github-mobile-reader --repo owner/repo --pr 42 --gemini-key AIzaSy...

GitHub Action

  1. Go to Settings β†’ Secrets and variables β†’ Actions β†’ New repository secret
  2. Name: GEMINI_API_KEY, Value: your key
  3. Add gemini_api_key: ${{ secrets.GEMINI_API_KEY }} to the workflow (see example above)

Security: GitHub Secrets are masked in all workflow logs and never exposed in plain text.


Output Format

# πŸ“– PR #7 β€” feat: add task filtering and sort controls

> Repository: owner/repo
> Commit: `3f8a21c`
> Changed JS/TS files: 3

---

## πŸ“„ `src/components/TodoList.tsx`

> πŸ’‘ Previously fetched all tasks unconditionally. Now accepts filter and sortOrder
> params β€” fetchTasks is called conditionally and filter state drives re-fetching.

**Import changes**
+ `FilterBar`
+ `useSortedTasks`
- `LegacyLoader` (removed)

**✏️ `TodoList`** _(Component)_ β€” changed
  λ³€μˆ˜: `filter`, `sortOrder`
  + (μƒνƒœ) `filter` ← `useState({})`
  + `useEffect` [filter] λ³€κ²½ μ‹œ μ‹€ν–‰

**✏️ `fetchTasks`** _(Function)_ β€” changed
  νŒŒλΌλ―Έν„°+ `filter`
  νŒŒλΌλ―Έν„°+ `sortOrder`
  + (guard) `!filter` β†’ early return
  + (API) `api.getTasks(filter)` β†’ `tasks`

**βœ… `handleFilterChange`** _(Function)_ β€” added
  νŒŒλΌλ―Έν„°+ `field`
  νŒŒλΌλ―Έν„°+ `value`
  + (setState) `setFilter({...filter, [field]: value})`

**✏️ `TaskCard`** _(Component)_ β€” changed
  Props+ `dueDate: '...'`
  + (cond) `!task.completed`
  UI: `<Badge>`

Symbol classification

Label Meaning
βœ… ... β€” added Function/component newly introduced in the diff
❌ ... β€” removed Function/component deleted in the diff
✏️ ... β€” changed Existing function/component with modified content
λ³€μˆ˜: x, y Simple variable assignments collapsed inline

Line prefixes

Prefix Meaning
(API) await call β€” fetches data from a server or external service
(setState) setState call β€” updates React state
(state) Hook assignment β€” const x = useHook()
(cond) if / else if branch
(guard) Guard clause β€” if (!x) return early-exit pattern
(catch) catch block
(return) Non-trivial return value
νŒŒλΌλ―Έν„°+ / νŒŒλΌλ―Έν„°- Function parameter added / removed
Props+ / Props- TypeScript interface/type member added / removed
UI: JSX component added or removed

npm Library Usage

npm install github-mobile-reader
import { generateReaderMarkdown } from 'github-mobile-reader';
import { execSync } from 'child_process';

const diff = execSync('git diff HEAD~1 HEAD', { encoding: 'utf8' });
const markdown = generateReaderMarkdown(diff, {
  pr: '42',
  commit: 'a1b2c3d',
  file: 'src/api/users.ts',
  repo: 'my-org/my-repo',
});

console.log(markdown);

Public API

import {
  generateReaderMarkdown,  // diff β†’ complete Markdown document
  parseDiffHunks,          // diff β†’ DiffHunk[]
  attributeLinesToSymbols, // DiffHunk[] β†’ SymbolDiff[]
  generateSymbolSections,  // SymbolDiff[] β†’ string[]
  extractImportChanges,    // detect added/removed imports
  extractParamChanges,     // detect added/removed function parameters
} from 'github-mobile-reader';

generateReaderMarkdown(diffText, meta?)

Parameter Type Description
diffText string Raw git diff output
meta.pr string? Pull request number
meta.commit string? Commit SHA
meta.file string? File name
meta.repo string? Repository in owner/repo format

Returns: string β€” the complete Markdown document.

SymbolDiff

interface SymbolDiff {
  name: string;
  kind: 'component' | 'function' | 'setup';
  status: 'added' | 'removed' | 'modified';
  addedLines: string[];
  removedLines: string[];
}

Language Support

The parser is optimized for JS/TS syntax patterns.

Language Extensions Support
JavaScript .js .mjs .cjs βœ… Full
TypeScript .ts βœ… Full
React JSX .jsx βœ… Full
React TSX .tsx βœ… Full
Others β€” πŸ”œ Planned

Project Structure

github-mobile-reader/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ parser.ts    ← diff parsing and symbol analysis (core logic)
β”‚   β”œβ”€β”€ gemini.ts    ← Gemini 2.5 Flash Lite AI summaries (opt-in)
β”‚   β”œβ”€β”€ index.ts     ← public npm API
β”‚   β”œβ”€β”€ action.ts    ← GitHub Action entry point
β”‚   β”œβ”€β”€ cli.ts       ← CLI entry point
β”‚   └── test.ts      ← smoke tests (npx ts-node src/test.ts)
β”œβ”€β”€ dist/            ← compiled output (auto-generated)
β”œβ”€β”€ reader-output/   ← CLI output directory (gitignored)
β”œβ”€β”€ action.yml       ← GitHub Action definition
└── package.json

Contributing

git clone https://github.com/3rdflr/github-mobile-reader.git
cd github-mobile-reader
npm install
npm run build:all        # build library + Action + CLI
npx ts-node src/test.ts  # run smoke tests

Pull requests are welcome.


License

MIT Β© 3rdflr

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •