claude_stream_viewer 1.0.9 → 1.0.11

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.
@@ -2,99 +2,150 @@
2
2
  // Reads Claude API stream-json events line-by-line from stdin and pretty-prints
3
3
  // them with colorized output. Designed to wrap an upstream `claude --stream-json`
4
4
  // pipe, e.g. `claude … | claude_stream_viewer`.
5
- import readline from 'node:readline';
6
- import chalk from 'chalk';
5
+ import Readline from 'node:readline';
6
+ import Fs from 'node:fs';
7
+ import * as Commander from 'commander';
8
+ import Chalk from 'chalk';
9
+ import Path from 'node:path';
10
+ const __dirname = new URL('.', import.meta.url).pathname;
7
11
  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,
12
+ text: Chalk.white,
13
+ tool: Chalk.cyan,
14
+ system: Chalk.gray,
15
+ error: Chalk.red,
16
+ json: Chalk.dim,
17
+ header: Chalk.yellow.bold,
14
18
  };
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;
19
+ ///////////////////////////////////////////////////////////////////////////////
20
+ ///////////////////////////////////////////////////////////////////////////////
21
+ // class MainHelper
22
+ ///////////////////////////////////////////////////////////////////////////////
23
+ ///////////////////////////////////////////////////////////////////////////////
24
+ class MainHelper {
25
+ static printText(text) {
26
+ process.stdout.write(colors.text(text));
27
+ }
28
+ static printNewline() {
29
+ process.stdout.write('\n');
30
+ }
31
+ static printHeader(label) {
32
+ MainHelper.printNewline();
33
+ console.log(colors.header(`\n=== ${label} ===`));
34
+ }
35
+ static printJSON(obj) {
36
+ console.log(colors.json(JSON.stringify(obj, null, 2)));
37
+ }
38
+ static handleEvent(data) {
39
+ try {
40
+ // Newer SDKs wrap each event in a `stream_event` envelope.
41
+ if (data.type === 'stream_event' && data.event) {
42
+ const evt = data.event;
43
+ if (evt.delta?.type === 'text_delta' && evt.delta.text) {
44
+ MainHelper.printText(evt.delta.text);
45
+ return;
46
+ }
47
+ if (evt.type === 'tool_use') {
48
+ MainHelper.printHeader('TOOL USE');
49
+ MainHelper.printJSON(evt);
50
+ return;
51
+ }
52
+ // `content_block_delta` is the parent envelope of `text_delta` —
53
+ // already rendered above, so skip it here to avoid duplicating
54
+ // the text stream as a JSON dump.
55
+ if (evt.type && evt.type !== 'content_block_delta') {
56
+ MainHelper.printHeader(`EVENT: ${evt.type}`);
57
+ MainHelper.printJSON(evt);
58
+ return;
59
+ }
41
60
  }
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;
61
+ // Legacy format: pre-`stream_event` SDKs emit raw `message_delta`
62
+ // events with the text delta hanging off the root.
63
+ if (data.type === 'message_delta') {
64
+ const text = data.delta?.text;
65
+ if (text) {
66
+ MainHelper.printText(text);
67
+ return;
68
+ }
49
69
  }
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);
70
+ // Final assistant message bundling all content blocks for the turn.
71
+ if (data.message?.content) {
72
+ MainHelper.printHeader('FINAL MESSAGE');
73
+ for (const block of data.message.content) {
74
+ if (block.text) {
75
+ console.log(colors.text(block.text));
76
+ }
77
+ }
57
78
  return;
58
79
  }
80
+ // Unrecognized shape — dump it raw so the user can extend ClaudeEvent.
81
+ MainHelper.printHeader('UNKNOWN EVENT');
82
+ MainHelper.printJSON(data);
59
83
  }
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;
84
+ catch (err) {
85
+ // Never let a single malformed event take down the stream — log and move on.
86
+ console.error(colors.error('Error processing event:'), err);
87
+ MainHelper.printJSON(data);
69
88
  }
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
89
  }
79
90
  }
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);
91
+ ///////////////////////////////////////////////////////////////////////////////
92
+ ///////////////////////////////////////////////////////////////////////////////
93
+ // function main
94
+ ///////////////////////////////////////////////////////////////////////////////
95
+ ///////////////////////////////////////////////////////////////////////////////
96
+ async function main() {
97
+ const packageJsonPath = Path.join(__dirname, '..', 'package.json');
98
+ const packageJson = JSON.parse(await Fs.promises.readFile(packageJsonPath, 'utf-8'));
99
+ const packageVersion = packageJson.version ?? 'unknown';
100
+ ///////////////////////////////////////////////////////////////////////////////
101
+ ///////////////////////////////////////////////////////////////////////////////
102
+ //
103
+ ///////////////////////////////////////////////////////////////////////////////
104
+ ///////////////////////////////////////////////////////////////////////////////
105
+ const program = new Commander.Command();
106
+ program
107
+ .name('claude_stream_viewer')
108
+ .description('Pretty-prints Claude API stream-json events from stdin with colorized output.')
109
+ .version(packageVersion)
110
+ .option('--no-color', 'disable colored output')
111
+ .parse(process.argv);
112
+ const options = program.opts();
113
+ ///////////////////////////////////////////////////////////////////////////////
114
+ ///////////////////////////////////////////////////////////////////////////////
115
+ //
116
+ ///////////////////////////////////////////////////////////////////////////////
117
+ ///////////////////////////////////////////////////////////////////////////////
118
+ if (options.color === false) {
119
+ Chalk.level = 0;
94
120
  }
95
- });
96
- rl.on('close', () => {
97
- printNewline();
98
- console.log(colors.system('\n[stream ended]'));
99
- });
121
+ const readline = Readline.createInterface({
122
+ input: process.stdin,
123
+ crlfDelay: Infinity,
124
+ });
125
+ readline.on('line', (line) => {
126
+ // stream-json pipes occasionally pad with blank lines between events.
127
+ if (line.trim() === '')
128
+ return;
129
+ try {
130
+ const parsed = JSON.parse(line);
131
+ MainHelper.handleEvent(parsed);
132
+ }
133
+ catch (err) {
134
+ console.error(colors.error('Invalid JSON:'), line);
135
+ }
136
+ });
137
+ await new Promise((resolve) => {
138
+ readline.on('close', () => {
139
+ MainHelper.printNewline();
140
+ console.log(colors.system('\n[stream ended]'));
141
+ resolve();
142
+ });
143
+ });
144
+ }
145
+ ///////////////////////////////////////////////////////////////////////////////
146
+ ///////////////////////////////////////////////////////////////////////////////
147
+ //
148
+ ///////////////////////////////////////////////////////////////////////////////
149
+ ///////////////////////////////////////////////////////////////////////////////
150
+ await main();
100
151
  //# sourceMappingURL=claude_stream_viewer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"claude_stream_viewer.js","sourceRoot":"","sources":["../src/claude_stream_viewer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claude_stream_viewer.js","sourceRoot":"","sources":["../src/claude_stream_viewer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude_stream_viewer",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Claude Stream Viewer - Pretty-prints Claude API stream-json events from stdin with colorized output.",
5
5
  "main": "dist/claude_stream_viewer.js",
6
6
  "bin": {
@@ -20,7 +20,8 @@
20
20
  "license": "ISC",
21
21
  "type": "module",
22
22
  "dependencies": {
23
- "chalk": "^5.6.2"
23
+ "chalk": "^5.6.2",
24
+ "commander": "^12.1.0"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@types/node": "^20.0.0",
@@ -4,8 +4,17 @@
4
4
  // them with colorized output. Designed to wrap an upstream `claude --stream-json`
5
5
  // pipe, e.g. `claude … | claude_stream_viewer`.
6
6
 
7
- import readline from 'node:readline';
8
- import chalk from 'chalk';
7
+ import Readline from 'node:readline';
8
+ import Fs from 'node:fs';
9
+ import * as Commander from 'commander';
10
+ import Chalk from 'chalk';
11
+ import Path from 'node:path';
12
+
13
+ const __dirname = new URL('.', import.meta.url).pathname;
14
+
15
+ type CliOptions = {
16
+ color: boolean;
17
+ };
9
18
 
10
19
  // Partial typing on purpose: stream-json's shape varies across SDK versions and
11
20
  // event subtypes, and we only consume the few fields we render. Anything we
@@ -31,107 +40,164 @@ type ClaudeEvent = {
31
40
  };
32
41
 
33
42
  const colors = {
34
- text: chalk.white,
35
- tool: chalk.cyan,
36
- system: chalk.gray,
37
- error: chalk.red,
38
- json: chalk.dim,
39
- header: chalk.yellow.bold,
43
+ text: Chalk.white,
44
+ tool: Chalk.cyan,
45
+ system: Chalk.gray,
46
+ error: Chalk.red,
47
+ json: Chalk.dim,
48
+ header: Chalk.yellow.bold,
40
49
  };
41
50
 
42
- function printText(text: string) {
43
- process.stdout.write(colors.text(text));
44
- }
51
+ ///////////////////////////////////////////////////////////////////////////////
52
+ ///////////////////////////////////////////////////////////////////////////////
53
+ // class MainHelper
54
+ ///////////////////////////////////////////////////////////////////////////////
55
+ ///////////////////////////////////////////////////////////////////////////////
45
56
 
46
- function printNewline() {
47
- process.stdout.write('\n');
48
- }
57
+ class MainHelper {
58
+ static printText(text: string) {
59
+ process.stdout.write(colors.text(text));
60
+ }
49
61
 
50
- function printHeader(label: string) {
51
- printNewline();
52
- console.log(colors.header(`\n=== ${label} ===`));
53
- }
62
+ static printNewline() {
63
+ process.stdout.write('\n');
64
+ }
54
65
 
55
- function printJSON(obj: unknown) {
56
- console.log(colors.json(JSON.stringify(obj, null, 2)));
57
- }
66
+ static printHeader(label: string) {
67
+ MainHelper.printNewline();
68
+ console.log(colors.header(`\n=== ${label} ===`));
69
+ }
58
70
 
59
- function handleEvent(data: ClaudeEvent) {
60
- try {
61
- // Newer SDKs wrap each event in a `stream_event` envelope.
62
- if (data.type === 'stream_event' && data.event) {
63
- const evt = data.event;
71
+ static printJSON(obj: unknown) {
72
+ console.log(colors.json(JSON.stringify(obj, null, 2)));
73
+ }
64
74
 
65
- if (evt.delta?.type === 'text_delta' && evt.delta.text) {
66
- printText(evt.delta.text);
67
- return;
68
- }
75
+ static handleEvent(data: ClaudeEvent) {
76
+ try {
77
+ // Newer SDKs wrap each event in a `stream_event` envelope.
78
+ if (data.type === 'stream_event' && data.event) {
79
+ const evt = data.event;
69
80
 
70
- if (evt.type === 'tool_use') {
71
- printHeader('TOOL USE');
72
- printJSON(evt);
73
- return;
74
- }
81
+ if (evt.delta?.type === 'text_delta' && evt.delta.text) {
82
+ MainHelper.printText(evt.delta.text);
83
+ return;
84
+ }
75
85
 
76
- // `content_block_delta` is the parent envelope of `text_delta` —
77
- // already rendered above, so skip it here to avoid duplicating
78
- // the text stream as a JSON dump.
79
- if (evt.type && evt.type !== 'content_block_delta') {
80
- printHeader(`EVENT: ${evt.type}`);
81
- printJSON(evt);
82
- return;
86
+ if (evt.type === 'tool_use') {
87
+ MainHelper.printHeader('TOOL USE');
88
+ MainHelper.printJSON(evt);
89
+ return;
90
+ }
91
+
92
+ // `content_block_delta` is the parent envelope of `text_delta` —
93
+ // already rendered above, so skip it here to avoid duplicating
94
+ // the text stream as a JSON dump.
95
+ if (evt.type && evt.type !== 'content_block_delta') {
96
+ MainHelper.printHeader(`EVENT: ${evt.type}`);
97
+ MainHelper.printJSON(evt);
98
+ return;
99
+ }
83
100
  }
84
- }
85
101
 
86
- // Legacy format: pre-`stream_event` SDKs emit raw `message_delta`
87
- // events with the text delta hanging off the root.
88
- if (data.type === 'message_delta') {
89
- const text = data.delta?.text;
90
- if (text) {
91
- printText(text);
92
- return;
102
+ // Legacy format: pre-`stream_event` SDKs emit raw `message_delta`
103
+ // events with the text delta hanging off the root.
104
+ if (data.type === 'message_delta') {
105
+ const text = data.delta?.text;
106
+ if (text) {
107
+ MainHelper.printText(text);
108
+ return;
109
+ }
93
110
  }
94
- }
95
111
 
96
- // Final assistant message bundling all content blocks for the turn.
97
- if (data.message?.content) {
98
- printHeader('FINAL MESSAGE');
99
- for (const block of data.message.content) {
100
- if (block.text) {
101
- console.log(colors.text(block.text));
112
+ // Final assistant message bundling all content blocks for the turn.
113
+ if (data.message?.content) {
114
+ MainHelper.printHeader('FINAL MESSAGE');
115
+ for (const block of data.message.content) {
116
+ if (block.text) {
117
+ console.log(colors.text(block.text));
118
+ }
102
119
  }
120
+ return;
103
121
  }
104
- return;
105
- }
106
122
 
107
- // Unrecognized shape — dump it raw so the user can extend ClaudeEvent.
108
- printHeader('UNKNOWN EVENT');
109
- printJSON(data);
110
- } catch (err) {
111
- // Never let a single malformed event take down the stream — log and move on.
112
- console.error(colors.error('Error processing event:'), err);
113
- printJSON(data);
123
+ // Unrecognized shape — dump it raw so the user can extend ClaudeEvent.
124
+ MainHelper.printHeader('UNKNOWN EVENT');
125
+ MainHelper.printJSON(data);
126
+ } catch (err) {
127
+ // Never let a single malformed event take down the stream — log and move on.
128
+ console.error(colors.error('Error processing event:'), err);
129
+ MainHelper.printJSON(data);
130
+ }
114
131
  }
115
132
  }
116
133
 
117
- const rl = readline.createInterface({
118
- input: process.stdin,
119
- crlfDelay: Infinity,
120
- });
134
+ ///////////////////////////////////////////////////////////////////////////////
135
+ ///////////////////////////////////////////////////////////////////////////////
136
+ // function main
137
+ ///////////////////////////////////////////////////////////////////////////////
138
+ ///////////////////////////////////////////////////////////////////////////////
139
+
140
+ async function main(): Promise<void> {
141
+
142
+ const packageJsonPath = Path.join(__dirname, '..', 'package.json');
143
+ const packageJson: object = JSON.parse(await Fs.promises.readFile(packageJsonPath, 'utf-8'))
144
+ const packageVersion = (packageJson as { version?: string }).version ?? 'unknown';
145
+
146
+ ///////////////////////////////////////////////////////////////////////////////
147
+ ///////////////////////////////////////////////////////////////////////////////
148
+ //
149
+ ///////////////////////////////////////////////////////////////////////////////
150
+ ///////////////////////////////////////////////////////////////////////////////
151
+
152
+ const program = new Commander.Command();
153
+ program
154
+ .name('claude_stream_viewer')
155
+ .description('Pretty-prints Claude API stream-json events from stdin with colorized output.')
156
+ .version(packageVersion)
157
+ .option('--no-color', 'disable colored output')
158
+ .parse(process.argv);
159
+
160
+ const options = program.opts<CliOptions>();
161
+ ///////////////////////////////////////////////////////////////////////////////
162
+ ///////////////////////////////////////////////////////////////////////////////
163
+ //
164
+ ///////////////////////////////////////////////////////////////////////////////
165
+ ///////////////////////////////////////////////////////////////////////////////
166
+
167
+ if (options.color === false) {
168
+ Chalk.level = 0;
169
+ }
121
170
 
122
- rl.on('line', (line) => {
123
- // stream-json pipes occasionally pad with blank lines between events.
124
- if (line.trim() === '') return;
171
+ const readline = Readline.createInterface({
172
+ input: process.stdin,
173
+ crlfDelay: Infinity,
174
+ });
125
175
 
126
- try {
127
- const parsed = JSON.parse(line);
128
- handleEvent(parsed);
129
- } catch (err) {
130
- console.error(colors.error('Invalid JSON:'), line);
131
- }
132
- });
176
+ readline.on('line', (line) => {
177
+ // stream-json pipes occasionally pad with blank lines between events.
178
+ if (line.trim() === '') return;
179
+
180
+ try {
181
+ const parsed = JSON.parse(line);
182
+ MainHelper.handleEvent(parsed);
183
+ } catch (err) {
184
+ console.error(colors.error('Invalid JSON:'), line);
185
+ }
186
+ });
187
+
188
+ await new Promise<void>((resolve) => {
189
+ readline.on('close', () => {
190
+ MainHelper.printNewline();
191
+ console.log(colors.system('\n[stream ended]'));
192
+ resolve();
193
+ });
194
+ });
195
+ }
196
+
197
+ ///////////////////////////////////////////////////////////////////////////////
198
+ ///////////////////////////////////////////////////////////////////////////////
199
+ //
200
+ ///////////////////////////////////////////////////////////////////////////////
201
+ ///////////////////////////////////////////////////////////////////////////////
133
202
 
134
- rl.on('close', () => {
135
- printNewline();
136
- console.log(colors.system('\n[stream ended]'));
137
- });
203
+ await main();