| name | cli-printing-press-generator |
| description | Generate AI-agent-first CLIs from any API (OpenAPI, GraphQL, or browser-sniffed) with SQLite sync, compound commands, and MCP servers |
| triggers | ["generate a CLI for an API","create an MCP server for this service","build a CLI with offline search","print a CLI from OpenAPI spec","reverse engineer an API and generate CLI","create agent-native CLI with SQLite","generate compound commands for API","build CLI with local data sync"] |
CLI Printing Press Generator
Skill by ara.so — Devtools Skills collection.
CLI Printing Press generates production-ready CLIs from any API — REST, GraphQL, or browser-sniffed traffic. Each generated CLI includes SQLite-backed local storage, offline full-text search, compound insight commands, and dual interfaces (Cobra CLI + MCP server). Designed for AI agents first with typed exit codes, auto-JSON output when piped, and token-efficient --compact mode.
What It Does
The Printing Press follows a 6-phase autonomous workflow:
- Research: Discovers official docs, competing CLIs, MCP servers, and community patterns
- Non-Obvious Insight: Identifies the API's secret identity (what it's actually useful for)
- Code Generation: Builds Go CLI with domain-specific SQLite tables, FTS5 indexes, sync engine
- Verification: Runs scorecard, dogfood tests, proof-of-behavior checks, live API smoke tests
- Polish: Auto-fixes verify failures, removes dead code, cleans descriptions
- Publishing: Packages for library, generates PR with quality score
Key differentiators:
- Local-first: High-gravity resources get domain tables (not JSON blobs) with incremental sync
- Compound commands: Cross-resource queries (
stale, health, bottleneck) impossible with stateless wrappers
- Dual output: Every API generates both
<api>-pp-cli (Cobra) and <api>-pp-mcp (MCP server)
- No spec required: Point at a website, captures traffic, reverse-engineers the API
- Agent-native: Auto-JSON when piped, typed exit codes,
--compact flag, --dry-run
Installation
Prerequisites
- Go 1.26.3+ (install)
- Claude Code or compatible AI agent harness
- Git (for cloning skills repo)
1. Install Binary
go install github.com/mvanhorn/cli-printing-press/v4/cmd/printing-press@latest
Verify:
printing-press --version
2. Install Skills (Recommended Method)
Clone the repo to get skills and automatic updates via git pull:
git clone https://github.com/mvanhorn/cli-printing-press.git
cd cli-printing-press
3. Start Printing Session
From the cloned repo root:
claude --plugin-dir .
claude --plugin-dir . -w
Core Commands
Primary Generation Command
Inside Claude Code session:
/printing-press <api-name>
/printing-press <url>
/printing-press <api-name> codex
Examples:
# Generate from API name (auto-discovers docs/specs)
/printing-press Notion
# Generate from website (browser-sniff traffic)
/printing-press https://postman.com/explore
# Use Codex for code generation (60% fewer Opus tokens)
/printing-press HubSpot codex
# Reprint existing CLI under latest machine
/printing-press-reprint notion
Polish Existing CLI
Runs diagnostics, fixes verify failures, removes dead code:
/printing-press-polish <api-name>
Example:
/printing-press-polish linear
Publish to Library
Validates, packages, creates PR:
/printing-press-publish <api-name>
Example:
/printing-press-publish superhuman
Amend from Dogfood Session
Turn session friction into PR (auto-detects target CLI):
/printing-press-amend
/printing-press-amend <api-name>
Binary CLI Usage
The printing-press binary is called by skills but can be used directly:
printing-press research <api-name> --output ./output
printing-press generate notion --output ~/clis/notion-pp-cli
printing-press verify ~/clis/notion-pp-cli
printing-press scorecard ~/clis/notion-pp-cli
printing-press dogfood ~/clis/notion-pp-cli
Configuration
Output Locations
Default structure (auto-managed by skills):
~/printing-press/
├── .runstate/<scope>/runs/<run-id>/working/<api>-pp-cli/ # Active runs
├── library/<api>/ # Published CLIs
└── manuscripts/<api>/<run-id>/ # Archived runs
├── research/
├── proofs/
├── discovery/
└── pipeline/
<scope> derives from git checkout path (parallel worktrees don't conflict).
Override Output
printing-press generate stripe --output /custom/path/stripe-cli
Environment Variables
Generated CLIs use these patterns:
export NOTION_API_KEY="secret_..."
export LINEAR_API_KEY="lin_api_..."
export GITHUB_TOKEN="ghp_..."
export NOTION_PP_STORE_PATH="/custom/notion.db"
export LINEAR_PP_REFRESH_TTL="5m"
Generated CLI Patterns
Every generated CLI follows these conventions:
Authentication Setup
notion-pp-cli auth login
export NOTION_API_KEY="secret_abc123"
Data Source Modes
linear-pp-cli issues list --data-source auto
linear-pp-cli issues list --data-source live
linear-pp-cli issues list --data-source local
Sync and Search
notion-pp-cli sync
notion-pp-cli sync --incremental
notion-pp-cli search "authentication flow"
notion-pp-cli sql "SELECT title FROM pages WHERE updated_at > date('now', '-7 days')"
Agent-Native Features
linear-pp-cli issues list | jq '.[] | select(.priority == "urgent")'
linear-pp-cli issues list --compact
linear-pp-cli issues create --title "Test" --dry-run
linear-pp-cli issues get ISSUE-123
echo $?
Compound Commands
Generated CLIs include domain-specific insight commands:
linear-pp-cli stale --threshold 7d
linear-pp-cli health --team backend
linear-pp-cli bottleneck
discord-pp-cli knowledge --channel docs
discord-pp-cli stale-threads --days 30
stripe-pp-cli churn-signals
stripe-pp-cli cohort-health --month 2026-04
Code Examples
Using a Generated CLI in Go
package main
import (
"context"
"fmt"
"os"
"os/exec"
"encoding/json"
)
func getBlockedIssues() ([]Issue, error) {
cmd := exec.Command("linear-pp-cli", "stale",
"--threshold", "7d",
"--output", "json")
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("CLI error: %w", err)
}
var issues []Issue
if err := json.Unmarshal(output, &issues); err != nil {
return nil, err
}
return issues, nil
}
func callMCPServer(ctx context.Context, method string, params map[string]interface{}) error {
cmd := exec.CommandContext(ctx, "linear-pp-mcp")
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
if err := cmd.Start(); err != nil {
return err
}
req := map[string]{}{
: ,
: method,
: params,
: ,
}
json.NewEncoder(stdin).Encode(req)
resp []{}
json.NewDecoder(stdout).Decode(&resp)
cmd.Wait()
}
Integrating Generated CLI into Scripts
#!/bin/bash
notion-pp-cli sync --incremental
BLOCKERS=$(notion-pp-cli sql "
SELECT b.id, b.title, i.title as blocked_issue
FROM issues i
JOIN issues b ON i.blocker_id = b.id
WHERE b.state = 'in_progress'
AND b.updated_at < date('now', '-7 days')
" --output json)
if [ "$(echo "$BLOCKERS" | jq 'length')" -gt 0 ]; then
echo "$BLOCKERS" | jq -r '.[] | "⚠️ \(.blocked_issue) blocked by stale: \(.title)"' \
| slack-cli send --channel "#deploy-alerts"
fi
Creating Custom Commands
Generated CLIs support plugin architecture:
package custom
import (
"github.com/spf13/cobra"
"github.com/mvanhorn/linear-pp-cli/internal/store"
)
func NewReconcileCmd(st *store.Store) *cobra.Command {
cmd := &cobra.Command{
Use: "reconcile",
Short: "Find issues in API but missing from local store",
RunE: func(cmd *cobra.Command, args []string) error {
localIDs := st.GetAllIssueIDs()
liveIDs := fetchLiveIssueIDs()
missing := difference(liveIDs, localIDs)
for _, id := range missing {
fmt.Printf("Missing: %s\n", id)
}
return nil
},
}
return cmd
}
Troubleshooting
Generation Failures
Problem: Research phase hangs or fails
curl -I https://developers.notion.com
/printing-press https://raw.githubusercontent.com/notion/openapi/main/spec.yaml
/printing-press https://internal-tool.company.com
Problem: Codex mode fails repeatedly
The press auto-falls back to local generation after 3 Codex failures. Check logs:
tail -f ~/.printing-press/.runstate/<scope>/runs/<run-id>/logs/generation.log
Verification Errors
Problem: Scorecard shows low score
/printing-press-polish <api-name>
printing-press verify ~/printing-press/library/<api> --verbose
Problem: Auth tests fail
export API_KEY="your_key"
curl -H "Authorization: Bearer $API_KEY" https://api.service.com/test
cat ~/printing-press/library/<api>-pp-cli/internal/auth/auth.go
Runtime Issues with Generated CLIs
Problem: Sync fails with rate limit
api-pp-cli sync --incremental --rate-limit 10/min
api-pp-cli sql "SELECT resource, cursor, updated_at FROM sync_cursors"
Problem: Search returns no results
api-pp-cli sql "DELETE FROM pages_fts"
api-pp-cli sync --rebuild-index
api-pp-cli sql "SELECT * FROM sqlite_master WHERE type='table' AND name LIKE '%_fts'"
Problem: Compound command errors
api-pp-cli sql ".schema" | grep -A 5 "CREATE TABLE"
api-pp-cli stale --threshold 7d --log-level debug
MCP Server Issues
Problem: MCP server not responding
echo '{"jsonrpc":"2.0","method":"ping","id":1}' | linear-pp-mcp
cat ~/printing-press/library/linear-pp-mcp/mcp.json
Problem: IDE can't find MCP server
Add to Claude Code config (~/.config/claude-code/mcp-servers.json):
{
"linear": {
"command": "/path/to/linear-pp-mcp",
"env": {
"LINEAR_API_KEY": "${LINEAR_API_KEY}"
}
}
}
Performance Optimization
Problem: Large dataset sync is slow
api-pp-cli sync --batch-size 1000 --workers 4
api-pp-cli sync --resources "issues,comments" --since "2026-01-01"
Problem: Search queries are slow
api-pp-cli sql "EXPLAIN QUERY PLAN SELECT * FROM pages_fts WHERE pages_fts MATCH 'search term'"
api-pp-cli sql "CREATE INDEX idx_pages_updated ON pages(updated_at DESC)"
Best Practices
- Always run polish after generation: Auto-fixes 80% of verify failures
- Use
--compact for agent calls: 60-80% fewer tokens, same data
- Sync incrementally in production: Full sync only on init
- Set refresh TTL based on data volatility: Real-time (1m), hourly (5m), daily (1h)
- Use typed exit codes for error handling: Don't parse stderr
- Leverage compound commands: They're the CLI's superpower
- Credit sources in custom commands: Check generated README's "Sources and Inspiration"
Additional Resources