Skip to content

Examples

WebSocket Relay

Spawn Claude Code on a server, relay events to browser clients over WebSocket.

typescript
import { spawn } from 'child_process'
import { createInterface } from 'readline'
import { WebSocketServer } from 'ws'
import { parseLine, Translator, createMessage } from 'claude-code-parser'

const claude = spawn('claude', [
  '-p', '--input-format', 'stream-json',
  '--output-format', 'stream-json', '--verbose',
], { cwd: '/path/to/repo', stdio: ['pipe', 'pipe', 'inherit'] })

const translator = new Translator()
const wss = new WebSocketServer({ port: 8080 })

// Claude stdout → parse → translate → broadcast to WS clients
const rl = createInterface({ input: claude.stdout! })
rl.on('line', (line) => {
  const event = parseLine(line)
  if (!event) return

  for (const ev of translator.translate(event)) {
    const msg = JSON.stringify(ev)
    wss.clients.forEach(ws => ws.send(msg))
  }
})

// WS client messages → Claude stdin
wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    const msg = JSON.parse(String(data))
    switch (msg.type) {
      case 'chat':
        claude.stdin!.write(createMessage.user(msg.content))
        break
      case 'approve':
        claude.stdin!.write(createMessage.approve(msg.toolUseId))
        break
      case 'deny':
        claude.stdin!.write(createMessage.deny(msg.toolUseId))
        break
    }
  })
})

Parse Saved Log Files

Read a captured NDJSON session log and extract structured data.

typescript
import { readFileSync } from 'fs'
import { parseLine, Translator } from 'claude-code-parser'

const log = readFileSync('session.ndjson', 'utf-8')
const translator = new Translator()

let totalCost = 0
const toolCalls: string[] = []

for (const line of log.split('\n')) {
  const event = parseLine(line)
  if (!event) continue

  for (const ev of translator.translate(event)) {
    if (ev.type === 'tool_use') {
      toolCalls.push(ev.toolName)
    }
    if (ev.type === 'turn_complete' && ev.costUsd) {
      totalCost += ev.costUsd
    }
  }
}

console.log(`Total cost: $${totalCost.toFixed(4)}`)
console.log(`Tools used: ${toolCalls.join(', ')}`)

CI Cost Tracker

Extract cost and token usage from Claude Code in a CI pipeline.

typescript
import { spawn } from 'child_process'
import { createInterface } from 'readline'
import { parseLine, Translator, createMessage } from 'claude-code-parser'

const claude = spawn('claude', [
  '-p', '--input-format', 'stream-json',
  '--output-format', 'stream-json', '--verbose',
  '--permission-mode', 'bypassPermissions',
], { stdio: ['pipe', 'pipe', 'inherit'] })

const translator = new Translator()

const rl = createInterface({ input: claude.stdout! })
rl.on('line', (line) => {
  const event = parseLine(line)
  if (!event) return

  for (const ev of translator.translate(event)) {
    if (ev.type === 'turn_complete') {
      // Post to CI — GitHub Actions output, PR comment, etc.
      console.log(`::notice::Claude cost: $${ev.costUsd?.toFixed(4)} | Tokens: ${ev.inputTokens}→${ev.outputTokens}`)
    }
    if (ev.type === 'error') {
      console.error(`::error::Claude error: ${ev.message}`)
      process.exit(1)
    }
  }
})

claude.stdin!.write(createMessage.user('Run the test suite and report results'))

Browser NDJSON Viewer

Parse NDJSON in the browser (no Node.js APIs used in core).

typescript
import { parseLine, Translator } from 'claude-code-parser'

const translator = new Translator()

// Fetch a saved session log
const response = await fetch('/logs/session-2026-03-22.ndjson')
const text = await response.text()

for (const line of text.split('\n')) {
  const event = parseLine(line)
  if (!event) continue

  for (const ev of translator.translate(event)) {
    renderEvent(ev) // Your UI rendering function
  }
}

Tool Approval Flow

Interactive tool approval via stdin messages.

typescript
import { parseLine, Translator, createMessage } from 'claude-code-parser'
import type { ToolUseEvent } from 'claude-code-parser'

// ... setup claude process and readline ...

const pendingApprovals = new Map<string, ToolUseEvent>()

rl.on('line', (line) => {
  const event = parseLine(line)
  if (!event) return

  for (const ev of translator.translate(event)) {
    if (ev.type === 'tool_use') {
      // Auto-approve Read/Grep, require manual approval for others
      if (['Read', 'Grep', 'Glob'].includes(ev.toolName)) {
        claude.stdin!.write(createMessage.approve(ev.toolUseId))
      } else {
        pendingApprovals.set(ev.toolUseId, ev)
        console.log(`Approve ${ev.toolName}? (tool_use_id: ${ev.toolUseId})`)
      }
    }
  }
})

// Later, when user approves/denies:
function handleUserDecision(toolUseId: string, approved: boolean) {
  if (approved) {
    claude.stdin!.write(createMessage.approve(toolUseId))
  } else {
    claude.stdin!.write(createMessage.deny(toolUseId))
  }
  pendingApprovals.delete(toolUseId)
}

Not affiliated with or endorsed by Anthropic.