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

boxfix

v1.2.0

Published

Fix misaligned ASCII diagram borders in markdown files

Downloads

125

Readme

boxfix

LLMs generate ASCII diagrams with broken borders. This fixes them.

Before                          After
┌─────────────────────┐         ┌─────────────────────┐
│ Component A        │    →     │ Component A         │
│ with content       │          │ with content        │
└─────────────────────┘         └─────────────────────┘

The Problem

LLMs generate ASCII diagrams with misaligned right borders. The top and bottom boundary lines (┌───┐, └───┘) are usually correct because they're repetitive patterns. But content lines with variable text end up short:

┌─────────────────────────┐
│ This line is too short│   ← Right border doesn't align
│ API Gateway           │   ← Same problem here
└─────────────────────────┘

This happens because:

  • LLMs count characters inconsistently when content varies
  • Boundary lines are easy (repeat until done)
  • Content lines require precise space calculation

See the examples/ directory for more before/after examples.

Installation

npm install -g boxfix

Or use directly with npx:

npx boxfix input.md

Usage

# Output to stdout
boxfix input.md

# Fix files in place
boxfix input.md --in-place
boxfix **/*.md -i

# Check mode for CI (exit code 1 if fixes needed)
boxfix --check *.md

# JSON output for agents/tooling
boxfix input.md --json

# Preview changes without modifying
boxfix input.md --dry-run

# Multiple files
boxfix doc1.md doc2.md --in-place

Options

| Flag | Short | Description | |------|-------|-------------| | --in-place | -i | Modify files in place | | --output <file> | -o | Write to specific file (single input only) | | --check | -c | Check mode - exit 1 if fixes needed | | --json | -j | Output results as JSON | | --dry-run | -d | Preview changes without modifying | | --quiet | -q | Suppress output except errors | | --hook | | Read JSON from stdin, extract file path, fix in-place (for AI agents) |

JSON Output

{
  "files": [
    {
      "file": "input.md",
      "linesFixed": 3,
      "blocksProcessed": 2,
      "diagramsFound": 1
    }
  ],
  "summary": {
    "totalFiles": 1,
    "filesWithFixes": 1,
    "totalLinesFixed": 3,
    "totalDiagramsFound": 1
  }
}

How It Works

The Boundary-Anchored Approach

The key insight: boundary lines are reliable, content lines aren't.

┌─────────────────────┐  ← Boundary: LLMs get this right (repetitive)
│ Content here       │   ← Content: LLMs mess this up (variable)
│ More content       │   ← Content: Same problem
└─────────────────────┘  ← Boundary: LLMs get this right (repetitive)

Algorithm:

  1. Scan - Find all boundary lines (┌───┐, └───┘, +---+)
  2. Measure - Record the display width of each boundary line
  3. Match - For each content line ending with or |:
    • Find a boundary width that's 1-3 characters wider
    • This is the "target width" for that line
  4. Pad - Insert spaces before the right border character

Supported Diagram Styles

Unicode box-drawing:

┌───────┐    ╔═══════╗
│ Box 1 │    ║ Box 2 ║
└───────┘    ╚═══════╝

ASCII:

+-------+
| Box 3 |
+-------+

Nested boxes:

┌─────────────────┐
│ ┌─────────────┐ │
│ │ Inner       │ │
│ └─────────────┘ │
└─────────────────┘

What It Doesn't Touch

  • Tree structures (├── folder)
  • Lines without border characters
  • Already-aligned diagrams
  • Non-diagram code blocks
  • Code blocks with nofix or *-nofix language tag (e.g., ```nofix or ```text-nofix)

Programmatic API

import { boxfixMarkdown, boxfix } from 'boxfix';

// Process markdown with code blocks
const result = boxfixMarkdown(markdownContent);
console.log(result.fixed); // Fixed content
console.log(result.stats); // { linesFixed, blocksProcessed, diagramsFound }

// Process raw diagram content
const diagram = boxfix(diagramContent);

Exports

| Function | Description | |----------|-------------| | boxfixMarkdown(md) | Process markdown, fixing diagrams in code blocks | | boxfix(content) | Process raw content (auto-detects if diagram) | | boxfixDiagram(content) | Process content known to be a diagram | | isDiagram(content) | Check if content appears to be a diagram | | isBoundaryLine(line) | Check if line is a box boundary | | isContentLine(line) | Check if line is box content | | isTreeLine(line) | Check if line is a tree structure (excluded from processing) | | getDisplayWidth(str) | Get visual width (handles Unicode, CJK, emoji) | | expandTabs(str) | Expand tabs to spaces for consistent width calculation |

Type Exports

import type { BoxfixResult, BoxfixStats, CodeBlock } from 'boxfix';

interface BoxfixResult {
  fixed: string;          // The fixed content
  stats: BoxfixStats;     // Processing statistics
}

interface BoxfixStats {
  linesFixed: number;       // Lines that were padded
  blocksProcessed: number;  // Code blocks examined
  diagramsFound: number;    // Diagrams detected
}

interface CodeBlock {
  raw: string;              // Full block including fences
  content: string;          // Content inside fences
  language: string | null;  // Language identifier
  start: number;            // Start position in source
  end: number;              // End position in source
}

Integrations

Pre-commit Hook

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: boxfix
        name: Fix diagram borders
        entry: npx boxfix --check
        language: system
        files: '\.md$'

GitHub Actions

# .github/workflows/diagrams.yml
name: Check Diagrams
on: [push, pull_request]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npx boxfix --check **/*.md

AI Agent Hooks

The --hook flag enables seamless integration with AI coding agents. It reads JSON from stdin, extracts the file path from common field patterns, and silently processes markdown files.

Key features:

  • Reads JSON payload from stdin (as provided by agent hooks)
  • Extracts file path from common JSON structures
  • Silently skips non-markdown files
  • Always exits 0 to never break agentic workflows
  • Works with Claude Code, Cursor, Windsurf, and other agents

Supported JSON formats:

| Format | Example | Used by | |--------|---------|---------| | tool_input.file_path | {"tool_input":{"file_path":"..."}} | Claude Code | | file_path | {"file_path":"..."} | Cursor, Windsurf | | filePath | {"filePath":"..."} | Generic (camelCase) | | path | {"path":"..."} | Minimal |

Claude Code

Automatically fix diagrams as Claude writes them using hooks.

Add to .claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npx boxfix --hook"
          }
        ]
      }
    ]
  }
}

Cursor

Cursor 1.7+ supports hooks for agent lifecycle control.

Add to .cursor/hooks.json:

{
  "afterFileEdit": [
    {
      "command": "npx boxfix --hook"
    }
  ]
}

Windsurf

Windsurf (Codeium) supports Cascade Hooks for automation.

Add to .windsurf/hooks.json:

{
  "post_write_code": [
    {
      "command": "npx boxfix --hook"
    }
  ]
}

OpenCode

OpenCode supports plugins for extensibility. You can use the oh-my-opencode package which provides Claude Code hook compatibility, or create a custom plugin.

Other Agents

For any agent that pipes JSON with a file path to stdin on file edit events, the --hook flag should work out of the box. The tool checks for file paths in common locations (see table above) and silently exits 0 if no valid markdown path is found.

Why "boxfix"?

It fixes boxes. That's it.

Limitations

Current scope: boxfix pads short content lines to match boundary widths. It assumes the boundary lines are correct and adjusts content to fit.

What it doesn't do (yet):

  • Expand boundaries when content is longer than the box
  • Shrink content that overflows
  • Reflow text within boxes

If your diagram has content that overflows the boundaries, you'll need to either:

  1. Manually widen the boundary lines, or
  2. Shorten the content

Boundary expansion is planned for a future release.

Vibecoded

This entire library was built with Claude Code and Claude Opus 4.5. Every line of code, test, and documentation was generated through AI-assisted development.

License

MIT