@rikkainc/ccp 0.2.1
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 +5 -0
- package/dist/cli/claude-pty-wrapper.d.ts +2 -0
- package/dist/cli/claude-pty-wrapper.js +421 -0
- package/dist/cli/claude-pty-wrapper.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +4 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/core/claude-paths.d.ts +7 -0
- package/dist/core/claude-paths.js +64 -0
- package/dist/core/claude-paths.js.map +1 -0
- package/dist/core/claude-records.d.ts +8 -0
- package/dist/core/claude-records.js +78 -0
- package/dist/core/claude-records.js.map +1 -0
- package/dist/core/claude-session-tail.d.ts +13 -0
- package/dist/core/claude-session-tail.js +88 -0
- package/dist/core/claude-session-tail.js.map +1 -0
- package/dist/core/errors.d.ts +5 -0
- package/dist/core/errors.js +15 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/passthrough.d.ts +23 -0
- package/dist/core/passthrough.js +347 -0
- package/dist/core/passthrough.js.map +1 -0
- package/dist/core/pty-runner.d.ts +22 -0
- package/dist/core/pty-runner.js +40 -0
- package/dist/core/pty-runner.js.map +1 -0
- package/dist/core/stream-json.d.ts +16 -0
- package/dist/core/stream-json.js +149 -0
- package/dist/core/stream-json.js.map +1 -0
- package/dist/core/terminal-text.d.ts +1 -0
- package/dist/core/terminal-text.js +58 -0
- package/dist/core/terminal-text.js.map +1 -0
- package/dist/core/wrapper.d.ts +34 -0
- package/dist/core/wrapper.js +299 -0
- package/dist/core/wrapper.js.map +1 -0
- package/docs/stream-json.md +82 -0
- package/docs/usage.md +104 -0
- package/package.json +50 -0
- package/scripts/fix-node-pty-perms.mjs +27 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Stream JSON
|
|
2
|
+
|
|
3
|
+
Claude's legacy print-mode stream:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
claude -p --verbose --output-format stream-json "..."
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
emits runtime JSONL events. In local samples from Claude Code 2.1.140, a simple
|
|
10
|
+
turn produced:
|
|
11
|
+
|
|
12
|
+
- `system` / `init`, with runtime metadata such as `cwd`, tools, model, plugins,
|
|
13
|
+
and Claude Code version.
|
|
14
|
+
- `rate_limit_event`, when available.
|
|
15
|
+
- `assistant`, containing a full Claude message object.
|
|
16
|
+
- `result`, containing the final text, session id, turn count, timing, usage,
|
|
17
|
+
cost, and terminal status.
|
|
18
|
+
|
|
19
|
+
A tool-use turn produced the same shape plus:
|
|
20
|
+
|
|
21
|
+
- `assistant` records with `message.content` blocks such as `tool_use`.
|
|
22
|
+
- `user` records with `message.content` blocks such as `tool_result` and
|
|
23
|
+
optional `tool_use_result` metadata.
|
|
24
|
+
- A final `assistant` text message followed by `result`.
|
|
25
|
+
|
|
26
|
+
`claude-pty-wrapper --session-jsonl` is different: it emits the raw durable
|
|
27
|
+
session records from Claude's `~/.claude/projects/.../<session-id>.jsonl` file.
|
|
28
|
+
Those records use Claude's persisted history shape, often including camel-case
|
|
29
|
+
fields such as `sessionId` and terminal records such as
|
|
30
|
+
`{"type":"system","subtype":"turn_duration"}`.
|
|
31
|
+
|
|
32
|
+
`claude-pty-wrapper -p --output-format stream-json` translates the durable
|
|
33
|
+
session records into a legacy-like stream:
|
|
34
|
+
|
|
35
|
+
- It emits a synthetic `system/init` event. If Claude persisted init metadata,
|
|
36
|
+
the wrapper forwards known metadata fields; otherwise it supplies `cwd` and
|
|
37
|
+
`session_id`.
|
|
38
|
+
- It suppresses the initial prompt `user` record, matching legacy print-mode
|
|
39
|
+
stream output.
|
|
40
|
+
- It forwards `assistant` message records with their full `message.content`
|
|
41
|
+
blocks intact, including text, tool calls, reasoning-style blocks, and other
|
|
42
|
+
content types Claude persists.
|
|
43
|
+
- It forwards `user` tool-result records so consumers can observe tool outputs.
|
|
44
|
+
- It emits a synthetic `result` record after the durable `turn_duration`
|
|
45
|
+
completion marker.
|
|
46
|
+
- It accepts `--include-partial-messages` for CLI compatibility but silently
|
|
47
|
+
ignores it because durable session files do not contain runtime partial
|
|
48
|
+
message deltas.
|
|
49
|
+
|
|
50
|
+
The translation is compatibility-oriented, not byte-for-byte identical. The
|
|
51
|
+
wrapper cannot synthesize data that is not present in the durable session file,
|
|
52
|
+
such as rate-limit metadata, exact API timings, full cost accounting, or every
|
|
53
|
+
runtime-only init field.
|
|
54
|
+
|
|
55
|
+
## Local Durable Session Findings
|
|
56
|
+
|
|
57
|
+
A local review of recent `~/.claude/projects/**/*.jsonl` files and
|
|
58
|
+
`~/.local/state/task-runner` run state found these durable-session patterns:
|
|
59
|
+
|
|
60
|
+
- Task-runner stores Claude `backendSessionId` values and resolved
|
|
61
|
+
`~/.claude/projects/.../<session-id>.jsonl` paths in `run.json` and
|
|
62
|
+
`run-events.jsonl`, so those files are representative wrapper inputs.
|
|
63
|
+
- Durable session records use `sessionId`; legacy print-mode stream JSON uses
|
|
64
|
+
`session_id`. The translator normalizes to `session_id`.
|
|
65
|
+
- Durable sessions commonly include non-conversation metadata records such as
|
|
66
|
+
`last-prompt`, `custom-title`, `agent-name`, `attachment`, `ai-title`,
|
|
67
|
+
`permission-mode`, `queue-operation`, and `file-history-snapshot`. The
|
|
68
|
+
translator ignores these.
|
|
69
|
+
- Durable `system` records observed include `turn_duration`,
|
|
70
|
+
`stop_hook_summary`, `away_summary`, and `local_command`. Only
|
|
71
|
+
`turn_duration` terminates the translated stream and creates `result`.
|
|
72
|
+
- Durable sessions often do not persist `system/init`; the translator emits a
|
|
73
|
+
minimal synthetic init event when no persisted init is observed before the
|
|
74
|
+
turn begins.
|
|
75
|
+
- Assistant content block shapes observed include `text`, `thinking`, and
|
|
76
|
+
`tool_use`. Tool-use blocks include `caller`, `id`, `input`, `name`, and
|
|
77
|
+
`type`.
|
|
78
|
+
- User tool-result blocks include `content`, `tool_use_id`, `type`, and
|
|
79
|
+
sometimes `is_error`.
|
|
80
|
+
- Durable user records commonly store tool result metadata as `toolUseResult`;
|
|
81
|
+
legacy stream JSON uses `tool_use_result`. The translator emits
|
|
82
|
+
`tool_use_result`.
|
package/docs/usage.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Usage
|
|
2
|
+
|
|
3
|
+
From a checkout, build and link the command before running it from your shell:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install
|
|
7
|
+
npm run build
|
|
8
|
+
npm link
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For development validation before linking, run:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install
|
|
15
|
+
npm run check
|
|
16
|
+
npm link
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Run Claude's normal interactive mode:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
claude-pty-wrapper "Explain the current repository"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Bare interactive mode runs Claude in a wrapper-owned PTY relay. Claude output is
|
|
26
|
+
written back to the terminal unchanged, and terminal input is forwarded to
|
|
27
|
+
Claude without screen emulation.
|
|
28
|
+
|
|
29
|
+
Enable idle freshness prompts in bare interactive mode:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
claude-pty-wrapper --freshness-interval 60 "Explain the current repository"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
When freshness is enabled, PTY output and user stdin both reset the idle timer.
|
|
36
|
+
The default injected message is `Please wait for further instructions.`.
|
|
37
|
+
|
|
38
|
+
Refresh Claude's login/session state and exit when the interactive input prompt
|
|
39
|
+
is ready:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
claude-pty-wrapper --login-only
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Login-only mode starts Claude in a wrapper-owned PTY without sending a prompt.
|
|
46
|
+
Terminal input and output are still relayed, so browser-login or pasted-token
|
|
47
|
+
flows can complete before the wrapper exits.
|
|
48
|
+
|
|
49
|
+
Run a fresh print-like turn:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
claude-pty-wrapper -p "Explain the current repository"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Run with explicit Claude model and effort flags:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
claude-pty-wrapper --model sonnet --effort high -p "Review the diff"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The wrapper owns `-p/--print`, `--output-format`, `--input-format`, and
|
|
62
|
+
`--session-jsonl`; those flags select wrapper behavior and are not passed to
|
|
63
|
+
Claude. `--login-only` is also wrapper-owned. Wrapper diagnostics such as
|
|
64
|
+
`--claude-bin`, `--cwd`, `--timeout`, `--raw-pty-log`, and `--wrapper-debug`
|
|
65
|
+
are also handled by the wrapper.
|
|
66
|
+
Passthrough freshness flags such as `--freshness-interval`,
|
|
67
|
+
`--freshness-message`, `--freshness-max-iterations`, and
|
|
68
|
+
`--freshness-max-duration` are handled by the wrapper and are rejected with
|
|
69
|
+
wrapper-managed output modes. Other supported Claude flags keep their Claude
|
|
70
|
+
names and are forwarded; run `claude-pty-wrapper --help` for the full accepted
|
|
71
|
+
flag list.
|
|
72
|
+
|
|
73
|
+
Emit durable session records instead of extracted text:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
claude-pty-wrapper --session-jsonl "List changed files"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Emit a legacy-like stream JSONL translation:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
claude-pty-wrapper -p --output-format stream-json "List changed files"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Emit a single Claude-shaped result object:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
claude-pty-wrapper -p --output-format json "List changed files"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A bare prompt without wrapper output flags runs Claude interactively through the
|
|
92
|
+
wrapper-owned PTY relay.
|
|
93
|
+
|
|
94
|
+
Resume an existing Claude session:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
claude-pty-wrapper --resume 18a18377-217d-4b29-9a68-c70a89b79330 -p "Continue"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Run opt-in live smoke tests against the installed Claude binary:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
CLAUDE_PTY_WRAPPER_LIVE_SMOKE=1 npm run test:smoke:live
|
|
104
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rikkainc/ccp",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "A PTY-backed Claude print-mode replacement that streams Claude session JSONL.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ccp": "dist/cli/main.js"
|
|
8
|
+
},
|
|
9
|
+
"files": ["dist", "README.md", "docs", "scripts/fix-node-pty-perms.mjs"],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc -p tsconfig.json && node scripts/chmod-bin.mjs",
|
|
12
|
+
"postinstall": "node scripts/fix-node-pty-perms.mjs",
|
|
13
|
+
"prepack": "npm run build",
|
|
14
|
+
"typecheck": "tsc -p tsconfig.test.json --noEmit",
|
|
15
|
+
"test": "vitest run test/unit test/integration",
|
|
16
|
+
"test:smoke": "npm run build && vitest run test/smoke",
|
|
17
|
+
"test:smoke:live": "npm run build && vitest run test/smoke/live-claude-smoke.test.ts",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"lint": "biome check .",
|
|
20
|
+
"lint:fix": "biome check --write .",
|
|
21
|
+
"format": "biome format --write .",
|
|
22
|
+
"check": "npm run lint && npm run typecheck && npm run test && npm run test:smoke"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"commander": "^12.1.0",
|
|
26
|
+
"node-pty": "^1.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@biomejs/biome": "^1.9.4",
|
|
30
|
+
"@types/node": "^22.10.2",
|
|
31
|
+
"typescript": "^5.7.2",
|
|
32
|
+
"vitest": "^2.1.8"
|
|
33
|
+
},
|
|
34
|
+
"lint-staged": {
|
|
35
|
+
"*.{ts,tsx,js,jsx,mjs,cjs,json}": ["biome format --write"]
|
|
36
|
+
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/rikkainc/ccp.git"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/rikkainc/ccp#readme",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/rikkainc/ccp/issues"
|
|
44
|
+
},
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"keywords": ["claude", "claude-code", "pty", "cli", "jsonl"],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=20"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { chmodSync, existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
if (process.platform !== "darwin" && process.platform !== "linux") {
|
|
6
|
+
process.exit(0);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
10
|
+
const helperSuffix = join(
|
|
11
|
+
"node-pty",
|
|
12
|
+
"prebuilds",
|
|
13
|
+
`${process.platform}-${process.arch}`,
|
|
14
|
+
"spawn-helper",
|
|
15
|
+
);
|
|
16
|
+
const candidates = [
|
|
17
|
+
join(packageRoot, "node_modules", helperSuffix),
|
|
18
|
+
join(packageRoot, "..", helperSuffix),
|
|
19
|
+
join(packageRoot, "..", "..", helperSuffix),
|
|
20
|
+
join(process.cwd(), "node_modules", helperSuffix),
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
for (const helperPath of candidates) {
|
|
24
|
+
if (existsSync(helperPath)) {
|
|
25
|
+
chmodSync(helperPath, 0o755);
|
|
26
|
+
}
|
|
27
|
+
}
|