npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

claude-cast

v0.1.0

Published

Share Claude Code sessions via GitHub Gist

Readme

claude-cast

Turn Claude Code sessions into shareable HTML or Markdown — then optionally upload to GitHub Gist.

Claude Code stores every conversation as a .jsonl file, but those files are messy internal logs full of streaming fragments, progress updates, and system bookkeeping. claude-cast parses them, throws out the noise, merges the streaming duplicates, and produces a clean readable document.

Quick Start

# List your recent sessions
npx claude-cast --list

# Export the latest session as HTML
npx claude-cast latest -o session.html

# Export by session name
npx claude-cast greedy-squishing-spring -o session.html

# Interactive picker (arrow keys to select)
npx claude-cast

# Upload to GitHub Gist
npx claude-cast latest -f md --gist

Installation

# Run directly (no install)
npx claude-cast

# Or install globally
npm install -g claude-cast
claude-cast --list

CLI Reference

Usage: claude-cast [options] [session]

Arguments:
  session                Session UUID, slug, or 'latest'

Session Selection

| Command | Description | |---|---| | claude-cast | Interactive picker — browse recent sessions | | claude-cast latest | Most recently modified session | | claude-cast <slug> | Find by slug name (e.g. greedy-squishing-spring) | | claude-cast <uuid> | Find by session UUID | | claude-cast --list | List 20 recent sessions with previews |

Output Options

| Flag | Description | Default | |---|---|---| | -f, --format <format> | Output format: html or md | html | | -o, --output <path> | Write to file instead of stdout | stdout | | --dry-run | Force output to stdout | — |

Content Options

| Flag | Description | Default | |---|---|---| | --include-thinking | Include Claude's extended thinking blocks | excluded | | --no-tool-results | Hide tool call outputs | included |

Gist Upload

| Flag | Description | |---|---| | --gist | Upload to GitHub Gist (secret by default) | | --gist-public | Make the gist publicly visible | | --token <token> | GitHub personal access token |

Examples

# HTML file with thinking blocks visible
claude-cast latest --include-thinking -o session.html

# Markdown without tool outputs (clean reading)
claude-cast latest -f md --no-tool-results -o session.md

# Upload specific session to public gist
claude-cast greedy-squishing-spring -f md --gist --gist-public

# Pipe HTML to clipboard (macOS)
claude-cast latest | pbcopy

Gist Setup

claude-cast resolves GitHub tokens in this order:

  1. --token flagclaude-cast latest --gist --token ghp_xxx
  2. GITHUB_TOKEN env varexport GITHUB_TOKEN=ghp_xxx
  3. GH_TOKEN env var — same as above, alternate name
  4. gh CLI — if you have GitHub CLI installed and authenticated

Getting a Token

Option A: GitHub CLI (recommended)

brew install gh
gh auth login
# Done — claude-cast will pick up your auth automatically

Option B: Personal Access Token

  1. Go to github.com/settings/tokens
  2. Generate new token (classic)
  3. Check only the gist scope
  4. Copy the ghp_... token
# Use inline
claude-cast latest -f md --gist --token ghp_xxx

# Or set as env var
export GITHUB_TOKEN=ghp_xxx
claude-cast latest -f md --gist

Output Formats

HTML

  • Self-contained single file — all CSS inlined, no external dependencies
  • Automatic dark/light theme (follows your OS setting)
  • Chat-style layout with colored borders: blue (you), green (Claude), purple (tool calls)
  • Collapsible tool calls and thinking blocks
  • Best for: sharing via file, opening in browser

Markdown

  • GitHub-flavored Markdown with <details> blocks for tools/thinking
  • Metadata table at the top (date, model, project, turns)
  • Auto-splits into multiple files if content exceeds 900KB (GitHub Gist limit)
  • Best for: uploading to Gist (renders natively on GitHub)

Smart Content Detection

claude-cast doesn't just dump raw tool data — it detects patterns in the conversation and renders them appropriately:

| Pattern | Raw Data | Rendered As | |---|---|---| | User Q&A answers | "question"="answer" pairs buried in tool result | Visible highlighted Q&A block | | Agent/Task results | Thousands of lines of agent output | Full formatted markdown (tables, code, headings) | | Plan files | Write tool JSON with .md content | Rendered markdown document, open by default | | Plan rejections | "The user doesn't want to proceed..." tool error | Clean "Plan was rejected" notice | | Interrupted messages | [Request interrupted by user for tool use] | Filtered out entirely | | Agent metadata | <usage> tags, agentId: lines | Stripped from output |


Architecture

The Problem

A Claude Code session file is a .jsonl log at ~/.claude/projects/<escaped-path>/<uuid>.jsonl. A typical session with 7 conversation exchanges produces 159 JSONL lines:

| Record type | Count | What it is | |---|---|---| | progress | 115 | Streaming progress updates | | assistant | 22 | Claude's responses (fragmented by streaming) | | user | 12 | Your messages + tool results + system injections | | file-history-snapshot | 4 | Internal file state tracking | | system | 4 | System configuration | | queue-operation | 2 | Internal queue management |

72% is streaming noise. The 22 assistant records are really 7 responses — each written as ~3 streaming snapshots. And several "user" records are actually system-injected messages, not things you typed.

Pipeline

  JSONL file
      │
      ▼
┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
│  Locate  │ ──▶ │  Filter  │ ──▶ │  Dedup   │ ──▶ │   Tree   │ ──▶ │  Render  │
│          │     │          │     │          │     │          │     │  HTML/MD │
│ find the │     │ drop 72% │     │ 22 recs  │     │ attach   │     │          │
│ file     │     │ noise    │     │ → 7 turns│     │ tool     │     │          │
└──────────┘     └──────────┘     └──────────┘     │ results  │     └──────────┘
                                                   └──────────┘

Project Structure

claude-cast/
├── bin/
│   └── cli.ts              # CLI entry point (commander)
└── src/
    ├── types.ts             # All TypeScript interfaces
    ├── utils.ts             # Shared helpers
    ├── picker.ts            # Interactive session selector (inquirer)
    ├── gist.ts              # GitHub Gist upload (gh CLI + REST API)
    ├── parser/
    │   ├── index.ts         # parseSession() orchestrator
    │   ├── locate.ts        # Find sessions by UUID/slug/latest
    │   ├── filter.ts        # Remove noise records
    │   ├── dedup.ts         # Merge streaming fragments
    │   └── tree.ts          # Build conversation turns
    └── renderers/
        ├── markdown.ts      # Markdown output with byte-aware splitting
        ├── html.ts          # HTML renderer (uses marked)
        └── html-template.ts # Self-contained CSS + HTML shell

Module Walkthrough

Types (src/types.ts)

Two layers of data shapes:

Raw layer — what Claude Code writes to disk:

  • RawRecord — every line in the JSONL. Has type ("user", "assistant", "progress", ...), uuid, parentUuid (linking records into a chain), sessionId, requestId, and a message object.
  • RawMessage — the message inside a record. content is either a plain string (simple user messages) or an array of RawContentItem (structured content with tool calls, thinking blocks, etc).

Normalized layer — what the parser produces:

  • ContentItem — discriminated union: text | thinking | tool_use | tool_result
  • ConversationTurn — one message bubble. Role + timestamp + array of ContentItems.
  • ParsedSession — the final product: { meta: SessionMeta, turns: ConversationTurn[] }

Locator (src/parser/locate.ts)

Sessions are scattered across ~/.claude/projects/*/. Each project directory is a path with slashes replaced by dashes (e.g. /Users/faisal/Desktop/projects-Users-faisal-Desktop-projects).

  • findSessionByUuid() — matches against filenames (fast, no file reading)
  • findSessionBySlug() — reads the first user record of each file to check its slug field (slower, opens every file)
  • findLatestSession() — checks filesystem mtime of every .jsonl
  • listRecentSessions() — sorts by mtime, reads metadata from top N files

All file reading uses Node's readline with streams — reads only the first few lines, then closes. This matters when you have dozens of multi-MB session files.

Filter (src/parser/filter.ts)

Three rules:

  1. Drop noise typesprogress, system, file-history-snapshot, queue-operation, etc.
  2. Drop sidechain recordsisSidechain: true (branching explorations)
  3. Drop system user messages — messages starting with <system-reminder>, <command-name>, <local-command-caveat>, etc.

Dedup (src/parser/dedup.ts)

The most technically interesting module. When Claude responds, Claude Code writes multiple JSONL records for the same response — they share a requestId but each contains a partial content snapshot at that moment of streaming:

Record 1 (requestId=req_abc): content=[{type:"text", text:"\n\n"}]
Record 2 (requestId=req_abc): content=[{type:"thinking", thinking:"Let me..."}]
Record 3 (requestId=req_abc): content=[{type:"text", text:"Here's the answer..."}]

deduplicateAssistants():

  1. Groups assistant records by requestId
  2. Sorts each group by timestamp
  3. Collects all content items in order
  4. Deduplicates tool_use by ID (streaming can repeat the same tool call)
  5. Coalesces adjacent text blocks split by streaming

Result: one merged response per actual Claude reply.

Tree Builder (src/parser/tree.ts)

Handles the tricky part: tool result attachment.

In the raw data, when Claude calls a tool (like Read or Bash), the result comes back as a "user" record — but it's not something you typed. The tree builder:

  1. Indexes all tool_use items by their ID
  2. Finds tool_result records in user messages
  3. Splices each result right after its matching tool call in the assistant turn
  4. Drops user records that contain only tool results (they'd be empty bubbles)
  5. Merges all turns and sorts by timestamp

Renderers

Both take a ParsedSession and produce a string.

Markdown (src/renderers/markdown.ts): Uses <details> blocks for tool calls and thinking. Tracks byte count and splits at turn boundaries if a file would exceed 900KB (GitHub Gist limit).

HTML (src/renderers/html.ts + html-template.ts): Self-contained file with all CSS inlined. Uses the CSS light-dark() function for automatic dark/light theming — no JavaScript. Assistant markdown is converted to HTML via the marked library.

Both renderers use content-aware detection — they inspect tool names and result patterns to decide how to render each item (see Smart Content Detection above).

Gist Upload (src/gist.ts)

Dual-path with automatic fallback:

  1. gh CLI — if installed and authenticated, writes to temp files and runs gh gist create
  2. REST APIPOST https://api.github.com/gists with token from flag/env/gh-auth

Data Flow Example

Running claude-cast greedy-squishing-spring -o output.html:

1. CLI parses args
   → session="greedy-squishing-spring", format="html", output="output.html"

2. findSessionBySlug("greedy-squishing-spring")
   → Scans ~/.claude/projects/*/
   → Reads first user record of each .jsonl
   → Match found: 3ba41ea0-b987-4731-9dec-e8e1412eb264.jsonl

3. parseSession(filePath)
   → Read 159 lines → RawRecord[]
   → Filter: 159 → 34 records (dropped noise + system messages)
   → Dedup: 22 assistant records → 7 merged responses
   → Tree: 12 conversation turns (5 user + 7 assistant)
   → Meta: {slug, model: "claude-opus-4-6", turnCount: 12, ...}

4. renderHtml(session, options)
   → Header with metadata pills
   → Each turn → avatar + content card
   → Assistant text → marked.parse() → HTML
   → Tool calls → collapsible <details>
   → Wrap in htmlShell() with full inline CSS

5. Write to output.html → 42KB self-contained HTML file

License

MIT