claude-code-parser 0.1.0
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/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +12 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +21 -0
- package/dist/parser.js.map +1 -0
- package/dist/translator.d.ts +40 -0
- package/dist/translator.d.ts.map +1 -0
- package/dist/translator.js +247 -0
- package/dist/translator.js.map +1 -0
- package/dist/types/events.d.ts +46 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +2 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/protocol.d.ts +72 -0
- package/dist/types/protocol.d.ts.map +1 -0
- package/dist/types/protocol.js +9 -0
- package/dist/types/protocol.js.map +1 -0
- package/dist/writer.d.ts +41 -0
- package/dist/writer.d.ts.map +1 -0
- package/dist/writer.js +62 -0
- package/dist/writer.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# claude-code-parser
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/claude-code-parser)
|
|
4
|
+
[](https://bundlephobia.com/package/claude-code-parser)
|
|
5
|
+
|
|
6
|
+
Parse Claude Code's `--output-format stream-json` NDJSON output into fully typed TypeScript events. Zero dependencies. 9 kB.
|
|
7
|
+
|
|
8
|
+
**[Documentation](https://udhaykumarbala.github.io/claude-code-parser/)** | **[Protocol Reference](https://udhaykumarbala.github.io/claude-code-parser/protocol/overview)**
|
|
9
|
+
|
|
10
|
+
> Includes the first standalone documentation of Claude Code's undocumented `stream-json` protocol.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install claude-code-parser
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { spawn } from 'child_process'
|
|
22
|
+
import { createInterface } from 'readline'
|
|
23
|
+
import { parseLine, Translator, createMessage } from 'claude-code-parser'
|
|
24
|
+
|
|
25
|
+
const claude = spawn('claude', [
|
|
26
|
+
'-p', '--input-format', 'stream-json',
|
|
27
|
+
'--output-format', 'stream-json', '--verbose',
|
|
28
|
+
], { stdio: ['pipe', 'pipe', 'inherit'] })
|
|
29
|
+
|
|
30
|
+
const translator = new Translator()
|
|
31
|
+
|
|
32
|
+
const rl = createInterface({ input: claude.stdout! })
|
|
33
|
+
rl.on('line', (line) => {
|
|
34
|
+
const event = parseLine(line)
|
|
35
|
+
if (!event) return
|
|
36
|
+
|
|
37
|
+
for (const relay of translator.translate(event)) {
|
|
38
|
+
switch (relay.type) {
|
|
39
|
+
case 'text_delta': process.stdout.write(relay.content); break
|
|
40
|
+
case 'tool_use': console.log(`\n[tool] ${relay.toolName}`); break
|
|
41
|
+
case 'turn_complete': console.log(`\n[done] $${relay.costUsd?.toFixed(4)}`); break
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
claude.stdin!.write(createMessage.user('What is 2 + 2?'))
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
| Export | Description |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `parseLine(line)` | NDJSON line → `ClaudeEvent \| null` |
|
|
54
|
+
| `Translator` | Stateful dedup translator → `RelayEvent[]` |
|
|
55
|
+
| `createMessage.user(text)` | Construct stdin user message |
|
|
56
|
+
| `createMessage.approve(id)` | Approve pending tool execution |
|
|
57
|
+
| `createMessage.deny(id)` | Deny pending tool execution |
|
|
58
|
+
| `createMessage.toolResult(id, content)` | Send tool result |
|
|
59
|
+
| `extractContent(raw)` | Normalize polymorphic tool_result content |
|
|
60
|
+
|
|
61
|
+
### RelayEvent Types
|
|
62
|
+
|
|
63
|
+
`text_delta` | `thinking_delta` | `tool_use` | `tool_result` | `session_meta` | `turn_complete` | `error`
|
|
64
|
+
|
|
65
|
+
[Full API docs](https://udhaykumarbala.github.io/claude-code-parser/guide/api)
|
|
66
|
+
|
|
67
|
+
## Why This Exists
|
|
68
|
+
|
|
69
|
+
The official SDK (`@anthropic-ai/claude-code`) couples parsing with subprocess management. This library **only parses** — for developers building custom relays, dashboards, CI tools, or browser viewers who need raw event access.
|
|
70
|
+
|
|
71
|
+
Key problems it solves:
|
|
72
|
+
- **`--verbose` deduplication** — cumulative snapshots require stateful content tracking
|
|
73
|
+
- **Multi-agent interleaving** — sub-agents produce interleaved events on the same stdout
|
|
74
|
+
- **Polymorphic content** — `tool_result.content` can be string, array, or null
|
|
75
|
+
- **Double-encoded results** — the `result` field is JSON inside JSON
|
|
76
|
+
|
|
77
|
+
## Documentation
|
|
78
|
+
|
|
79
|
+
- [Getting Started](https://udhaykumarbala.github.io/claude-code-parser/guide/getting-started)
|
|
80
|
+
- [API Reference](https://udhaykumarbala.github.io/claude-code-parser/guide/api)
|
|
81
|
+
- [Examples](https://udhaykumarbala.github.io/claude-code-parser/guide/examples) — WS relay, log viewer, CI tracker, tool approval
|
|
82
|
+
- [Protocol Overview](https://udhaykumarbala.github.io/claude-code-parser/protocol/overview)
|
|
83
|
+
- [Output Events](https://udhaykumarbala.github.io/claude-code-parser/protocol/output-events) — Full event catalog
|
|
84
|
+
- [Input Messages](https://udhaykumarbala.github.io/claude-code-parser/protocol/input-messages) — Undocumented stdin protocol
|
|
85
|
+
- [Deduplication](https://udhaykumarbala.github.io/claude-code-parser/protocol/deduplication)
|
|
86
|
+
- [Multi-Agent](https://udhaykumarbala.github.io/claude-code-parser/protocol/multi-agent)
|
|
87
|
+
- [Gotchas](https://udhaykumarbala.github.io/claude-code-parser/protocol/gotchas) — Double encoding, polymorphic content, thinking field names
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
|
92
|
+
|
|
93
|
+
> This is an unofficial community package. Not affiliated with or endorsed by Anthropic.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { parseLine } from './parser.js';
|
|
2
|
+
export { Translator, extractContent } from './translator.js';
|
|
3
|
+
export { createMessage } from './writer.js';
|
|
4
|
+
export type { ClaudeEvent, ClaudeMessage, ClaudeContent, ModelUsageEntry, } from './types/protocol.js';
|
|
5
|
+
export type { RelayEvent, TextDeltaEvent, ThinkingDeltaEvent, ToolUseEvent, ToolResultEvent, SessionMetaEvent, TurnCompleteEvent, ErrorEvent, } from './types/events.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAGvC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAG5D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAG3C,YAAY,EACV,WAAW,EACX,aAAa,EACb,aAAa,EACb,eAAe,GAChB,MAAM,qBAAqB,CAAA;AAG5B,YAAY,EACV,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,GACX,MAAM,mBAAmB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Core parser
|
|
2
|
+
export { parseLine } from './parser.js';
|
|
3
|
+
// Stateful translator with dedup
|
|
4
|
+
export { Translator, extractContent } from './translator.js';
|
|
5
|
+
// Stdin message constructors
|
|
6
|
+
export { createMessage } from './writer.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc;AACd,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC,iCAAiC;AACjC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAE5D,6BAA6B;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA"}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ClaudeEvent } from './types/protocol.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a single NDJSON line from Claude Code's stdout into a typed event.
|
|
4
|
+
*
|
|
5
|
+
* Returns `null` for empty lines or unparseable JSON (matching the Go
|
|
6
|
+
* reference which logs and skips bad lines).
|
|
7
|
+
*
|
|
8
|
+
* @param line - One line of NDJSON from Claude Code's stdout
|
|
9
|
+
* @returns Typed ClaudeEvent, or null if the line is empty/invalid
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseLine(line: string): ClaudeEvent | null;
|
|
12
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAS1D"}
|
package/dist/parser.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a single NDJSON line from Claude Code's stdout into a typed event.
|
|
3
|
+
*
|
|
4
|
+
* Returns `null` for empty lines or unparseable JSON (matching the Go
|
|
5
|
+
* reference which logs and skips bad lines).
|
|
6
|
+
*
|
|
7
|
+
* @param line - One line of NDJSON from Claude Code's stdout
|
|
8
|
+
* @returns Typed ClaudeEvent, or null if the line is empty/invalid
|
|
9
|
+
*/
|
|
10
|
+
export function parseLine(line) {
|
|
11
|
+
const trimmed = line.trim();
|
|
12
|
+
if (trimmed.length === 0)
|
|
13
|
+
return null;
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(trimmed);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;IAC3B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAErC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAgB,CAAA;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ClaudeEvent } from './types/protocol.js';
|
|
2
|
+
import type { RelayEvent } from './types/events.js';
|
|
3
|
+
/**
|
|
4
|
+
* Translates raw ClaudeEvents into deduplicated RelayEvents.
|
|
5
|
+
*
|
|
6
|
+
* Tracks content block index to avoid re-emitting blocks that were
|
|
7
|
+
* already sent in previous `assistant` events (the `--verbose` mode
|
|
8
|
+
* cumulative snapshot problem).
|
|
9
|
+
*
|
|
10
|
+
* Port of the Go Translator in mobile-code/translator.go.
|
|
11
|
+
*/
|
|
12
|
+
export declare class Translator {
|
|
13
|
+
private lastContentIndex;
|
|
14
|
+
private lastFirstBlockKey;
|
|
15
|
+
private _sessionId;
|
|
16
|
+
private _model;
|
|
17
|
+
/** The session ID captured from the most recent `system/init` event. */
|
|
18
|
+
get sessionId(): string | undefined;
|
|
19
|
+
/** The model name captured from the most recent `system/init` event. */
|
|
20
|
+
get model(): string | undefined;
|
|
21
|
+
/** Reset content index tracking. Call on new turn/session. */
|
|
22
|
+
reset(): void;
|
|
23
|
+
/** Convert a raw ClaudeEvent into zero or more RelayEvents. */
|
|
24
|
+
translate(raw: ClaudeEvent): RelayEvent[];
|
|
25
|
+
private translateSystem;
|
|
26
|
+
private translateResult;
|
|
27
|
+
private translateAssistant;
|
|
28
|
+
private translateUser;
|
|
29
|
+
private translateContentBlock;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Handle the polymorphic `content` field in tool_result blocks.
|
|
33
|
+
*
|
|
34
|
+
* Three possible shapes:
|
|
35
|
+
* 1. `string` — plain text
|
|
36
|
+
* 2. `Array<{ type: string; text: string }>` — structured text blocks, joined with newline
|
|
37
|
+
* 3. `null` / `undefined` — empty string
|
|
38
|
+
*/
|
|
39
|
+
export declare function extractContent(raw: unknown): string;
|
|
40
|
+
//# sourceMappingURL=translator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translator.d.ts","sourceRoot":"","sources":["../src/translator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAgC,MAAM,qBAAqB,CAAA;AACpF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD;;;;;;;;GAQG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,MAAM,CAAoB;IAElC,wEAAwE;IACxE,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED,wEAAwE;IACxE,IAAI,KAAK,IAAI,MAAM,GAAG,SAAS,CAE9B;IAED,8DAA8D;IAC9D,KAAK,IAAI,IAAI;IAKb,+DAA+D;IAC/D,SAAS,CAAC,GAAG,EAAE,WAAW,GAAG,UAAU,EAAE;IAgBzC,OAAO,CAAC,eAAe;IAgCvB,OAAO,CAAC,eAAe;IAiCvB,OAAO,CAAC,kBAAkB;IA+B1B,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,qBAAqB;CAsC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAiBnD"}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translates raw ClaudeEvents into deduplicated RelayEvents.
|
|
3
|
+
*
|
|
4
|
+
* Tracks content block index to avoid re-emitting blocks that were
|
|
5
|
+
* already sent in previous `assistant` events (the `--verbose` mode
|
|
6
|
+
* cumulative snapshot problem).
|
|
7
|
+
*
|
|
8
|
+
* Port of the Go Translator in mobile-code/translator.go.
|
|
9
|
+
*/
|
|
10
|
+
export class Translator {
|
|
11
|
+
lastContentIndex = 0;
|
|
12
|
+
lastFirstBlockKey;
|
|
13
|
+
_sessionId;
|
|
14
|
+
_model;
|
|
15
|
+
/** The session ID captured from the most recent `system/init` event. */
|
|
16
|
+
get sessionId() {
|
|
17
|
+
return this._sessionId;
|
|
18
|
+
}
|
|
19
|
+
/** The model name captured from the most recent `system/init` event. */
|
|
20
|
+
get model() {
|
|
21
|
+
return this._model;
|
|
22
|
+
}
|
|
23
|
+
/** Reset content index tracking. Call on new turn/session. */
|
|
24
|
+
reset() {
|
|
25
|
+
this.lastContentIndex = 0;
|
|
26
|
+
this.lastFirstBlockKey = undefined;
|
|
27
|
+
}
|
|
28
|
+
/** Convert a raw ClaudeEvent into zero or more RelayEvents. */
|
|
29
|
+
translate(raw) {
|
|
30
|
+
switch (raw.type) {
|
|
31
|
+
case 'system':
|
|
32
|
+
return this.translateSystem(raw);
|
|
33
|
+
case 'result':
|
|
34
|
+
return this.translateResult(raw);
|
|
35
|
+
case 'assistant':
|
|
36
|
+
return this.translateAssistant(raw);
|
|
37
|
+
case 'user':
|
|
38
|
+
return this.translateUser(raw);
|
|
39
|
+
default:
|
|
40
|
+
// progress, rate_limit_event, and unknown types are ignored
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
translateSystem(raw) {
|
|
45
|
+
switch (raw.subtype) {
|
|
46
|
+
case 'init':
|
|
47
|
+
if (raw.session_id)
|
|
48
|
+
this._sessionId = raw.session_id;
|
|
49
|
+
if (raw.model)
|
|
50
|
+
this._model = raw.model;
|
|
51
|
+
return [{
|
|
52
|
+
type: 'session_meta',
|
|
53
|
+
model: raw.model ?? 'unknown',
|
|
54
|
+
}];
|
|
55
|
+
case 'result': {
|
|
56
|
+
const resultText = parseDoubleEncodedResult(raw.result);
|
|
57
|
+
if (raw.is_error) {
|
|
58
|
+
this.reset();
|
|
59
|
+
return [{
|
|
60
|
+
type: 'error',
|
|
61
|
+
message: resultText,
|
|
62
|
+
sessionId: raw.session_id,
|
|
63
|
+
}];
|
|
64
|
+
}
|
|
65
|
+
this.reset();
|
|
66
|
+
return [{
|
|
67
|
+
type: 'turn_complete',
|
|
68
|
+
sessionId: raw.session_id,
|
|
69
|
+
}];
|
|
70
|
+
}
|
|
71
|
+
default:
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
translateResult(raw) {
|
|
76
|
+
const resultText = parseDoubleEncodedResult(raw.result);
|
|
77
|
+
if (raw.subtype === 'error' || raw.is_error) {
|
|
78
|
+
this.reset();
|
|
79
|
+
return [{
|
|
80
|
+
type: 'error',
|
|
81
|
+
message: resultText,
|
|
82
|
+
sessionId: raw.session_id,
|
|
83
|
+
}];
|
|
84
|
+
}
|
|
85
|
+
const ev = {
|
|
86
|
+
type: 'turn_complete',
|
|
87
|
+
sessionId: raw.session_id,
|
|
88
|
+
costUsd: raw.total_cost_usd,
|
|
89
|
+
};
|
|
90
|
+
// Extract usage from modelUsage (take first model entry)
|
|
91
|
+
if (raw.modelUsage) {
|
|
92
|
+
for (const usage of Object.values(raw.modelUsage)) {
|
|
93
|
+
ev.inputTokens =
|
|
94
|
+
usage.inputTokens + usage.cacheReadInputTokens + usage.cacheCreationInputTokens;
|
|
95
|
+
ev.outputTokens = usage.outputTokens;
|
|
96
|
+
ev.contextWindow = usage.contextWindow;
|
|
97
|
+
break; // Take the first (usually only) model
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
this.reset();
|
|
101
|
+
return [ev];
|
|
102
|
+
}
|
|
103
|
+
translateAssistant(raw) {
|
|
104
|
+
const msg = raw.message;
|
|
105
|
+
if (!msg?.content || msg.content.length === 0)
|
|
106
|
+
return [];
|
|
107
|
+
// Detect context switches (new turn, different sub-agent, etc.)
|
|
108
|
+
// by fingerprinting the first content block. If it doesn't match
|
|
109
|
+
// what we were tracking, this is a different message stream → reset.
|
|
110
|
+
const firstKey = blockFingerprint(msg.content[0]);
|
|
111
|
+
if (firstKey !== this.lastFirstBlockKey) {
|
|
112
|
+
this.lastContentIndex = 0;
|
|
113
|
+
this.lastFirstBlockKey = firstKey;
|
|
114
|
+
}
|
|
115
|
+
// Safety: if content shrank below our index for any other reason, reset.
|
|
116
|
+
if (msg.content.length < this.lastContentIndex) {
|
|
117
|
+
this.lastContentIndex = 0;
|
|
118
|
+
}
|
|
119
|
+
const events = [];
|
|
120
|
+
// Only process content blocks we haven't sent yet (dedup)
|
|
121
|
+
for (let i = this.lastContentIndex; i < msg.content.length; i++) {
|
|
122
|
+
const block = msg.content[i];
|
|
123
|
+
const ev = this.translateContentBlock(block);
|
|
124
|
+
if (ev)
|
|
125
|
+
events.push(ev);
|
|
126
|
+
}
|
|
127
|
+
this.lastContentIndex = msg.content.length;
|
|
128
|
+
return events;
|
|
129
|
+
}
|
|
130
|
+
translateUser(raw) {
|
|
131
|
+
const msg = raw.message;
|
|
132
|
+
if (!msg?.content)
|
|
133
|
+
return [];
|
|
134
|
+
const events = [];
|
|
135
|
+
for (const block of msg.content) {
|
|
136
|
+
if (block.type === 'tool_result') {
|
|
137
|
+
events.push({
|
|
138
|
+
type: 'tool_result',
|
|
139
|
+
toolUseId: block.tool_use_id ?? '',
|
|
140
|
+
output: extractContent(block.content),
|
|
141
|
+
isError: block.is_error ?? false,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return events;
|
|
146
|
+
}
|
|
147
|
+
translateContentBlock(block) {
|
|
148
|
+
switch (block.type) {
|
|
149
|
+
case 'text':
|
|
150
|
+
return {
|
|
151
|
+
type: 'text_delta',
|
|
152
|
+
content: block.text ?? '',
|
|
153
|
+
};
|
|
154
|
+
case 'thinking': {
|
|
155
|
+
const text = block.thinking ?? block.text ?? '';
|
|
156
|
+
if (!text)
|
|
157
|
+
return null;
|
|
158
|
+
return {
|
|
159
|
+
type: 'thinking_delta',
|
|
160
|
+
content: text,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
case 'tool_use':
|
|
164
|
+
return {
|
|
165
|
+
type: 'tool_use',
|
|
166
|
+
toolUseId: block.id ?? '',
|
|
167
|
+
toolName: block.name ?? '',
|
|
168
|
+
input: block.input != null ? JSON.stringify(block.input) : '',
|
|
169
|
+
};
|
|
170
|
+
case 'tool_result':
|
|
171
|
+
return {
|
|
172
|
+
type: 'tool_result',
|
|
173
|
+
toolUseId: block.tool_use_id ?? '',
|
|
174
|
+
output: extractContent(block.content),
|
|
175
|
+
isError: block.is_error ?? false,
|
|
176
|
+
};
|
|
177
|
+
default:
|
|
178
|
+
// Unknown block types are silently skipped
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Handle the polymorphic `content` field in tool_result blocks.
|
|
185
|
+
*
|
|
186
|
+
* Three possible shapes:
|
|
187
|
+
* 1. `string` — plain text
|
|
188
|
+
* 2. `Array<{ type: string; text: string }>` — structured text blocks, joined with newline
|
|
189
|
+
* 3. `null` / `undefined` — empty string
|
|
190
|
+
*/
|
|
191
|
+
export function extractContent(raw) {
|
|
192
|
+
if (raw == null)
|
|
193
|
+
return '';
|
|
194
|
+
if (typeof raw === 'string')
|
|
195
|
+
return raw;
|
|
196
|
+
if (Array.isArray(raw)) {
|
|
197
|
+
const parts = [];
|
|
198
|
+
for (const block of raw) {
|
|
199
|
+
if (block && typeof block === 'object' && 'text' in block && typeof block.text === 'string') {
|
|
200
|
+
if (block.text)
|
|
201
|
+
parts.push(block.text);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return parts.join('\n');
|
|
205
|
+
}
|
|
206
|
+
// Fallback: stringify whatever we got
|
|
207
|
+
return String(raw);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Create a fingerprint for a content block to detect context switches.
|
|
211
|
+
*
|
|
212
|
+
* Uses the block's stable identity: tool_use blocks have unique IDs,
|
|
213
|
+
* text/thinking blocks use a prefix of their content. This lets the
|
|
214
|
+
* translator detect when interleaved sub-agent events switch context.
|
|
215
|
+
*/
|
|
216
|
+
function blockFingerprint(block) {
|
|
217
|
+
// tool_use blocks have unique IDs — best signal
|
|
218
|
+
if (block.id)
|
|
219
|
+
return `${block.type}:${block.id}`;
|
|
220
|
+
// thinking/text blocks — use first 64 chars as fingerprint
|
|
221
|
+
const text = block.thinking ?? block.text ?? '';
|
|
222
|
+
if (text)
|
|
223
|
+
return `${block.type}:${text.slice(0, 64)}`;
|
|
224
|
+
// Fallback for exotic blocks
|
|
225
|
+
return `${block.type}:${block.tool_use_id ?? 'unknown'}`;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Parse the double-encoded `result` field.
|
|
229
|
+
* Claude Code's result field is a JSON-encoded string (e.g., `"\"actual text\""`).
|
|
230
|
+
*/
|
|
231
|
+
function parseDoubleEncodedResult(result) {
|
|
232
|
+
if (result == null)
|
|
233
|
+
return '';
|
|
234
|
+
if (typeof result === 'string') {
|
|
235
|
+
try {
|
|
236
|
+
const parsed = JSON.parse(result);
|
|
237
|
+
if (typeof parsed === 'string')
|
|
238
|
+
return parsed;
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Not double-encoded, use as-is
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
return String(result);
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=translator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translator.js","sourceRoot":"","sources":["../src/translator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translated relay events — the normalized output of the Translator.
|
|
3
|
+
*
|
|
4
|
+
* This is a discriminated union on the `type` field. Consumers can
|
|
5
|
+
* switch on `event.type` to get full type narrowing.
|
|
6
|
+
*/
|
|
7
|
+
export type RelayEvent = TextDeltaEvent | ThinkingDeltaEvent | ToolUseEvent | ToolResultEvent | SessionMetaEvent | TurnCompleteEvent | ErrorEvent;
|
|
8
|
+
export interface TextDeltaEvent {
|
|
9
|
+
type: 'text_delta';
|
|
10
|
+
content: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ThinkingDeltaEvent {
|
|
13
|
+
type: 'thinking_delta';
|
|
14
|
+
content: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ToolUseEvent {
|
|
17
|
+
type: 'tool_use';
|
|
18
|
+
toolUseId: string;
|
|
19
|
+
toolName: string;
|
|
20
|
+
/** Tool input as a JSON string. */
|
|
21
|
+
input: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ToolResultEvent {
|
|
24
|
+
type: 'tool_result';
|
|
25
|
+
toolUseId: string;
|
|
26
|
+
output: string;
|
|
27
|
+
isError: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface SessionMetaEvent {
|
|
30
|
+
type: 'session_meta';
|
|
31
|
+
model: string;
|
|
32
|
+
}
|
|
33
|
+
export interface TurnCompleteEvent {
|
|
34
|
+
type: 'turn_complete';
|
|
35
|
+
sessionId?: string;
|
|
36
|
+
costUsd?: number;
|
|
37
|
+
inputTokens?: number;
|
|
38
|
+
outputTokens?: number;
|
|
39
|
+
contextWindow?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ErrorEvent {
|
|
42
|
+
type: 'error';
|
|
43
|
+
message: string;
|
|
44
|
+
sessionId?: string;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/types/events.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAClB,cAAc,GACd,kBAAkB,GAClB,YAAY,GACZ,eAAe,GACf,gBAAgB,GAChB,iBAAiB,GACjB,UAAU,CAAA;AAEd,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,gBAAgB,CAAA;IACtB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,aAAa,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,cAAc,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,eAAe,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/types/events.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw NDJSON types from Claude Code's `--output-format stream-json`.
|
|
3
|
+
*
|
|
4
|
+
* These mirror the wire format exactly. Fields use snake_case to match
|
|
5
|
+
* the JSON keys Claude Code emits. Types are intentionally loose —
|
|
6
|
+
* the protocol is undocumented and can add fields/types without notice.
|
|
7
|
+
*/
|
|
8
|
+
/** Per-model token usage breakdown, keyed by model ID in ClaudeEvent.modelUsage. */
|
|
9
|
+
export interface ModelUsageEntry {
|
|
10
|
+
inputTokens: number;
|
|
11
|
+
outputTokens: number;
|
|
12
|
+
cacheReadInputTokens: number;
|
|
13
|
+
cacheCreationInputTokens: number;
|
|
14
|
+
contextWindow: number;
|
|
15
|
+
}
|
|
16
|
+
/** Raw NDJSON envelope from Claude Code's stdout. */
|
|
17
|
+
export interface ClaudeEvent {
|
|
18
|
+
type: string;
|
|
19
|
+
subtype?: string;
|
|
20
|
+
message?: ClaudeMessage;
|
|
21
|
+
/** Double-encoded JSON string — must be JSON.parse()'d to get actual text. */
|
|
22
|
+
result?: unknown;
|
|
23
|
+
session_id?: string;
|
|
24
|
+
model?: string;
|
|
25
|
+
tools?: string[];
|
|
26
|
+
duration_ms?: number;
|
|
27
|
+
duration_api_ms?: number;
|
|
28
|
+
cost_usd?: number;
|
|
29
|
+
total_cost_usd?: number;
|
|
30
|
+
is_error?: boolean;
|
|
31
|
+
num_turns?: number;
|
|
32
|
+
modelUsage?: Record<string, ModelUsageEntry>;
|
|
33
|
+
usage?: unknown;
|
|
34
|
+
}
|
|
35
|
+
/** Message payload within a ClaudeEvent. */
|
|
36
|
+
export interface ClaudeMessage {
|
|
37
|
+
content: ClaudeContent[];
|
|
38
|
+
role?: string;
|
|
39
|
+
stop_reason?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Polymorphic content block.
|
|
43
|
+
*
|
|
44
|
+
* `type` is deliberately `string` (not a union) because Claude Code
|
|
45
|
+
* can add new block types in any release. The translator handles
|
|
46
|
+
* known types and silently skips unknown ones.
|
|
47
|
+
*/
|
|
48
|
+
export interface ClaudeContent {
|
|
49
|
+
type: string;
|
|
50
|
+
/** Text content (for `text` blocks, also fallback for `thinking` blocks in some formats). */
|
|
51
|
+
text?: string;
|
|
52
|
+
/** Thinking content (primary field for `thinking` blocks). */
|
|
53
|
+
thinking?: string;
|
|
54
|
+
/** Tool use ID (for `tool_use` blocks). */
|
|
55
|
+
id?: string;
|
|
56
|
+
/** Tool name (for `tool_use` blocks). */
|
|
57
|
+
name?: string;
|
|
58
|
+
/** Tool input — kept as unknown since shapes vary by tool. */
|
|
59
|
+
input?: unknown;
|
|
60
|
+
/**
|
|
61
|
+
* Tool result content — polymorphic:
|
|
62
|
+
* - `string` — plain text
|
|
63
|
+
* - `Array<{ type: string; text: string }>` — structured text blocks
|
|
64
|
+
* - `null` — no content
|
|
65
|
+
*/
|
|
66
|
+
content?: unknown;
|
|
67
|
+
/** Back-reference to the tool_use block (for `tool_result` blocks). */
|
|
68
|
+
tool_use_id?: string;
|
|
69
|
+
/** Whether the tool result is an error (for `tool_result` blocks). */
|
|
70
|
+
is_error?: boolean;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/types/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,oFAAoF;AACpF,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,wBAAwB,EAAE,MAAM,CAAA;IAChC,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,qDAAqD;AACrD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAC5C,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,aAAa,EAAE,CAAA;IACxB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,6FAA6F;IAC7F,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2CAA2C;IAC3C,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8DAA8D;IAC9D,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw NDJSON types from Claude Code's `--output-format stream-json`.
|
|
3
|
+
*
|
|
4
|
+
* These mirror the wire format exactly. Fields use snake_case to match
|
|
5
|
+
* the JSON keys Claude Code emits. Types are intentionally loose —
|
|
6
|
+
* the protocol is undocumented and can add fields/types without notice.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/types/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
package/dist/writer.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Construct NDJSON messages for Claude Code's `--input-format stream-json` stdin.
|
|
3
|
+
*
|
|
4
|
+
* Each function returns a single NDJSON line (JSON + newline) ready to be
|
|
5
|
+
* written to the Claude Code process's stdin.
|
|
6
|
+
*
|
|
7
|
+
* Port of Go WriteToStdin, WriteToolResult, WriteToolResultWithContent
|
|
8
|
+
* from mobile-code/session.go.
|
|
9
|
+
*/
|
|
10
|
+
export declare const createMessage: {
|
|
11
|
+
/**
|
|
12
|
+
* Construct a user chat message.
|
|
13
|
+
*
|
|
14
|
+
* @param content - The message text to send to Claude
|
|
15
|
+
* @returns NDJSON line to write to stdin
|
|
16
|
+
*/
|
|
17
|
+
readonly user: (content: string) => string;
|
|
18
|
+
/**
|
|
19
|
+
* Approve a pending tool execution.
|
|
20
|
+
*
|
|
21
|
+
* @param toolUseId - The tool_use block ID to approve
|
|
22
|
+
* @returns NDJSON line to write to stdin
|
|
23
|
+
*/
|
|
24
|
+
readonly approve: (toolUseId: string) => string;
|
|
25
|
+
/**
|
|
26
|
+
* Deny a pending tool execution.
|
|
27
|
+
*
|
|
28
|
+
* @param toolUseId - The tool_use block ID to deny
|
|
29
|
+
* @returns NDJSON line to write to stdin
|
|
30
|
+
*/
|
|
31
|
+
readonly deny: (toolUseId: string) => string;
|
|
32
|
+
/**
|
|
33
|
+
* Send a tool result with custom content (for interactive tools like AskUserQuestion).
|
|
34
|
+
*
|
|
35
|
+
* @param toolUseId - The tool_use block ID this result is for
|
|
36
|
+
* @param content - The result content to send
|
|
37
|
+
* @returns NDJSON line to write to stdin
|
|
38
|
+
*/
|
|
39
|
+
readonly toolResult: (toolUseId: string, content: string) => string;
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../src/writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa;IACxB;;;;;OAKG;6BACW,MAAM,KAAG,MAAM;IAO7B;;;;;OAKG;kCACgB,MAAM,KAAG,MAAM;IAOlC;;;;;OAKG;+BACa,MAAM,KAAG,MAAM;IAO/B;;;;;;OAMG;qCACmB,MAAM,WAAW,MAAM,KAAG,MAAM;CAO9C,CAAA"}
|
package/dist/writer.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Construct NDJSON messages for Claude Code's `--input-format stream-json` stdin.
|
|
3
|
+
*
|
|
4
|
+
* Each function returns a single NDJSON line (JSON + newline) ready to be
|
|
5
|
+
* written to the Claude Code process's stdin.
|
|
6
|
+
*
|
|
7
|
+
* Port of Go WriteToStdin, WriteToolResult, WriteToolResultWithContent
|
|
8
|
+
* from mobile-code/session.go.
|
|
9
|
+
*/
|
|
10
|
+
export const createMessage = {
|
|
11
|
+
/**
|
|
12
|
+
* Construct a user chat message.
|
|
13
|
+
*
|
|
14
|
+
* @param content - The message text to send to Claude
|
|
15
|
+
* @returns NDJSON line to write to stdin
|
|
16
|
+
*/
|
|
17
|
+
user(content) {
|
|
18
|
+
return JSON.stringify({
|
|
19
|
+
type: 'user',
|
|
20
|
+
message: { role: 'user', content },
|
|
21
|
+
}) + '\n';
|
|
22
|
+
},
|
|
23
|
+
/**
|
|
24
|
+
* Approve a pending tool execution.
|
|
25
|
+
*
|
|
26
|
+
* @param toolUseId - The tool_use block ID to approve
|
|
27
|
+
* @returns NDJSON line to write to stdin
|
|
28
|
+
*/
|
|
29
|
+
approve(toolUseId) {
|
|
30
|
+
return JSON.stringify({
|
|
31
|
+
type: 'approve',
|
|
32
|
+
tool_use_id: toolUseId,
|
|
33
|
+
}) + '\n';
|
|
34
|
+
},
|
|
35
|
+
/**
|
|
36
|
+
* Deny a pending tool execution.
|
|
37
|
+
*
|
|
38
|
+
* @param toolUseId - The tool_use block ID to deny
|
|
39
|
+
* @returns NDJSON line to write to stdin
|
|
40
|
+
*/
|
|
41
|
+
deny(toolUseId) {
|
|
42
|
+
return JSON.stringify({
|
|
43
|
+
type: 'deny',
|
|
44
|
+
tool_use_id: toolUseId,
|
|
45
|
+
}) + '\n';
|
|
46
|
+
},
|
|
47
|
+
/**
|
|
48
|
+
* Send a tool result with custom content (for interactive tools like AskUserQuestion).
|
|
49
|
+
*
|
|
50
|
+
* @param toolUseId - The tool_use block ID this result is for
|
|
51
|
+
* @param content - The result content to send
|
|
52
|
+
* @returns NDJSON line to write to stdin
|
|
53
|
+
*/
|
|
54
|
+
toolResult(toolUseId, content) {
|
|
55
|
+
return JSON.stringify({
|
|
56
|
+
type: 'tool_result',
|
|
57
|
+
tool_use_id: toolUseId,
|
|
58
|
+
content,
|
|
59
|
+
}) + '\n';
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.js","sourceRoot":"","sources":["../src/writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B;;;;;OAKG;IACH,IAAI,CAAC,OAAe;QAClB,OAAO,IAAI,CAAC,SAAS,CAAC;YACpB,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;SACnC,CAAC,GAAG,IAAI,CAAA;IACX,CAAC;IAED;;;;;OAKG;IACH,OAAO,CAAC,SAAiB;QACvB,OAAO,IAAI,CAAC,SAAS,CAAC;YACpB,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,SAAS;SACvB,CAAC,GAAG,IAAI,CAAA;IACX,CAAC;IAED;;;;;OAKG;IACH,IAAI,CAAC,SAAiB;QACpB,OAAO,IAAI,CAAC,SAAS,CAAC;YACpB,IAAI,EAAE,MAAM;YACZ,WAAW,EAAE,SAAS;SACvB,CAAC,GAAG,IAAI,CAAA;IACX,CAAC;IAED;;;;;;OAMG;IACH,UAAU,CAAC,SAAiB,EAAE,OAAe;QAC3C,OAAO,IAAI,CAAC,SAAS,CAAC;YACpB,IAAI,EAAE,aAAa;YACnB,WAAW,EAAE,SAAS;YACtB,OAAO;SACR,CAAC,GAAG,IAAI,CAAA;IACX,CAAC;CACO,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-code-parser",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Parse Claude Code's --output-format stream-json into fully typed TypeScript events",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"prepublishOnly": "npm run build",
|
|
28
|
+
"docs:dev": "vitepress dev docs",
|
|
29
|
+
"docs:build": "vitepress build docs",
|
|
30
|
+
"docs:preview": "vitepress preview docs"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"claude",
|
|
34
|
+
"claude-code",
|
|
35
|
+
"anthropic",
|
|
36
|
+
"stream-json",
|
|
37
|
+
"ndjson",
|
|
38
|
+
"parser",
|
|
39
|
+
"cli",
|
|
40
|
+
"streaming",
|
|
41
|
+
"typescript"
|
|
42
|
+
],
|
|
43
|
+
"author": "",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^25.5.0",
|
|
47
|
+
"typescript": "^5.9.3",
|
|
48
|
+
"vitepress": "^1.6.4",
|
|
49
|
+
"vitest": "^4.1.0"
|
|
50
|
+
}
|
|
51
|
+
}
|