open-claude-p 1.0.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.ja.md +708 -0
- package/README.ko.md +713 -0
- package/README.md +850 -0
- package/README.zh.md +708 -0
- package/bin/cli.js +782 -0
- package/package.json +68 -0
- package/scripts/postinstall.js +60 -0
- package/src/chat/event-filters.js +116 -0
- package/src/chat/index.js +1225 -0
- package/src/completion/detector.js +163 -0
- package/src/daemon/client.js +172 -0
- package/src/daemon/server.js +267 -0
- package/src/daemon/socket.js +78 -0
- package/src/index.js +908 -0
- package/src/options/index.js +4 -0
- package/src/options/parse-argv.js +214 -0
- package/src/options/spec.js +519 -0
- package/src/options/validate.js +104 -0
- package/src/output/index.js +8 -0
- package/src/output/json.js +83 -0
- package/src/output/registry.js +35 -0
- package/src/output/stream-json.js +111 -0
- package/src/output/text.js +94 -0
- package/src/parsers/ansi-strip.js +94 -0
- package/src/parsers/index.js +8 -0
- package/src/parsers/pipeline.js +50 -0
- package/src/parsers/registry.js +43 -0
- package/src/parsers/sentinel.js +41 -0
- package/src/parsers/tui-frame.js +256 -0
- package/src/print-mode.js +214 -0
- package/src/pty/index.js +3 -0
- package/src/pty/pool.js +127 -0
- package/src/pty/session.js +88 -0
- package/src/session-log.js +124 -0
package/README.md
ADDED
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
**English** · [한국어](README.ko.md) · [中文](README.zh.md) · [日本語](README.ja.md)
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/open-claude-p)
|
|
4
|
+
[](https://www.npmjs.com/package/open-claude-p)
|
|
5
|
+
[](https://github.com/empty-user77/open-claude-p/stargazers)
|
|
6
|
+
[](https://github.com/empty-user77/open-claude-p/blob/main/LICENSE)
|
|
7
|
+
[](https://github.com/sponsors/empty-user77)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# open-claude-p (ocp)
|
|
12
|
+
|
|
13
|
+
A PTY-based compatibility layer that drives the interactive `claude` CLI via **node-pty**, providing the same functionality as `claude -p` (headless print mode) in environments where it is unavailable.
|
|
14
|
+
|
|
15
|
+
> **Key difference**: `claude -p` is Claude Code's non-interactive mode that operates through an internal API, but it is not available on certain plans/environments. `open-claude-p` runs the actual TUI client through a PTY and parses the output stream to achieve the same result.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Table of Contents
|
|
20
|
+
|
|
21
|
+
- [Installation](#installation)
|
|
22
|
+
- [Recommended one-time setup](#recommended-one-time-setup)
|
|
23
|
+
- [CLI Usage](#cli-usage) — full reference in [docs/cli-reference.md](./docs/cli-reference.md)
|
|
24
|
+
- [Daemon (Session Persistence)](#daemon-session-persistence)
|
|
25
|
+
- [SDK Usage](#sdk-usage) — full reference in [docs/sdk-reference.md](./docs/sdk-reference.md)
|
|
26
|
+
- [High-level: createChatClient](#high-level-createchatclient)
|
|
27
|
+
- [Low-level: createDriver](#low-level-createdriver)
|
|
28
|
+
- [Session Management](#session-management)
|
|
29
|
+
- [Working with JSONL Session Files](#working-with-jsonl-session-files)
|
|
30
|
+
- [About Output Parsing](#about-output-parsing)
|
|
31
|
+
- [Environment Variables](#environment-variables)
|
|
32
|
+
- [Full Option Reference](#full-option-reference)
|
|
33
|
+
- [Sample App](#sample-app)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
### npm
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Install as a project dependency
|
|
43
|
+
npm install open-claude-p
|
|
44
|
+
|
|
45
|
+
# Or install globally to use the `ocp` CLI anywhere
|
|
46
|
+
npm install -g open-claude-p
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### From source (development)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Clone and symlink from the project root
|
|
53
|
+
git clone https://github.com/empty-user77/open-claude-p.git
|
|
54
|
+
cd open-claude-p
|
|
55
|
+
npm link
|
|
56
|
+
|
|
57
|
+
# Or install via a local path from another project
|
|
58
|
+
npm install /path/to/open-claude-p
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Prerequisite**: The `claude` CLI must be installed and available on `PATH`.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Verify Claude Code CLI installation
|
|
65
|
+
claude --version
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Recommended one-time setup
|
|
71
|
+
|
|
72
|
+
`ocp` is non-interactive automation — set these once in `~/.zshrc` /
|
|
73
|
+
`~/.bashrc` so prompts that need WebSearch / Bash / file tools "just
|
|
74
|
+
work" without prompts you cannot answer:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
export OCP_AUTO_ACCEPT_TRUST=1 # auto-accept "do you trust this folder?" on first use
|
|
78
|
+
export OCP_DEFAULT_SKIP_PERMS=1 # default --dangerously-skip-permissions for the CLI
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Without `OCP_DEFAULT_SKIP_PERMS`, claude declines tool use with
|
|
82
|
+
"I don't have access to that tool" (the permission prompt would need a
|
|
83
|
+
human to click "Yes").
|
|
84
|
+
|
|
85
|
+
> ⚠️ With `OCP_DEFAULT_SKIP_PERMS=1`, every `ocp "…"` invocation can
|
|
86
|
+
> execute Bash, Write, Edit, and other tools on whatever prompt you
|
|
87
|
+
> feed it, with no per-tool confirmation. Use this on a personal
|
|
88
|
+
> workstation. **Never** set it in CI, in a shared shell, or in a
|
|
89
|
+
> project `.envrc` you might clone from an untrusted source.
|
|
90
|
+
|
|
91
|
+
See [docs/cli-reference.md](./docs/cli-reference.md) for the full env var list.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## CLI Usage
|
|
96
|
+
|
|
97
|
+
The package installs a single binary: **`ocp`**.
|
|
98
|
+
|
|
99
|
+
When stderr is an interactive terminal, ocp shows a live spinner while
|
|
100
|
+
working and prints a one-line meta footer after the response:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
⏱ 42.8s · ↑41.2K ↓864 tok · $0.0287 · 🔧 Web Search, ToolSearch, WebSearch
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Hide it with `--no-meta` / `OCP_NO_META=1`. In pipe / redirect mode
|
|
107
|
+
both spinner and meta are suppressed automatically.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Basic usage
|
|
111
|
+
ocp "Hello"
|
|
112
|
+
|
|
113
|
+
# Supports the same argv format as claude -p (-p flag is ignored for compatibility)
|
|
114
|
+
ocp -p "Hello"
|
|
115
|
+
|
|
116
|
+
# Read prompt from stdin
|
|
117
|
+
echo "What is the weather in Seoul?" | ocp
|
|
118
|
+
|
|
119
|
+
# Specify output format
|
|
120
|
+
ocp --output-format json "Answer in one word: apple"
|
|
121
|
+
ocp --output-format stream-json "Hi"
|
|
122
|
+
|
|
123
|
+
# Specify model
|
|
124
|
+
ocp --model sonnet "Complex question..."
|
|
125
|
+
ocp --model claude-opus-4-7 "Architecture review..."
|
|
126
|
+
|
|
127
|
+
# Append system prompt
|
|
128
|
+
ocp --append-system-prompt "Always reply in English" "what's the weather?"
|
|
129
|
+
|
|
130
|
+
# Resume a session — sessionId is printed to stderr
|
|
131
|
+
SID=$(ocp "Say only kiwi" 2>&1 >/dev/null | grep sessionId | grep -oE '[0-9a-f-]{36}')
|
|
132
|
+
ocp --resume "$SID" "What did you just say?"
|
|
133
|
+
|
|
134
|
+
# Or automatically continue the most recent session
|
|
135
|
+
ocp --continue "What did you just say?"
|
|
136
|
+
|
|
137
|
+
# Skip permission checks (for automation)
|
|
138
|
+
ocp --dangerously-skip-permissions "Read and analyze the file"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Output Formats
|
|
142
|
+
|
|
143
|
+
#### `text` (default)
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
Hello! How can I help you today?
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### `json`
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"result": "Hello! How can I help you today?",
|
|
154
|
+
"session_id": "a1b2c3d4-...",
|
|
155
|
+
"is_error": false,
|
|
156
|
+
"cost_usd": null,
|
|
157
|
+
"duration_ms": 4200,
|
|
158
|
+
"num_turns": 1
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### `stream-json` (NDJSON)
|
|
163
|
+
|
|
164
|
+
Response is streamed line by line:
|
|
165
|
+
|
|
166
|
+
```jsonl
|
|
167
|
+
{"type":"system","subtype":"init","session_id":"a1b2c3d4-...","tools":[],"mcp_servers":[]}
|
|
168
|
+
{"type":"assistant","session_id":"a1b2c3d4-...","message":{"role":"assistant","content":[{"type":"text","text":"Hello!"}]}}
|
|
169
|
+
{"type":"result","subtype":"success","session_id":"a1b2c3d4-...","is_error":false,"duration_ms":4200}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Daemon (Session Persistence)
|
|
175
|
+
|
|
176
|
+
The `ocp` CLI keeps PTY sessions alive through a **background daemon** by default.
|
|
177
|
+
This means repeated calls in the same directory skip the 2.5-second warmup wait, and conversation context is automatically preserved.
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
ocp "First question" → starts a new daemon if none exists, reuses if one does
|
|
181
|
+
ocp "Second question" → connects to the same daemon, context is preserved
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Daemon sockets are stored under `~/.ocp/`, **one per working directory**.
|
|
185
|
+
|
|
186
|
+
### Disabling the Daemon
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
OCP_NO_DAEMON=1 ocp "Run just once" # spawns a PTY directly, no daemon
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
When you should skip the daemon:
|
|
193
|
+
- When using `--resume`, `--continue`, or `--fork-session` flags (automatically switches to direct mode)
|
|
194
|
+
- When using `--input-format=stream-json`
|
|
195
|
+
- For single isolated runs in CI/CD environments
|
|
196
|
+
|
|
197
|
+
### Daemon Environment Variables
|
|
198
|
+
|
|
199
|
+
| Variable | Description | Default |
|
|
200
|
+
|----------|-------------|---------|
|
|
201
|
+
| `OCP_NO_DAEMON` | Set to `1` to disable the daemon | — |
|
|
202
|
+
| `OCP_DAEMON_IDLE_MS` | Auto-terminate daemon after this much idle time | `600000` (10 min) |
|
|
203
|
+
| `OCP_MAX_DAEMONS` | Maximum number of daemons to keep alive simultaneously | `30` |
|
|
204
|
+
| `OCP_DAEMON_MAX_PENDING` | Max queued requests per daemon socket; over → `busy` reply | `16` |
|
|
205
|
+
| `OCP_DAEMON_MAX_REQ_BYTES` | Per-request body cap | `4194304` (4 MiB) |
|
|
206
|
+
| `OCP_DAEMON_SOCKET_TIMEOUT_MS` | Idle timeout for a single daemon socket | `30000` |
|
|
207
|
+
| `OCP_DAEMON_SOCKET_MAX_LIFETIME_MS` | Hard cap on a single socket's total lifetime (slow-loris guard) | `300000` (5 min) |
|
|
208
|
+
| `OCP_DAEMON_MAX_RESPONSE_BYTES` | Client-side cap on bytes buffered from a single daemon reply | `67108864` (64 MiB) |
|
|
209
|
+
| `OCP_DAEMON_MAX_PARALLEL` | Max warm PTYs the daemon keeps for concurrent fresh requests | `8` |
|
|
210
|
+
| `OCP_NO_DEFAULT_PROMPT` | Set to `1` to suppress the CLI's default tool-use system prompt | — |
|
|
211
|
+
| `OCP_NO_DEFAULT_TOOLS` | Set to `1` to NOT pre-approve `WebSearch`/`WebFetch` | — |
|
|
212
|
+
| `OCP_DIR` | Override the daemon state directory | `~/.ocp` |
|
|
213
|
+
|
|
214
|
+
### Driver / Pool Environment Variables
|
|
215
|
+
|
|
216
|
+
| Variable | Description | Default |
|
|
217
|
+
|----------|-------------|---------|
|
|
218
|
+
| `OCP_CLAUDE_BIN` | Path to the upstream `claude` binary | `claude` (PATH lookup) |
|
|
219
|
+
| `OCP_POOL_SIZE` | Default `poolSize` for `createDriver` | `0` (off) |
|
|
220
|
+
| `OCP_POOL_MAX_AGE_MS` | Max age of a pooled PTY before forced refresh | `600000` |
|
|
221
|
+
| `OCP_WARMUP_MS` / `OCP_REUSE_WARMUP_MS` | Per-spawn / reuse warmup wait | tuned defaults |
|
|
222
|
+
| `OCP_IDLE_MS` / `OCP_PRE_IDLE_MS` | Completion idle thresholds (post-sentinel / silence-only) | `1500` / `8000` |
|
|
223
|
+
| `OCP_FIRST_RESPONSE_MS` | Max wait for first byte after prompt submit | `120000` |
|
|
224
|
+
| `OCP_MAX_RESPONSE_MS` | Per-turn hard ceiling in ms. Default is intentionally long because in-flight idle / pre-idle silence detectors already abort genuinely-stuck runs much earlier. | `86400000` (24 h) |
|
|
225
|
+
| `OCP_TRUST_SETTLE_MS` | Pause after dismissing the trust prompt | tuned |
|
|
226
|
+
| `OCP_PROMPT_BOX_WAIT_MS` / `OCP_PROMPT_BOX_SETTLE_MS` | Wait for prompt box / settle after type | tuned |
|
|
227
|
+
| `OCP_AUTO_ACCEPT_TRUST` | Auto-accept the "trust this folder" dialog | `0` |
|
|
228
|
+
| `OCP_DEFAULT_SKIP_PERMS` | Auto-pass `--dangerously-skip-permissions` | `0` |
|
|
229
|
+
| `OCP_NO_LIVE` | Suppress live spinner / progress in CLI | `0` |
|
|
230
|
+
| `OCP_NO_META` | Suppress the post-turn meta line in CLI | `0` |
|
|
231
|
+
| `OCP_NO_WARN` | Mute non-fatal driver warnings to stderr | `0` |
|
|
232
|
+
| `OCP_DEBUG` | Verbose debug logging (path takes precedence over flag) | `0` |
|
|
233
|
+
| `OCP_PRINT_MODE` | Force print-mode codepath | `0` |
|
|
234
|
+
| `OCP_END` | Sentinel string the driver appends to detect completion | internal |
|
|
235
|
+
| `OCP_ALLOW_UNSAFE_ARGV` | **Security-sensitive.** Disable argv sanitizer (control-char / `--` stripping). Daemon refuses this var; CLI prints a warning. Only set for trusted, ephemeral invocations. | `0` |
|
|
236
|
+
|
|
237
|
+
### Chat SDK Environment Variables
|
|
238
|
+
|
|
239
|
+
| Variable | Description | Default |
|
|
240
|
+
|----------|-------------|---------|
|
|
241
|
+
| `OCP_MAX_CONVERSATIONS` | Hard cap on conversations kept in the JSON store | `500` |
|
|
242
|
+
| `OCP_MAX_MESSAGES_PER_CONV` | Hard cap on messages per conversation | `500` |
|
|
243
|
+
| `OCP_MAX_MESSAGE_CHARS` | Reject `chat.send` messages larger than this | `262144` (256 KiB) |
|
|
244
|
+
| `OCP_MAX_JSONL_BYTES` | Tail-read cap for `~/.claude/projects/.../*.jsonl` | `8388608` (8 MiB) |
|
|
245
|
+
| `OCP_MAX_STDIN_BYTES` | CLI stdin payload cap | `262144` |
|
|
246
|
+
| `OCP_LOCK_SWEEP_AGE_MS` | Drop sentinel files older than this from the lock dir | `86400000` (24 h) |
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## SDK Usage
|
|
251
|
+
|
|
252
|
+
Two layers ship in the same `open-claude-p` package — pick whichever
|
|
253
|
+
matches your altitude:
|
|
254
|
+
|
|
255
|
+
| Layer | Import | For |
|
|
256
|
+
|-------|--------|-----|
|
|
257
|
+
| **High-level chat client** | `open-claude-p/chat` | Build a chat UI / app. Brings conversation persistence, skill loading, JSONL clean-text, cost calc. |
|
|
258
|
+
| **Low-level driver** | `open-claude-p` | One-shot PTY runs with full control. |
|
|
259
|
+
|
|
260
|
+
Full API: [docs/sdk-reference.md](./docs/sdk-reference.md).
|
|
261
|
+
|
|
262
|
+
### High-level: `createChatClient`
|
|
263
|
+
|
|
264
|
+
The same SDK the bundled sample chat server uses. One import gives you
|
|
265
|
+
conversation state, multi-turn `--resume`, skill loading, JSONL clean
|
|
266
|
+
markdown, and per-turn cost / token / tool tracking.
|
|
267
|
+
|
|
268
|
+
```js
|
|
269
|
+
import { createChatClient } from 'open-claude-p/chat';
|
|
270
|
+
|
|
271
|
+
const chat = createChatClient({
|
|
272
|
+
// dbPath: './conversations.json', // default: <cwd>/conversations.json
|
|
273
|
+
// skillsDir: '~/.claude/skills', // default: ~/.claude/skills
|
|
274
|
+
dangerouslySkipPermissions: true, // let claude actually use its tools
|
|
275
|
+
// appendSystemPrompt: 'extra rules', // appended on top of SDK base default
|
|
276
|
+
// // (null to opt the base out entirely)
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// New conversation
|
|
280
|
+
const r1 = await chat.send({
|
|
281
|
+
message: 'What is the weather in Seoul right now?',
|
|
282
|
+
onEvent(ev) {
|
|
283
|
+
if (ev.type === 'spinner') process.stderr.write(`[…] ${ev.label}\n`);
|
|
284
|
+
if (ev.type === 'assistant-text') process.stdout.write(ev.text);
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
console.log(r1.text); // clean markdown
|
|
288
|
+
console.log(r1.meta); // { elapsedMs, inputTokens, outputTokens, costUsd, tools }
|
|
289
|
+
|
|
290
|
+
// Continue the same conversation
|
|
291
|
+
const r2 = await chat.send({
|
|
292
|
+
conversationId: r1.conversationId,
|
|
293
|
+
message: 'And tomorrow?',
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// CRUD
|
|
297
|
+
await chat.listConversations(); // [{ id, title, ... }]
|
|
298
|
+
await chat.getConversation(r1.conversationId);
|
|
299
|
+
await chat.deleteConversation(r1.conversationId);
|
|
300
|
+
await chat.listSkills(); // [{ name, description }]
|
|
301
|
+
|
|
302
|
+
await chat.close();
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Also exported from the same module: `readSessionText` (read JSONL clean
|
|
306
|
+
markdown), `cleanResponse` (TUI chrome strip), `cleanSpinnerLabel`,
|
|
307
|
+
`isAssistantTextNoise`, `extractToolName`. See the
|
|
308
|
+
[SDK reference](./docs/sdk-reference.md#standalone-helpers-also-exported-from-open-claude-pchat).
|
|
309
|
+
|
|
310
|
+
### Low-level: `createDriver`
|
|
311
|
+
|
|
312
|
+
For one-shot PTY runs with no conversation state or higher-level helpers.
|
|
313
|
+
|
|
314
|
+
### `createDriver(opts?)`
|
|
315
|
+
|
|
316
|
+
Creates a driver. Share a single driver instance across your entire application.
|
|
317
|
+
|
|
318
|
+
```js
|
|
319
|
+
import { createDriver } from 'open-claude-p';
|
|
320
|
+
|
|
321
|
+
const driver = createDriver({
|
|
322
|
+
claudeBin: 'claude', // path to claude binary (default: claude on PATH)
|
|
323
|
+
warmupMs: 2500, // PTY initialization wait time (ms)
|
|
324
|
+
reuseWarmupMs: 200, // wait time when reusing from pool (ms)
|
|
325
|
+
idleMs: 1500, // silence wait after response completes (ms)
|
|
326
|
+
preIdleMs: 8000, // minimum wait before sentinel matching (ms)
|
|
327
|
+
maxResponseMs: 86_400_000, // hard timeout (ms), default 24 h (idleMs/preIdleMs abort earlier)
|
|
328
|
+
poolSize: 0, // PTY pool size (0=disabled, N>0=keep N warmed up)
|
|
329
|
+
poolMaxAgeMs: 600_000, // maximum pool session lifetime (ms)
|
|
330
|
+
cwd: process.cwd(), // working directory
|
|
331
|
+
env: {}, // additional environment variables
|
|
332
|
+
debug: false, // print debug logs to stderr
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### `runOneShot(req)`
|
|
337
|
+
|
|
338
|
+
Sends a single prompt to Claude and waits for the response.
|
|
339
|
+
|
|
340
|
+
```js
|
|
341
|
+
const result = await driver.runOneShot({
|
|
342
|
+
prompt: 'What is the current weather in Seoul?',
|
|
343
|
+
|
|
344
|
+
// ── Model / Behavior ──────────────────────────
|
|
345
|
+
model: 'sonnet', // model name
|
|
346
|
+
effort: 'high', // 'low' | 'medium' | 'high' | 'max'
|
|
347
|
+
thinking: 'adaptive', // 'enabled' | 'adaptive' | 'disabled'
|
|
348
|
+
maxTurns: 5, // max agent turns (shim-enforced)
|
|
349
|
+
|
|
350
|
+
// ── System Prompt ─────────────────────────────
|
|
351
|
+
systemPrompt: 'You are a weather expert', // replace entire system prompt
|
|
352
|
+
appendSystemPrompt: 'Always reply in English', // append to default prompt
|
|
353
|
+
|
|
354
|
+
// ── Permissions / Tools ───────────────────────
|
|
355
|
+
dangerouslySkipPermissions: true, // skip permission checks
|
|
356
|
+
allowedTools: ['WebSearch', 'Read'], // tool whitelist
|
|
357
|
+
disallowedTools: ['Bash'], // tool blacklist
|
|
358
|
+
|
|
359
|
+
// ── Session ───────────────────────────────────
|
|
360
|
+
resume: 'a1b2c3d4-...', // resume from previous session UUID
|
|
361
|
+
continue: false, // continue most recent session
|
|
362
|
+
forkSession: false, // create a new session ID on resume
|
|
363
|
+
|
|
364
|
+
// ── Working Directory ─────────────────────────
|
|
365
|
+
cwd: '/path/to/project',
|
|
366
|
+
|
|
367
|
+
// ── Cancellation ──────────────────────────────
|
|
368
|
+
abortSignal: controller.signal,
|
|
369
|
+
|
|
370
|
+
// ── Real-time Event Callback ──────────────────
|
|
371
|
+
onEvent(ev) {
|
|
372
|
+
// called in real time as the response is generated
|
|
373
|
+
// see "Event Types" section below
|
|
374
|
+
if (ev.type === 'assistant-text') {
|
|
375
|
+
process.stdout.write(ev.text);
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Return Value: OneShotResult
|
|
382
|
+
|
|
383
|
+
When `runOneShot()` resolves, it returns an object with the following structure:
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
{
|
|
387
|
+
// ── Core Result ────────────────────────────────────────────────────
|
|
388
|
+
text: string,
|
|
389
|
+
// Claude's final response text (TUI artifacts removed).
|
|
390
|
+
// Raw text as Claude produced it — markdown, HTML, code blocks, etc.
|
|
391
|
+
// Rendering/parsing is the caller's responsibility.
|
|
392
|
+
|
|
393
|
+
sessionId: string | null,
|
|
394
|
+
// Claude session UUID for this request.
|
|
395
|
+
// Use with --resume <sessionId> to continue the conversation.
|
|
396
|
+
// Falls back to filesystem scan of ~/.claude/projects/ if banner capture fails.
|
|
397
|
+
|
|
398
|
+
isError: boolean,
|
|
399
|
+
// true = completed with error or timeout
|
|
400
|
+
|
|
401
|
+
completionReason: string,
|
|
402
|
+
// How the request completed:
|
|
403
|
+
// 'sentinel' normal completion (sentinel string detected)
|
|
404
|
+
// 'idle' silence timeout after response
|
|
405
|
+
// 'prompt-box' TUI input box reappeared
|
|
406
|
+
// 'timeout' maxResponseMs exceeded
|
|
407
|
+
// 'max-turns' maxTurns limit reached
|
|
408
|
+
// 'upstream-exited' claude process exited first
|
|
409
|
+
// 'write-failed' PTY write failed
|
|
410
|
+
// 'cancelled' cancelled via AbortSignal
|
|
411
|
+
|
|
412
|
+
exitCode: number,
|
|
413
|
+
// 0 = success, 1 = error
|
|
414
|
+
|
|
415
|
+
// ── Event Array ───────────────────────────────────────────────────
|
|
416
|
+
events: Array<object>,
|
|
417
|
+
// All events produced by the pipeline (same objects as onEvent callback).
|
|
418
|
+
// See "Event Types" section below.
|
|
419
|
+
|
|
420
|
+
// ── Performance Metrics ───────────────────────────────────────────
|
|
421
|
+
durationMs: number,
|
|
422
|
+
// Total elapsed time (ms)
|
|
423
|
+
|
|
424
|
+
cost: { totalUsd: number | null, numTurns: number | null },
|
|
425
|
+
// Currently null (cost info is not directly available from PTY).
|
|
426
|
+
// For accurate token/cost data, read the JSONL session file (see below).
|
|
427
|
+
|
|
428
|
+
diagnostics: { rawBytes: number, strippedBytes: number },
|
|
429
|
+
// Raw bytes received from PTY / bytes after ANSI stripping
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Event Types (onEvent callback)
|
|
434
|
+
|
|
435
|
+
The `onEvent` callback and `result.events` array contain events of the following types:
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
// When Claude starts responding (⏺ marker detected)
|
|
439
|
+
{ type: 'assistant-region-entered', n: number }
|
|
440
|
+
|
|
441
|
+
// When the response region closes (hr or sentinel detected)
|
|
442
|
+
{ type: 'assistant-region-exited', n: number }
|
|
443
|
+
|
|
444
|
+
// One line of response text (real-time streaming)
|
|
445
|
+
{
|
|
446
|
+
type: 'assistant-text',
|
|
447
|
+
text: string, // one line of text (raw markdown)
|
|
448
|
+
region: number // which response region (higher = later; useful when resuming)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Claude session UUID detected (from banner or exit message)
|
|
452
|
+
{ type: 'session-id', id: string }
|
|
453
|
+
|
|
454
|
+
// TUI spinner (indicates Claude is working)
|
|
455
|
+
// label: "Searching the web...", "Reading file...", "Cogitated for 25s", etc.
|
|
456
|
+
{ type: 'spinner', label: string }
|
|
457
|
+
|
|
458
|
+
// TUI input box appeared on screen (one of the completion signals)
|
|
459
|
+
{ type: 'prompt-box-shown' }
|
|
460
|
+
|
|
461
|
+
// Sentinel string detected (normal completion)
|
|
462
|
+
{ type: 'sentinel' }
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### Event Usage Example
|
|
466
|
+
|
|
467
|
+
```js
|
|
468
|
+
const result = await driver.runOneShot({
|
|
469
|
+
prompt: 'Analyze this long document',
|
|
470
|
+
onEvent(ev) {
|
|
471
|
+
switch (ev.type) {
|
|
472
|
+
case 'assistant-text':
|
|
473
|
+
// real-time streaming — print line by line
|
|
474
|
+
process.stdout.write(ev.text + '\n');
|
|
475
|
+
break;
|
|
476
|
+
|
|
477
|
+
case 'spinner':
|
|
478
|
+
// spinner label — shown while a tool is in use (e.g. "Searching the web...")
|
|
479
|
+
process.stderr.write(`\r⏳ ${ev.label} `);
|
|
480
|
+
break;
|
|
481
|
+
|
|
482
|
+
case 'session-id':
|
|
483
|
+
// save session ID early so you can resume even if a timeout occurs
|
|
484
|
+
saveSessionId(ev.id);
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
// Full text can also be reconstructed from events
|
|
491
|
+
const lines = result.events
|
|
492
|
+
.filter(e => e.type === 'assistant-text' && e.region === Math.max(...result.events.filter(e => e.type === 'assistant-text').map(e => e.region)))
|
|
493
|
+
.map(e => e.text);
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
## Session Management
|
|
499
|
+
|
|
500
|
+
Claude identifies each session with a UUID, which you can use to resume previous conversations.
|
|
501
|
+
|
|
502
|
+
```js
|
|
503
|
+
// 1. First request — start a new session
|
|
504
|
+
const result1 = await driver.runOneShot({
|
|
505
|
+
prompt: 'Implement Fibonacci in Python',
|
|
506
|
+
});
|
|
507
|
+
console.log('Session ID:', result1.sessionId);
|
|
508
|
+
// → "a1b2c3d4-5678-..."
|
|
509
|
+
|
|
510
|
+
// 2. Resume session — previous conversation context is preserved
|
|
511
|
+
const result2 = await driver.runOneShot({
|
|
512
|
+
prompt: 'Now rewrite that without recursion using iteration',
|
|
513
|
+
resume: result1.sessionId,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// 3. Fork session — explore a different direction while preserving the original
|
|
517
|
+
const result3 = await driver.runOneShot({
|
|
518
|
+
prompt: 'Instead, make a generator version',
|
|
519
|
+
resume: result1.sessionId,
|
|
520
|
+
forkSession: true, // assigns a new UUID, original session is preserved
|
|
521
|
+
});
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## Working with JSONL Session Files
|
|
527
|
+
|
|
528
|
+
The Claude CLI saves each session as a JSONL file at:
|
|
529
|
+
|
|
530
|
+
```
|
|
531
|
+
~/.claude/projects/<cwd-encoded-as-path>/<session-uuid>.jsonl
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
For example, if `cwd` is `/Users/alice/myproject`:
|
|
535
|
+
→ `~/.claude/projects/-Users-alice-myproject/<uuid>.jsonl`
|
|
536
|
+
|
|
537
|
+
These files contain **token usage, cost, and tool usage metadata** that is not available in the PTY output.
|
|
538
|
+
|
|
539
|
+
```js
|
|
540
|
+
import { readFile } from 'node:fs/promises';
|
|
541
|
+
import path from 'node:path';
|
|
542
|
+
import os from 'node:os';
|
|
543
|
+
|
|
544
|
+
async function readSessionMeta(sessionId, cwd = process.cwd()) {
|
|
545
|
+
const key = path.resolve(cwd).replace(/\//g, '-');
|
|
546
|
+
const filePath = path.join(os.homedir(), '.claude', 'projects', key, `${sessionId}.jsonl`);
|
|
547
|
+
const lines = (await readFile(filePath, 'utf8')).split('\n').filter(Boolean);
|
|
548
|
+
|
|
549
|
+
// Extract usage from the last assistant message
|
|
550
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
551
|
+
try {
|
|
552
|
+
const ev = JSON.parse(lines[i]);
|
|
553
|
+
if (ev.message?.role === 'assistant') {
|
|
554
|
+
const textBlock = ev.message.content?.find(c => c.type === 'text');
|
|
555
|
+
return {
|
|
556
|
+
text: textBlock?.text, // clean markdown text (no TUI artifacts)
|
|
557
|
+
usage: ev.message.usage, // { input_tokens, output_tokens, cache_read_input_tokens, ... }
|
|
558
|
+
timestamp: ev.timestamp,
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
} catch {}
|
|
562
|
+
}
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const meta = await readSessionMeta(result.sessionId);
|
|
567
|
+
// meta.usage.input_tokens → input tokens
|
|
568
|
+
// meta.usage.output_tokens → output tokens
|
|
569
|
+
// meta.usage.cache_read_input_tokens → cache read tokens
|
|
570
|
+
// meta.usage.server_tool_use.web_search_requests → web search count
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### What You Can Get from JSONL
|
|
574
|
+
|
|
575
|
+
| Item | PTY result.text | JSONL |
|
|
576
|
+
|------|----------------|-------|
|
|
577
|
+
| Response text | ✅ (may have TUI artifacts) | ✅ (clean markdown) |
|
|
578
|
+
| Input token count | ❌ | ✅ |
|
|
579
|
+
| Output token count | ❌ | ✅ |
|
|
580
|
+
| Cache token count | ❌ | ✅ |
|
|
581
|
+
| Cost calculation | ❌ | ✅ (tokens × unit price) |
|
|
582
|
+
| Web search count | ❌ | ✅ |
|
|
583
|
+
| Timestamps | ❌ | ✅ |
|
|
584
|
+
| Tool usage details | Partial (events) | ✅ |
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## About Output Parsing
|
|
589
|
+
|
|
590
|
+
**`result.text` is the raw markdown/text produced by Claude.**
|
|
591
|
+
It is an open format — rendering, parsing, and display are your responsibility.
|
|
592
|
+
|
|
593
|
+
```
|
|
594
|
+
result.text example:
|
|
595
|
+
─────────────────────────────────────
|
|
596
|
+
# Fibonacci Sequence
|
|
597
|
+
|
|
598
|
+
Here is how to implement Fibonacci in Python:
|
|
599
|
+
|
|
600
|
+
```python
|
|
601
|
+
def fib(n):
|
|
602
|
+
a, b = 0, 1
|
|
603
|
+
for _ in range(n):
|
|
604
|
+
a, b = b, a + b
|
|
605
|
+
return a
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
- Time complexity: O(n)
|
|
609
|
+
- Space complexity: O(1)
|
|
610
|
+
─────────────────────────────────────
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### Parsing Implementation Reference
|
|
614
|
+
|
|
615
|
+
The `renderMarkdown()` function in `sample/public/app.js` is a parsing example for web UIs.
|
|
616
|
+
Implement your own based on your target environment:
|
|
617
|
+
|
|
618
|
+
```js
|
|
619
|
+
// Web UI → HTML rendering (example)
|
|
620
|
+
import { marked } from 'marked';
|
|
621
|
+
const html = marked.parse(result.text);
|
|
622
|
+
|
|
623
|
+
// Terminal → ANSI color rendering (example)
|
|
624
|
+
import { renderMarkdown } from 'cli-markdown';
|
|
625
|
+
console.log(renderMarkdown(result.text));
|
|
626
|
+
|
|
627
|
+
// Pass to another LLM → use as-is
|
|
628
|
+
const nextPrompt = `Previous response: ${result.text}\n\nNow proceed to the next step`;
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
### On TUI Artifacts
|
|
632
|
+
|
|
633
|
+
`result.text` has TUI rendering residue removed as much as possible by ocp, but it may not be perfect.
|
|
634
|
+
If you need cleaner text, reading from the **JSONL session file** is recommended (see above).
|
|
635
|
+
|
|
636
|
+
---
|
|
637
|
+
|
|
638
|
+
## Environment Variables
|
|
639
|
+
|
|
640
|
+
Most-used:
|
|
641
|
+
|
|
642
|
+
| Variable | What |
|
|
643
|
+
|----------|------|
|
|
644
|
+
| `OCP_AUTO_ACCEPT_TRUST=1` | Auto-accept first-use "Do you trust this folder?" dialog |
|
|
645
|
+
| `OCP_DEFAULT_SKIP_PERMS=1` | Default `--dangerously-skip-permissions` for the CLI (tools just work) |
|
|
646
|
+
| `OCP_NO_LIVE=1` | Disable the live spinner on stderr |
|
|
647
|
+
| `OCP_NO_META=1` | Hide the trailing meta footer (`⏱ … · 🔧 …`) |
|
|
648
|
+
| `OCP_NO_DAEMON=1` | Fresh PTY for every call (no warm daemon) |
|
|
649
|
+
| `OCP_MAX_RESPONSE_MS` | Hard timeout in ms, default `86400000` (24 h) |
|
|
650
|
+
| `OCP_FIRST_RESPONSE_MS` | Fail-fast if no progress within N ms after prompt, default `20000` |
|
|
651
|
+
| `OCP_PROMPT_BOX_WAIT_MS` | Max wait for the input chevron, default `15000` (raise for heavy hook/MCP loading) |
|
|
652
|
+
| `OCP_CLAUDE_BIN` | Path to upstream `claude` binary, default `'claude'` |
|
|
653
|
+
|
|
654
|
+
Full table (timeouts, pool, daemon, sanitizer escape hatch, etc.):
|
|
655
|
+
[docs/cli-reference.md#environment-variables](./docs/cli-reference.md#environment-variables).
|
|
656
|
+
|
|
657
|
+
```bash
|
|
658
|
+
# Tighten per-turn timeout to 10 minutes (default is 24 h)
|
|
659
|
+
OCP_MAX_RESPONSE_MS=600000 ocp "Complex task..."
|
|
660
|
+
|
|
661
|
+
# Single run without daemon
|
|
662
|
+
OCP_NO_DAEMON=1 ocp "Run just once"
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
## Full Option Reference
|
|
668
|
+
|
|
669
|
+
Quick mapping between `runOneShot(req)` request fields and CLI flags
|
|
670
|
+
appears below; the canonical, fully-described table is in
|
|
671
|
+
[docs/cli-reference.md#options](./docs/cli-reference.md#options).
|
|
672
|
+
|
|
673
|
+
| req field | CLI flag | Type | Description |
|
|
674
|
+
|-----------|----------|------|-------------|
|
|
675
|
+
| `model` | `--model` | string | Model name (e.g. `sonnet`, `claude-sonnet-4-6`) |
|
|
676
|
+
| `systemPrompt` | `--system-prompt` | string | Replace entire system prompt |
|
|
677
|
+
| `appendSystemPrompt` | `--append-system-prompt` | string | Append to default system prompt |
|
|
678
|
+
| `dangerouslySkipPermissions` | `--dangerously-skip-permissions` | boolean | Skip permission checks |
|
|
679
|
+
| `allowedTools` | `--allowed-tools` | string[] | Tool whitelist |
|
|
680
|
+
| `disallowedTools` | `--disallowed-tools` | string[] | Tool blacklist |
|
|
681
|
+
| `resume` | `--resume` / `-r` | string | Resume from session UUID |
|
|
682
|
+
| `continue` | `--continue` / `-c` | boolean | Continue most recent session |
|
|
683
|
+
| `forkSession` | `--fork-session` | boolean | Create new session ID on resume |
|
|
684
|
+
| `sessionId` | `--session-id` | string | Assign a specific UUID to the new session |
|
|
685
|
+
| `noSessionPersistence` | `--no-session-persistence` | boolean | Disable session saving |
|
|
686
|
+
| `effort` | `--effort` | enum | `low` \| `medium` \| `high` \| `max` |
|
|
687
|
+
| `thinking` | `--thinking` | enum | `enabled` \| `adaptive` \| `disabled` |
|
|
688
|
+
| `maxTurns` | `--max-turns` | number | Maximum agent turns |
|
|
689
|
+
| `fallbackModel` | `--fallback-model` | string | Fallback when primary model is overloaded |
|
|
690
|
+
| `permissionMode` | `--permission-mode` | string | `default` \| `plan` \| `acceptEdits` \| `bypassPermissions` |
|
|
691
|
+
| `mcpConfig` | `--mcp-config` | string[] | MCP config paths |
|
|
692
|
+
| `addDir` | `--add-dir` | string[] | Additional directories tools can access |
|
|
693
|
+
| `bare` | `--bare` | boolean | Minimal mode (disables hooks, LSP, plugins, etc.) |
|
|
694
|
+
| `debug` | `--debug` | boolean | Print debug logs to stderr |
|
|
695
|
+
| `verbose` | `--verbose` | boolean | Verbose output |
|
|
696
|
+
| `cwd` | `--cwd` | string | PTY process working directory |
|
|
697
|
+
| `abortSignal` | — | AbortSignal | Cancellation signal |
|
|
698
|
+
| `onEvent` | — | function | Real-time event callback |
|
|
699
|
+
| `passThroughArgv` | — | string[] | Additional argv passed directly to claude |
|
|
700
|
+
|
|
701
|
+
---
|
|
702
|
+
|
|
703
|
+
## Sample App
|
|
704
|
+
|
|
705
|
+
The `sample/` directory contains a web-based chat UI built with ocp.
|
|
706
|
+
|
|
707
|
+
### Running
|
|
708
|
+
|
|
709
|
+
```bash
|
|
710
|
+
cd sample
|
|
711
|
+
node server.js
|
|
712
|
+
# → http://localhost:3000
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
### Sample App Structure
|
|
716
|
+
|
|
717
|
+
```
|
|
718
|
+
sample/
|
|
719
|
+
server.js Express server — wraps ocp driver, SSE streaming
|
|
720
|
+
data/
|
|
721
|
+
conversations.json Conversation history (auto-generated)
|
|
722
|
+
public/
|
|
723
|
+
index.html Chat UI
|
|
724
|
+
app.js Client-side JavaScript
|
|
725
|
+
style.css Stylesheet
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
### Sample Server API
|
|
729
|
+
|
|
730
|
+
| Endpoint | Method | Description |
|
|
731
|
+
|----------|--------|-------------|
|
|
732
|
+
| `/api/conversations` | GET | List conversations |
|
|
733
|
+
| `/api/conversations/:id` | GET | Conversation detail (all messages) |
|
|
734
|
+
| `/api/conversations/:id` | DELETE | Delete conversation |
|
|
735
|
+
| `/api/chat` | POST | Send message (SSE streaming) |
|
|
736
|
+
| `/api/monitor` | GET | PTY event monitor (SSE) |
|
|
737
|
+
| `/api/skills` | GET | Skill list from `~/.claude/skills/` |
|
|
738
|
+
| `/api/processes` | GET | In-flight request list (`id`, `prompt`, `elapsedMs`) |
|
|
739
|
+
| `/api/processes/:id` | DELETE | Abort a specific request (`all` to abort all) |
|
|
740
|
+
|
|
741
|
+
### `/api/chat` SSE Events
|
|
742
|
+
|
|
743
|
+
Chat requests (`POST /api/chat`) stream responses as Server-Sent Events:
|
|
744
|
+
|
|
745
|
+
```js
|
|
746
|
+
// Client request
|
|
747
|
+
const resp = await fetch('/api/chat', {
|
|
748
|
+
method: 'POST',
|
|
749
|
+
headers: { 'Content-Type': 'application/json' },
|
|
750
|
+
body: JSON.stringify({
|
|
751
|
+
message: 'What is the weather in Seoul?',
|
|
752
|
+
conversationId: null, // null to start a new conversation
|
|
753
|
+
skillName: 'my-skill', // optional: skill name from ~/.claude/skills/
|
|
754
|
+
}),
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
// SSE event types
|
|
758
|
+
{ type: 'spinner', label: 'Searching the web...' } // working status
|
|
759
|
+
{ type: 'text', text: 'Hello...' } // streaming text (chunk)
|
|
760
|
+
{ type: 'error', error: 'error message' } // error
|
|
761
|
+
{
|
|
762
|
+
type: 'done',
|
|
763
|
+
conversationId: 'uuid', // conversation ID (saved)
|
|
764
|
+
text: 'full final response', // complete final text (clean markdown from JSONL)
|
|
765
|
+
isNew: true, // whether this is a new conversation
|
|
766
|
+
meta: {
|
|
767
|
+
elapsedMs: 4200, // elapsed time (ms)
|
|
768
|
+
inputTokens: 1500, // input tokens (including cache)
|
|
769
|
+
outputTokens: 320, // output tokens
|
|
770
|
+
costUsd: 0.0042, // cost (USD)
|
|
771
|
+
tools: ['WebSearch'], // tools used
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
### Markdown Parsing in the Sample
|
|
777
|
+
|
|
778
|
+
The sample app (`sample/public/app.js`) converts `result.text` to HTML via a `renderMarkdown()` function.
|
|
779
|
+
|
|
780
|
+
**This parsing code is sample-only.** For real projects, use:
|
|
781
|
+
- Web: `marked`, `markdown-it`, etc.
|
|
782
|
+
- Terminal: `cli-markdown`, `terminal-link`, etc.
|
|
783
|
+
- React: `react-markdown`
|
|
784
|
+
- LLM input: use as-is
|
|
785
|
+
|
|
786
|
+
### Process Manager (`ocp-ps`)
|
|
787
|
+
|
|
788
|
+
The sample app ships a CLI tool that uses the `/api/processes` API to list and cancel in-flight requests.
|
|
789
|
+
|
|
790
|
+
```bash
|
|
791
|
+
cd sample
|
|
792
|
+
|
|
793
|
+
node ocp-ps.js # list in-flight requests
|
|
794
|
+
node ocp-ps.js kill <id> # abort a specific request
|
|
795
|
+
node ocp-ps.js kill all # abort all
|
|
796
|
+
node ocp-ps.js watch # auto-refresh every second
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
> **Note**: `ocp-ps` is a sample implementation that uses the sample app's HTTP API (`/api/processes`).
|
|
800
|
+
> When building your own server with the ocp library, you can implement the same process management pattern.
|
|
801
|
+
|
|
802
|
+
### Skill Invocation (`/skillname`)
|
|
803
|
+
|
|
804
|
+
Typing `/` in the chat input shows a dropdown of skills from `~/.claude/skills/`.
|
|
805
|
+
|
|
806
|
+
```
|
|
807
|
+
User input: /my-skill Analyze this PRD and find related repos
|
|
808
|
+
↓
|
|
809
|
+
Server: injects SKILL.md content as appendSystemPrompt
|
|
810
|
+
↓
|
|
811
|
+
Claude: executes following skill instructions
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
---
|
|
815
|
+
|
|
816
|
+
## Module Structure
|
|
817
|
+
|
|
818
|
+
```
|
|
819
|
+
src/
|
|
820
|
+
index.js Library public API (createDriver, runOneShot)
|
|
821
|
+
options/
|
|
822
|
+
spec.js All option definitions (single source of truth)
|
|
823
|
+
parse-argv.js CLI argv parser
|
|
824
|
+
validate.js Cross-option validation
|
|
825
|
+
parsers/
|
|
826
|
+
ansi-strip.js ANSI escape removal
|
|
827
|
+
tui-frame.js TUI frame parser (event generation)
|
|
828
|
+
sentinel.js Completion sentinel detection
|
|
829
|
+
pipeline.js Parser pipeline composition
|
|
830
|
+
output/
|
|
831
|
+
text.js --output-format text adapter
|
|
832
|
+
json.js --output-format json adapter
|
|
833
|
+
stream-json.js --output-format stream-json adapter
|
|
834
|
+
pty/
|
|
835
|
+
session.js Single PTY session lifecycle
|
|
836
|
+
pool.js Warmed-up PTY pool
|
|
837
|
+
completion/
|
|
838
|
+
detector.js Completion detection (sentinel + idle + prompt-box)
|
|
839
|
+
bin/
|
|
840
|
+
cli.js ocp CLI entry point
|
|
841
|
+
sample/
|
|
842
|
+
server.js Example web server
|
|
843
|
+
public/ Chat UI
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
---
|
|
847
|
+
|
|
848
|
+
## License
|
|
849
|
+
|
|
850
|
+
MIT
|