claude_stream_viewer 1.0.15 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +54 -17
  2. package/data/claude_events/basic.claude_events.jsonl +99 -0
  3. package/data/claude_events/video_build_in_public.claude_events.jsonl +4224 -0
  4. package/data/claude_events/video_generation.claude_events.jsonl +196 -0
  5. package/demo_vhs/claude_stream_viewer.compressed.gif +0 -0
  6. package/demo_vhs/claude_stream_viewer.gif +0 -0
  7. package/demo_vhs/demo.tape +29 -0
  8. package/demo_vhs/fixture_demo.jsonl +6 -0
  9. package/dist/cli.d.ts +3 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +94 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/libs/colors.d.ts +15 -0
  14. package/dist/libs/colors.d.ts.map +1 -0
  15. package/dist/libs/colors.js +16 -0
  16. package/dist/libs/colors.js.map +1 -0
  17. package/dist/libs/terminal_output.d.ts +8 -0
  18. package/dist/libs/terminal_output.d.ts.map +1 -0
  19. package/dist/libs/terminal_output.js +13 -0
  20. package/dist/libs/terminal_output.js.map +1 -0
  21. package/dist/log_viewer/log_renderer.d.ts +28 -0
  22. package/dist/log_viewer/log_renderer.d.ts.map +1 -0
  23. package/dist/log_viewer/log_renderer.js +163 -0
  24. package/dist/log_viewer/log_renderer.js.map +1 -0
  25. package/dist/log_viewer/tool_input_formatter.d.ts +31 -0
  26. package/dist/log_viewer/tool_input_formatter.d.ts.map +1 -0
  27. package/dist/log_viewer/tool_input_formatter.js +190 -0
  28. package/dist/log_viewer/tool_input_formatter.js.map +1 -0
  29. package/dist/stats/stats_collector.d.ts +28 -0
  30. package/dist/stats/stats_collector.d.ts.map +1 -0
  31. package/dist/stats/stats_collector.js +165 -0
  32. package/dist/stats/stats_collector.js.map +1 -0
  33. package/dist/stats/stats_renderer.d.ts +13 -0
  34. package/dist/stats/stats_renderer.d.ts.map +1 -0
  35. package/dist/stats/stats_renderer.js +193 -0
  36. package/dist/stats/stats_renderer.js.map +1 -0
  37. package/dist/types/claude_event.d.ts +75 -0
  38. package/dist/types/claude_event.d.ts.map +1 -0
  39. package/dist/types/claude_event.js +2 -0
  40. package/dist/types/claude_event.js.map +1 -0
  41. package/dist/types/stats_report.d.ts +68 -0
  42. package/dist/types/stats_report.d.ts.map +1 -0
  43. package/dist/types/stats_report.js +2 -0
  44. package/dist/types/stats_report.js.map +1 -0
  45. package/package.json +13 -9
  46. package/src/cli.ts +112 -0
  47. package/src/libs/colors.ts +16 -0
  48. package/src/libs/terminal_output.ts +14 -0
  49. package/src/log_viewer/log_renderer.ts +160 -0
  50. package/src/log_viewer/tool_input_formatter.ts +183 -0
  51. package/src/stats/stats_collector.ts +175 -0
  52. package/src/stats/stats_renderer.ts +185 -0
  53. package/src/types/claude_event.ts +74 -0
  54. package/src/types/stats_report.ts +66 -0
  55. package/dist/claude-stream-viewer.d.ts +0 -3
  56. package/dist/claude-stream-viewer.d.ts.map +0 -1
  57. package/dist/claude-stream-viewer.js +0 -100
  58. package/dist/claude-stream-viewer.js.map +0 -1
  59. package/dist/claude_stream_viewer.d.ts +0 -3
  60. package/dist/claude_stream_viewer.d.ts.map +0 -1
  61. package/dist/claude_stream_viewer.js +0 -155
  62. package/dist/claude_stream_viewer.js.map +0 -1
  63. package/src/claude_stream_viewer.ts +0 -208
@@ -0,0 +1,185 @@
1
+ import { colors } from '../libs/colors.js';
2
+ import { TerminalOutput } from '../libs/terminal_output.js';
3
+ import type { StatsFormat, StatsReport } from '../types/stats_report.js';
4
+
5
+ /** Renders a {@link StatsReport} as colorized text, JSON, or Markdown. */
6
+ export class StatsRenderer {
7
+ /**
8
+ * Renders the report in the requested format and returns it as a single
9
+ * string ready to print. Text output is colorized; JSON and Markdown are not.
10
+ */
11
+ static render(report: StatsReport, format: StatsFormat): string {
12
+ if (format === 'json') return JSON.stringify(report, null, 2);
13
+ if (format === 'markdown') return StatsRenderer.formatMarkdown(report);
14
+ return StatsRenderer.formatText(report);
15
+ }
16
+
17
+ private static formatText(report: StatsReport): string {
18
+ const lines: string[] = [];
19
+ const r = report.result;
20
+ lines.push(TerminalOutput.header('RESULT'));
21
+ if (report.truncated === true) lines.push(colors.system('(no result event — stream truncated)'));
22
+ if (r.terminalReason !== undefined) lines.push(colors.system(`terminal_reason: ${r.terminalReason}`));
23
+ if (r.stopReason !== undefined) lines.push(colors.system(`stop_reason: ${r.stopReason}`));
24
+ if (r.isError === true) lines.push(colors.error('is_error: true'));
25
+ if (r.permissionDenials > 0) lines.push(colors.system(`permission_denials: ${r.permissionDenials}`));
26
+ if (r.numTurns !== undefined) lines.push(colors.system(`turns: ${r.numTurns}`));
27
+ if (r.durationMs !== undefined) lines.push(colors.system(`duration: ${(r.durationMs / 1000).toFixed(2)}s`));
28
+ if (r.totalCostUsd !== undefined) lines.push(colors.system(`cost: $${r.totalCostUsd.toFixed(4)}`));
29
+
30
+ const latency = report.latency;
31
+ if (latency !== undefined) {
32
+ lines.push(TerminalOutput.header('LATENCY'));
33
+ lines.push(colors.system(`wall: ${(latency.wallMs / 1000).toFixed(2)}s`));
34
+ if (latency.apiMs !== undefined) lines.push(colors.system(`api: ${(latency.apiMs / 1000).toFixed(2)}s`));
35
+ if (latency.localMs !== undefined) lines.push(colors.system(`local: ${(latency.localMs / 1000).toFixed(2)}s (tools + overhead)`));
36
+ if (latency.throughputTokPerS !== undefined) lines.push(colors.system(`throughput: ${latency.throughputTokPerS} output tok/s (api time)`));
37
+ }
38
+
39
+ const tokens = report.tokens;
40
+ if (tokens !== undefined) {
41
+ lines.push(TerminalOutput.header('TOKENS'));
42
+ lines.push(colors.system(`input: ${StatsRenderer.formatCount(tokens.input)}`));
43
+ lines.push(colors.system(`output: ${StatsRenderer.formatCount(tokens.output)}`));
44
+ lines.push(colors.system(`cache read: ${StatsRenderer.formatCount(tokens.cacheRead)}`));
45
+ lines.push(colors.system(`cache create: ${StatsRenderer.formatCount(tokens.cacheCreate)}`));
46
+ lines.push(colors.system(`total: ${StatsRenderer.formatCount(tokens.total)}`));
47
+ lines.push(colors.system(`cache hit: ${tokens.cacheHitPct.toFixed(1)}% of prompt tokens`));
48
+ if (tokens.byModel.length > 0) {
49
+ lines.push(colors.system('\nby model:'));
50
+ for (const model of tokens.byModel) {
51
+ const costSuffix = model.costUsd !== undefined ? ` $${model.costUsd.toFixed(4)}` : '';
52
+ lines.push(colors.system(` ${model.model}: in=${StatsRenderer.formatCount(model.input)} out=${StatsRenderer.formatCount(model.output)} cache_read=${StatsRenderer.formatCount(model.cacheRead)} cache_create=${StatsRenderer.formatCount(model.cacheCreate)}${costSuffix}`));
53
+ }
54
+ }
55
+ }
56
+
57
+ const context = report.context;
58
+ if (context.peakPromptTokens > 0) {
59
+ lines.push(TerminalOutput.header('CONTEXT'));
60
+ lines.push(colors.system(`peak prompt: ${StatsRenderer.formatCount(context.peakPromptTokens)} tokens`));
61
+ if (context.contextWindow !== undefined && context.peakPct !== undefined) {
62
+ lines.push(colors.system(`window: ${StatsRenderer.formatCount(context.contextWindow)} (${context.peakPct.toFixed(1)}% used at peak)`));
63
+ }
64
+ }
65
+
66
+ const tools = report.tools;
67
+ if (tools.byTool.length > 0) {
68
+ lines.push(TerminalOutput.header('TOOLS'));
69
+ lines.push(colors.system(`calls: ${tools.totalCalls}`));
70
+ for (const entry of tools.byTool) {
71
+ lines.push(colors.system(` ${entry.name}: ${entry.count}`));
72
+ }
73
+ if (tools.resultCount > 0) {
74
+ const line = `errors: ${tools.errorCount}/${tools.resultCount} (${tools.errorPct.toFixed(1)}%)`;
75
+ lines.push(tools.errorCount > 0 ? colors.error(line) : colors.system(line));
76
+ }
77
+ }
78
+
79
+ return lines.join('\n');
80
+ }
81
+
82
+ private static formatMarkdown(report: StatsReport): string {
83
+ const lines: string[] = [];
84
+ const r = report.result;
85
+ lines.push('## Stream stats');
86
+ lines.push('');
87
+ const summary: string[] = [];
88
+ if (r.terminalReason !== undefined) summary.push(r.terminalReason);
89
+ if (r.stopReason !== undefined) summary.push(r.stopReason);
90
+ if (r.numTurns !== undefined) summary.push(`${r.numTurns} turns`);
91
+ if (r.durationMs !== undefined) summary.push(`${(r.durationMs / 1000).toFixed(2)}s`);
92
+ if (r.totalCostUsd !== undefined) summary.push(`$${r.totalCostUsd.toFixed(4)}`);
93
+ if (summary.length > 0) {
94
+ lines.push(`**Result:** ${summary.join(' · ')}`);
95
+ lines.push('');
96
+ }
97
+ if (report.truncated === true) {
98
+ lines.push('_No result event — stream truncated._');
99
+ lines.push('');
100
+ }
101
+ if (r.isError === true) {
102
+ lines.push('> ⚠️ **is_error: true**');
103
+ lines.push('');
104
+ }
105
+ if (r.permissionDenials > 0) {
106
+ lines.push(`> permission denials: ${r.permissionDenials}`);
107
+ lines.push('');
108
+ }
109
+
110
+ const latency = report.latency;
111
+ if (latency !== undefined) {
112
+ lines.push('### Latency');
113
+ lines.push('');
114
+ lines.push('| metric | value |');
115
+ lines.push('|---|---|');
116
+ lines.push(`| wall | ${(latency.wallMs / 1000).toFixed(2)}s |`);
117
+ if (latency.apiMs !== undefined) lines.push(`| api | ${(latency.apiMs / 1000).toFixed(2)}s |`);
118
+ if (latency.localMs !== undefined) lines.push(`| local (tools + overhead) | ${(latency.localMs / 1000).toFixed(2)}s |`);
119
+ if (latency.throughputTokPerS !== undefined) lines.push(`| throughput | ${latency.throughputTokPerS} output tok/s |`);
120
+ lines.push('');
121
+ }
122
+
123
+ const tokens = report.tokens;
124
+ if (tokens !== undefined) {
125
+ lines.push('### Tokens');
126
+ lines.push('');
127
+ lines.push('| type | count |');
128
+ lines.push('|---|--:|');
129
+ lines.push(`| input | ${StatsRenderer.formatCount(tokens.input)} |`);
130
+ lines.push(`| output | ${StatsRenderer.formatCount(tokens.output)} |`);
131
+ lines.push(`| cache read | ${StatsRenderer.formatCount(tokens.cacheRead)} |`);
132
+ lines.push(`| cache create | ${StatsRenderer.formatCount(tokens.cacheCreate)} |`);
133
+ lines.push(`| **total** | **${StatsRenderer.formatCount(tokens.total)}** |`);
134
+ lines.push(`| cache hit | ${tokens.cacheHitPct.toFixed(1)}% |`);
135
+ lines.push('');
136
+ if (tokens.byModel.length > 0) {
137
+ lines.push('**By model:**');
138
+ lines.push('');
139
+ lines.push('| model | in | out | cache read | cache create | cost |');
140
+ lines.push('|---|--:|--:|--:|--:|--:|');
141
+ for (const model of tokens.byModel) {
142
+ const cost = model.costUsd !== undefined ? `$${model.costUsd.toFixed(4)}` : '';
143
+ lines.push(`| ${model.model} | ${StatsRenderer.formatCount(model.input)} | ${StatsRenderer.formatCount(model.output)} | ${StatsRenderer.formatCount(model.cacheRead)} | ${StatsRenderer.formatCount(model.cacheCreate)} | ${cost} |`);
144
+ }
145
+ lines.push('');
146
+ }
147
+ }
148
+
149
+ const context = report.context;
150
+ if (context.peakPromptTokens > 0) {
151
+ lines.push('### Context');
152
+ lines.push('');
153
+ if (context.contextWindow !== undefined && context.peakPct !== undefined) {
154
+ lines.push(`Peak prompt **${StatsRenderer.formatCount(context.peakPromptTokens)}** tokens — ${context.peakPct.toFixed(1)}% of ${StatsRenderer.formatCount(context.contextWindow)} window.`);
155
+ } else {
156
+ lines.push(`Peak prompt **${StatsRenderer.formatCount(context.peakPromptTokens)}** tokens.`);
157
+ }
158
+ lines.push('');
159
+ }
160
+
161
+ const tools = report.tools;
162
+ if (tools.byTool.length > 0) {
163
+ lines.push('### Tools');
164
+ lines.push('');
165
+ lines.push(`Total calls: **${tools.totalCalls}**`);
166
+ lines.push('');
167
+ lines.push('| tool | calls |');
168
+ lines.push('|---|--:|');
169
+ for (const entry of tools.byTool) {
170
+ lines.push(`| ${entry.name} | ${entry.count} |`);
171
+ }
172
+ lines.push('');
173
+ if (tools.resultCount > 0) {
174
+ lines.push(`Errors: ${tools.errorCount}/${tools.resultCount} (${tools.errorPct.toFixed(1)}%)`);
175
+ lines.push('');
176
+ }
177
+ }
178
+
179
+ return lines.join('\n').trimEnd();
180
+ }
181
+
182
+ private static formatCount(value: number): string {
183
+ return value.toLocaleString('en-US');
184
+ }
185
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * A single content block inside an `assistant` or `user` message.
3
+ *
4
+ * The same shape covers every block kind (`text`, `thinking`, `tool_use`,
5
+ * `tool_result`); only the fields relevant to a given `type` are populated.
6
+ */
7
+ export type ContentBlock = {
8
+ type?: string;
9
+ text?: string;
10
+ thinking?: string;
11
+ id?: string;
12
+ name?: string;
13
+ input?: unknown;
14
+ tool_use_id?: string;
15
+ content?: string | Array<{ type?: string; text?: string }>;
16
+ is_error?: boolean;
17
+ };
18
+
19
+ /** Raw, untyped input object of a `tool_use` block, keyed by parameter name. */
20
+ export type ToolInput = Record<string, unknown>;
21
+
22
+ /** Per-model usage aggregate, as found in the result event's `modelUsage` map. */
23
+ export type ModelUsage = {
24
+ inputTokens?: number;
25
+ outputTokens?: number;
26
+ cacheReadInputTokens?: number;
27
+ cacheCreationInputTokens?: number;
28
+ costUSD?: number;
29
+ contextWindow?: number;
30
+ };
31
+
32
+ /**
33
+ * A consolidated Claude Code stream-json event (`system`, `assistant`, `user`,
34
+ * `rate_limit_event`, `result`, …).
35
+ *
36
+ * Partial typing on purpose: only the fields the viewer reads are listed.
37
+ * Anything unrecognized falls through to the unknown-event branch.
38
+ */
39
+ export type ClaudeEvent = {
40
+ type?: string;
41
+ subtype?: string;
42
+ cwd?: string;
43
+ model?: string;
44
+ tools?: string[];
45
+ session_id?: string;
46
+ claude_code_version?: string;
47
+ message?: {
48
+ content?: ContentBlock[];
49
+ usage?: {
50
+ input_tokens?: number;
51
+ cache_read_input_tokens?: number;
52
+ cache_creation_input_tokens?: number;
53
+ };
54
+ };
55
+ rate_limit_info?: {
56
+ status?: string;
57
+ };
58
+ result?: string;
59
+ total_cost_usd?: number;
60
+ duration_ms?: number;
61
+ duration_api_ms?: number;
62
+ num_turns?: number;
63
+ is_error?: boolean;
64
+ stop_reason?: string;
65
+ permission_denials?: unknown[];
66
+ terminal_reason?: string;
67
+ usage?: {
68
+ input_tokens?: number;
69
+ output_tokens?: number;
70
+ cache_read_input_tokens?: number;
71
+ cache_creation_input_tokens?: number;
72
+ };
73
+ modelUsage?: Record<string, ModelUsage>;
74
+ };
@@ -0,0 +1,66 @@
1
+ /** Output format selected on the command line for the stats summary. */
2
+ export type StatsFormat = 'text' | 'json' | 'markdown';
3
+
4
+ /** Token counts and cost attributed to a single model within a session. */
5
+ export type ModelTokens = {
6
+ model: string;
7
+ input: number;
8
+ output: number;
9
+ cacheRead: number;
10
+ cacheCreate: number;
11
+ costUsd?: number;
12
+ };
13
+
14
+ /**
15
+ * Structured end-of-stream statistics assembled by {@link StatsCollector} from
16
+ * the `result` event and the accumulators tracked across the stream.
17
+ *
18
+ * `latency` and `tokens` are absent when the stream had no `result` event
19
+ * (see `truncated`); `context` and `tools` are always present because they are
20
+ * derived from per-event accumulators.
21
+ */
22
+ export type StatsReport = {
23
+ /** True when no `result` event was seen (the stream was cut short). */
24
+ truncated: boolean;
25
+ result: {
26
+ terminalReason?: string;
27
+ stopReason?: string;
28
+ isError: boolean;
29
+ numTurns?: number;
30
+ permissionDenials: number;
31
+ durationMs?: number;
32
+ totalCostUsd?: number;
33
+ };
34
+ /** Present only when the result event carried timing. */
35
+ latency?: {
36
+ wallMs: number;
37
+ apiMs?: number;
38
+ /** Wall time minus API time — local tool execution and overhead. */
39
+ localMs?: number;
40
+ throughputTokPerS?: number;
41
+ };
42
+ /** Present only when the result event carried usage. */
43
+ tokens?: {
44
+ input: number;
45
+ output: number;
46
+ cacheRead: number;
47
+ cacheCreate: number;
48
+ total: number;
49
+ /** Cache reads as a percentage of all prompt tokens. */
50
+ cacheHitPct: number;
51
+ byModel: ModelTokens[];
52
+ };
53
+ context: {
54
+ /** Largest single-message prompt (input + cache) seen in the stream. */
55
+ peakPromptTokens: number;
56
+ contextWindow?: number;
57
+ peakPct?: number;
58
+ };
59
+ tools: {
60
+ totalCalls: number;
61
+ byTool: Array<{ name: string; count: number }>;
62
+ resultCount: number;
63
+ errorCount: number;
64
+ errorPct: number;
65
+ };
66
+ };
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
3
- //# sourceMappingURL=claude-stream-viewer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"claude-stream-viewer.d.ts","sourceRoot":"","sources":["../src/claude-stream-viewer.ts"],"names":[],"mappings":""}
@@ -1,100 +0,0 @@
1
- #!/usr/bin/env node
2
- // Reads Claude API stream-json events line-by-line from stdin and pretty-prints
3
- // them with colorized output. Designed to wrap an upstream `claude --stream-json`
4
- // pipe, e.g. `claude … | claude_stream_viewer`.
5
- import readline from 'node:readline';
6
- import chalk from 'chalk';
7
- const colors = {
8
- text: chalk.white,
9
- tool: chalk.cyan,
10
- system: chalk.gray,
11
- error: chalk.red,
12
- json: chalk.dim,
13
- header: chalk.yellow.bold,
14
- };
15
- function printText(text) {
16
- process.stdout.write(colors.text(text));
17
- }
18
- function printNewline() {
19
- process.stdout.write('\n');
20
- }
21
- function printHeader(label) {
22
- printNewline();
23
- console.log(colors.header(`\n=== ${label} ===`));
24
- }
25
- function printJSON(obj) {
26
- console.log(colors.json(JSON.stringify(obj, null, 2)));
27
- }
28
- function handleEvent(data) {
29
- try {
30
- // Newer SDKs wrap each event in a `stream_event` envelope.
31
- if (data.type === 'stream_event' && data.event) {
32
- const evt = data.event;
33
- if (evt.delta?.type === 'text_delta' && evt.delta.text) {
34
- printText(evt.delta.text);
35
- return;
36
- }
37
- if (evt.type === 'tool_use') {
38
- printHeader('TOOL USE');
39
- printJSON(evt);
40
- return;
41
- }
42
- // `content_block_delta` is the parent envelope of `text_delta` —
43
- // already rendered above, so skip it here to avoid duplicating
44
- // the text stream as a JSON dump.
45
- if (evt.type && evt.type !== 'content_block_delta') {
46
- printHeader(`EVENT: ${evt.type}`);
47
- printJSON(evt);
48
- return;
49
- }
50
- }
51
- // Legacy format: pre-`stream_event` SDKs emit raw `message_delta`
52
- // events with the text delta hanging off the root.
53
- if (data.type === 'message_delta') {
54
- const text = data.delta?.text;
55
- if (text) {
56
- printText(text);
57
- return;
58
- }
59
- }
60
- // Final assistant message bundling all content blocks for the turn.
61
- if (data.message?.content) {
62
- printHeader('FINAL MESSAGE');
63
- for (const block of data.message.content) {
64
- if (block.text) {
65
- console.log(colors.text(block.text));
66
- }
67
- }
68
- return;
69
- }
70
- // Unrecognized shape — dump it raw so the user can extend ClaudeEvent.
71
- printHeader('UNKNOWN EVENT');
72
- printJSON(data);
73
- }
74
- catch (err) {
75
- // Never let a single malformed event take down the stream — log and move on.
76
- console.error(colors.error('Error processing event:'), err);
77
- printJSON(data);
78
- }
79
- }
80
- const rl = readline.createInterface({
81
- input: process.stdin,
82
- crlfDelay: Infinity,
83
- });
84
- rl.on('line', (line) => {
85
- // stream-json pipes occasionally pad with blank lines between events.
86
- if (line.trim() === '')
87
- return;
88
- try {
89
- const parsed = JSON.parse(line);
90
- handleEvent(parsed);
91
- }
92
- catch (err) {
93
- console.error(colors.error('Invalid JSON:'), line);
94
- }
95
- });
96
- rl.on('close', () => {
97
- printNewline();
98
- console.log(colors.system('\n[stream ended]'));
99
- });
100
- //# sourceMappingURL=claude-stream-viewer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"claude-stream-viewer.js","sourceRoot":"","sources":["../src/claude-stream-viewer.ts"],"names":[],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
3
- //# sourceMappingURL=claude_stream_viewer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"claude_stream_viewer.d.ts","sourceRoot":"","sources":["../src/claude_stream_viewer.ts"],"names":[],"mappings":""}
@@ -1,155 +0,0 @@
1
- #!/usr/bin/env node
2
- // Reads Claude API stream-json events line-by-line from stdin and pretty-prints
3
- // them with colorized output. Designed to wrap an upstream `claude --stream-json`
4
- // pipe, e.g. `claude … | npx claude_stream_viewer`.
5
- // Usage:
6
- // claude --stream-json | npx claude_stream_viewer
7
- //
8
- // claude --output-format stream-json --verbose --include-partial-messages --permission-mode auto -p "explain quantum computing like I'm 5" | npx claude_stream_viewer
9
- import Readline from 'node:readline';
10
- import Fs from 'node:fs';
11
- import * as Commander from 'commander';
12
- import Chalk from 'chalk';
13
- import Path from 'node:path';
14
- const __dirname = new URL('.', import.meta.url).pathname;
15
- const colors = {
16
- text: Chalk.white,
17
- tool: Chalk.cyan,
18
- system: Chalk.gray,
19
- error: Chalk.red,
20
- json: Chalk.dim,
21
- header: Chalk.yellow.bold,
22
- };
23
- ///////////////////////////////////////////////////////////////////////////////
24
- ///////////////////////////////////////////////////////////////////////////////
25
- // class MainHelper
26
- ///////////////////////////////////////////////////////////////////////////////
27
- ///////////////////////////////////////////////////////////////////////////////
28
- class MainHelper {
29
- static printText(text) {
30
- process.stdout.write(colors.text(text));
31
- }
32
- static printNewline() {
33
- process.stdout.write('\n');
34
- }
35
- static printHeader(label) {
36
- MainHelper.printNewline();
37
- console.log(colors.header(`\n=== ${label} ===`));
38
- }
39
- static printJSON(obj) {
40
- console.log(colors.json(JSON.stringify(obj, null, 2)));
41
- }
42
- static handleEvent(data) {
43
- try {
44
- // Newer SDKs wrap each event in a `stream_event` envelope.
45
- if (data.type === 'stream_event' && data.event) {
46
- const evt = data.event;
47
- if (evt.delta?.type === 'text_delta' && evt.delta.text) {
48
- MainHelper.printText(evt.delta.text);
49
- return;
50
- }
51
- if (evt.type === 'tool_use') {
52
- MainHelper.printHeader('TOOL USE');
53
- MainHelper.printJSON(evt);
54
- return;
55
- }
56
- // `content_block_delta` is the parent envelope of `text_delta` —
57
- // already rendered above, so skip it here to avoid duplicating
58
- // the text stream as a JSON dump.
59
- if (evt.type && evt.type !== 'content_block_delta') {
60
- MainHelper.printHeader(`EVENT: ${evt.type}`);
61
- MainHelper.printJSON(evt);
62
- return;
63
- }
64
- }
65
- // Legacy format: pre-`stream_event` SDKs emit raw `message_delta`
66
- // events with the text delta hanging off the root.
67
- if (data.type === 'message_delta') {
68
- const text = data.delta?.text;
69
- if (text) {
70
- MainHelper.printText(text);
71
- return;
72
- }
73
- }
74
- // Final assistant message bundling all content blocks for the turn.
75
- if (data.message?.content) {
76
- MainHelper.printHeader('FINAL MESSAGE');
77
- for (const block of data.message.content) {
78
- if (block.text) {
79
- console.log(colors.text(block.text));
80
- }
81
- }
82
- return;
83
- }
84
- // Unrecognized shape — dump it raw so the user can extend ClaudeEvent.
85
- MainHelper.printHeader('UNKNOWN EVENT');
86
- MainHelper.printJSON(data);
87
- }
88
- catch (err) {
89
- // Never let a single malformed event take down the stream — log and move on.
90
- console.error(colors.error('Error processing event:'), err);
91
- MainHelper.printJSON(data);
92
- }
93
- }
94
- }
95
- ///////////////////////////////////////////////////////////////////////////////
96
- ///////////////////////////////////////////////////////////////////////////////
97
- // function main
98
- ///////////////////////////////////////////////////////////////////////////////
99
- ///////////////////////////////////////////////////////////////////////////////
100
- async function main() {
101
- const packageJsonPath = Path.join(__dirname, '..', 'package.json');
102
- const packageJson = JSON.parse(await Fs.promises.readFile(packageJsonPath, 'utf-8'));
103
- const packageVersion = packageJson.version ?? 'unknown';
104
- ///////////////////////////////////////////////////////////////////////////////
105
- ///////////////////////////////////////////////////////////////////////////////
106
- //
107
- ///////////////////////////////////////////////////////////////////////////////
108
- ///////////////////////////////////////////////////////////////////////////////
109
- const program = new Commander.Command();
110
- program
111
- .name('claude_stream_viewer')
112
- .description('Pretty-prints Claude API stream-json events from stdin with colorized output.')
113
- .version(packageVersion)
114
- .option('--no-color', 'disable colored output')
115
- .parse(process.argv);
116
- const options = program.opts();
117
- ///////////////////////////////////////////////////////////////////////////////
118
- ///////////////////////////////////////////////////////////////////////////////
119
- //
120
- ///////////////////////////////////////////////////////////////////////////////
121
- ///////////////////////////////////////////////////////////////////////////////
122
- if (options.color === false) {
123
- Chalk.level = 0;
124
- }
125
- const readline = Readline.createInterface({
126
- input: process.stdin,
127
- crlfDelay: Infinity,
128
- });
129
- readline.on('line', (line) => {
130
- // stream-json pipes occasionally pad with blank lines between events.
131
- if (line.trim() === '')
132
- return;
133
- try {
134
- const parsed = JSON.parse(line);
135
- MainHelper.handleEvent(parsed);
136
- }
137
- catch (err) {
138
- console.error(colors.error('Invalid JSON:'), line);
139
- }
140
- });
141
- await new Promise((resolve) => {
142
- readline.on('close', () => {
143
- MainHelper.printNewline();
144
- console.log(colors.system('\n[stream ended]'));
145
- resolve();
146
- });
147
- });
148
- }
149
- ///////////////////////////////////////////////////////////////////////////////
150
- ///////////////////////////////////////////////////////////////////////////////
151
- //
152
- ///////////////////////////////////////////////////////////////////////////////
153
- ///////////////////////////////////////////////////////////////////////////////
154
- await main();
155
- //# sourceMappingURL=claude_stream_viewer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"claude_stream_viewer.js","sourceRoot":"","sources":["../src/claude_stream_viewer.ts"],"names":[],"mappings":"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"}