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