agentsclimarketplace

Claude code stream json driver

Skill kjuhwa/skills-hub/skills/agents/claude-code-stream-json-driver

Drive the Claude Code CLI non-interactively via stream-json — build input on stdin, parse typed events on stdout, handle session resume and token usage.From its SKILL.md

Install
npx -y skills add kjuhwa/skills-hub --skill claude-code-stream-json-driver

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

4.2 KB, 916 tokens by cl100k_base, as published. Nobody here has run it

When to use

  • You're building a server or daemon that needs to run Claude Code autonomously (no TTY, no user confirmation).
  • You need streaming events (text, tool_use, tool_result) not just a final answer.
  • You want to persist session IDs so the user can resume later.

Steps

  1. Invoke Claude with the stream-json protocol flags:
    claude -p \
      --output-format stream-json \
      --input-format  stream-json \
      --verbose \
      --strict-mcp-config \
      --permission-mode bypassPermissions \
      [--model <id>] [--max-turns <n>] \
      [--append-system-prompt <text>] \
      [--resume <session_id>] \
      [--mcp-config <path-to-temp-json>]
    
  2. Write the user prompt to stdin as one JSON line, then close stdin:
    input := map[string]any{
      "type": "user",
      "message": map[string]any{
        "role": "user",
        "content": []map[string]string{{"type": "text", "text": prompt}},
      },
    }
    b, _ := json.Marshal(input)
    stdin.Write(append(b, '\n'))
    stdin.Close()
    
  3. Read stdout line-by-line with a large buffer (tool results can be huge):
    scanner := bufio.NewScanner(stdout)
    scanner.Buffer(make([]byte, 0, 1024*1024), 10*1024*1024)
    for scanner.Scan() {
      var msg claudeSDKMessage
      if err := json.Unmarshal(scanner.Bytes(), &msg); err != nil { continue }
      switch msg.Type {
      case "assistant": handleAssistantBlocks(msg) // text, thinking, tool_use
      case "user":      handleToolResultBlocks(msg)
      case "system":    sessionID = msg.SessionID
      case "result":    finalText = msg.ResultText; isError = msg.IsError
      case "log":       forwardLog(msg.Log)
      }
    }
    
  4. Accumulate token usage per model name from message.usage on assistant events:
    u := usage[content.Model]
    u.InputTokens  += content.Usage.InputTokens
    u.OutputTokens += content.Usage.OutputTokens
    u.CacheReadTokens  += content.Usage.CacheReadInputTokens
    u.CacheWriteTokens += content.Usage.CacheCreationInputTokens
    usage[content.Model] = u
    
  5. Block protocol-critical flags from any user-provided custom_args so they don't break the wire format:
    var claudeBlockedArgs = map[string]blockedArgMode{
      "-p":                blockedStandalone,
      "--output-format":   blockedWithValue,
      "--input-format":    blockedWithValue,
      "--permission-mode": blockedWithValue,
      "--mcp-config":      blockedWithValue,
    }
    
  6. Strip parent-Claude env leakage before spawning:
    func isFilteredChildEnvKey(key string) bool {
      return key == "CLAUDECODE" ||
             strings.HasPrefix(key, "CLAUDECODE_") ||
             strings.HasPrefix(key, "CLAUDE_CODE_")
    }
    
  7. On failed runs where you requested --resume <id> and Claude emitted a different session ID, signal the caller to retry with fresh session by reporting empty session ID:
    if failed && requestedResume != "" && emitted != "" && emitted != requestedResume {
      return ""
    }
    return emitted
    

Example

Minimal Go wrapper is ~200 LOC; see server/pkg/agent/claude.go in the source repo for the full reference implementation.

Caveats

  • --permission-mode bypassPermissions makes the run fully autonomous — use only in a sandboxed environment you trust.
  • If the sandbox refuses to pass MCP config by value, write the JSON to a temp file and pass --mcp-config <path>; clean up the file after the goroutine that owns the process exits.
  • 10MB max-token is a reasonable upper bound for Read/Write tool results; bump if you see "token too long" errors.
  • Don't read stderr line-by-line; pipe it to a logger that Debug-level logs lines so noise doesn't dominate.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,144. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.