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/README.ja.md ADDED
@@ -0,0 +1,708 @@
1
+ [English](README.md) · [한국어](README.ko.md) · [中文](README.zh.md) · **日本語**
2
+
3
+ [![npm](https://img.shields.io/npm/v/open-claude-p.svg)](https://www.npmjs.com/package/open-claude-p)
4
+ [![downloads](https://img.shields.io/npm/dm/open-claude-p.svg)](https://www.npmjs.com/package/open-claude-p)
5
+ [![stars](https://img.shields.io/github/stars/empty-user77/open-claude-p.svg)](https://github.com/empty-user77/open-claude-p/stargazers)
6
+ [![License](https://img.shields.io/npm/l/open-claude-p.svg)](https://github.com/empty-user77/open-claude-p/blob/main/LICENSE)
7
+ [![Sponsor](https://img.shields.io/badge/Sponsor-%E2%9D%A4-ec1c8a?logo=github)](https://github.com/sponsors/empty-user77)
8
+
9
+ ---
10
+
11
+ # open-claude-p (ocp)
12
+
13
+ `claude -p`(ヘッドレスプリントモード)が使用できない環境で、インタラクティブな `claude` CLI を **node-pty で直接駆動**し、同等の機能を提供する PTY ベースの互換レイヤーです。
14
+
15
+ > **核心的な違い**:`claude -p` は Claude Code の非対話モードで内部 API を通じて動作しますが、特定のプラン/環境では使用できません。`open-claude-p` は実際の TUI クライアントを PTY で実行し、出力ストリームを解析することで同じ結果を得ます。
16
+
17
+ ---
18
+
19
+ ## 目次
20
+
21
+ - [インストール](#インストール)
22
+ - [CLI の使い方](#cli-の使い方)
23
+ - [デーモン(セッション持続)](#デーモンセッション持続)
24
+ - [ライブラリ API](#ライブラリ-api)
25
+ - [createDriver()](#createdriveropts)
26
+ - [runOneShot()](#runoneshotreq)
27
+ - [戻り値:OneShotResult](#戻り値oneshotresult)
28
+ - [イベントタイプ(onEvent コールバック)](#イベントタイプonevent-コールバック)
29
+ - [セッション管理](#セッション管理)
30
+ - [JSONL セッションファイルの活用](#jsonl-セッションファイルの活用)
31
+ - [出力パースについて](#出力パースについて)
32
+ - [環境変数](#環境変数)
33
+ - [オプション全一覧](#オプション全一覧)
34
+ - [サンプルアプリ](#サンプルアプリ)
35
+
36
+ ---
37
+
38
+ ## インストール
39
+
40
+ ### npm
41
+
42
+ ```bash
43
+ # プロジェクト依存としてインストール
44
+ npm install open-claude-p
45
+
46
+ # またはどこからでも `ocp` CLI を使うためにグローバルインストール
47
+ npm install -g open-claude-p
48
+ ```
49
+
50
+ ### ソースからインストール(開発用)
51
+
52
+ ```bash
53
+ # clone 後、プロジェクトルートでシンボリックリンクを作成
54
+ git clone https://github.com/empty-user77/open-claude-p.git
55
+ cd open-claude-p
56
+ npm link
57
+
58
+ # または他のプロジェクトからローカルパスでインストール
59
+ npm install /path/to/open-claude-p
60
+ ```
61
+
62
+ **前提条件**:`claude` CLI が `PATH` にインストールされている必要があります。
63
+
64
+ ```bash
65
+ # Claude Code CLI のインストール確認
66
+ claude --version
67
+ ```
68
+
69
+ ---
70
+
71
+ ## CLI の使い方
72
+
73
+ このパッケージは単一バイナリ **`ocp`** をインストールします。
74
+
75
+ ```bash
76
+ # 基本的な使い方
77
+ ocp "こんにちは"
78
+
79
+ # claude -p と同じ argv 形式をサポート(-p フラグは互換性のため無視)
80
+ ocp -p "こんにちは"
81
+
82
+ # stdin からプロンプトを読み込む
83
+ echo "東京の天気を教えて" | ocp
84
+
85
+ # 出力フォーマットを指定
86
+ ocp --output-format json "一言で答えて:りんご"
87
+ ocp --output-format stream-json "こんにちは"
88
+
89
+ # モデルを指定
90
+ ocp --model sonnet "複雑な質問..."
91
+ ocp --model claude-opus-4-7 "アーキテクチャレビュー..."
92
+
93
+ # システムプロンプトを追加
94
+ ocp --append-system-prompt "常に日本語で答えてください" "what's the weather?"
95
+
96
+ # セッションの再開 — sessionId は stderr に出力される
97
+ SID=$(ocp "キウイとだけ言って" 2>&1 >/dev/null | grep sessionId | grep -oE '[0-9a-f-]{36}')
98
+ ocp --resume "$SID" "さっき何て言ったの?"
99
+
100
+ # または最近のセッションを自動的に継続する
101
+ ocp --continue "さっき何て言ったの?"
102
+
103
+ # 権限チェックをスキップ(自動化環境向け)
104
+ ocp --dangerously-skip-permissions "ファイルを読んで分析して"
105
+ ```
106
+
107
+ ### 出力フォーマット
108
+
109
+ #### `text`(デフォルト)
110
+
111
+ ```
112
+ こんにちは!何かお手伝いできますか?
113
+ ```
114
+
115
+ #### `json`
116
+
117
+ ```json
118
+ {
119
+ "result": "こんにちは!何かお手伝いできますか?",
120
+ "session_id": "a1b2c3d4-...",
121
+ "is_error": false,
122
+ "cost_usd": null,
123
+ "duration_ms": 4200,
124
+ "num_turns": 1
125
+ }
126
+ ```
127
+
128
+ #### `stream-json`(NDJSON)
129
+
130
+ レスポンスが 1 行ずつストリーミングされます:
131
+
132
+ ```jsonl
133
+ {"type":"system","subtype":"init","session_id":"a1b2c3d4-...","tools":[],"mcp_servers":[]}
134
+ {"type":"assistant","session_id":"a1b2c3d4-...","message":{"role":"assistant","content":[{"type":"text","text":"こんにちは!"}]}}
135
+ {"type":"result","subtype":"success","session_id":"a1b2c3d4-...","is_error":false,"duration_ms":4200}
136
+ ```
137
+
138
+ ---
139
+
140
+ ## デーモン(セッション持続)
141
+
142
+ `ocp` CLI はデフォルトで**バックグラウンドデーモン**を通じて PTY セッションを維持します。
143
+ 同じディレクトリで繰り返し呼び出した際に 2.5 秒のウォームアップ待ちが不要になり、会話コンテキストが自動的に保持されます。
144
+
145
+ ```
146
+ ocp "最初の質問" → デーモンがなければ新規起動、あれば再利用
147
+ ocp "2番目の質問" → 同じデーモンに接続、コンテキスト保持
148
+ ```
149
+
150
+ デーモンソケットは `~/.ocp/` ディレクトリに**作業ディレクトリごとに**1 つ作成されます。
151
+
152
+ ### デーモンの無効化
153
+
154
+ ```bash
155
+ OCP_NO_DAEMON=1 ocp "一度だけ実行" # デーモンなしで直接 PTY を実行
156
+ ```
157
+
158
+ デーモンを使わない方が良い場合:
159
+ - `--resume`、`--continue`、`--fork-session` フラグを使用する場合(自動的に直接モードに切り替わります)
160
+ - `--input-format=stream-json` を使用する場合
161
+ - CI/CD など隔離された環境での単発実行の場合
162
+
163
+ ### デーモン関連の環境変数
164
+
165
+ | 変数 | 説明 | デフォルト |
166
+ |------|------|-----------|
167
+ | `OCP_NO_DAEMON` | `1` に設定するとデーモンを無効化 | — |
168
+ | `OCP_DAEMON_IDLE_MS` | アイドル状態が続いた場合にデーモンを自動終了 | `600000`(10 分) |
169
+ | `OCP_MAX_DAEMONS` | 同時に維持する最大デーモン数 | `30` |
170
+
171
+ ---
172
+
173
+ ## ライブラリ API
174
+
175
+ ### `createDriver(opts?)`
176
+
177
+ ドライバーを作成します。アプリケーション全体で単一のドライバーインスタンスを共有してください。
178
+
179
+ ```js
180
+ import { createDriver } from 'open-claude-p';
181
+
182
+ const driver = createDriver({
183
+ claudeBin: 'claude', // claude バイナリのパス(デフォルト:PATH の claude)
184
+ warmupMs: 2500, // PTY 初期化待機時間(ms)
185
+ reuseWarmupMs: 200, // プールから再利用する際の待機時間(ms)
186
+ idleMs: 1500, // レスポンス完了後の無音待機(ms)
187
+ preIdleMs: 8000, // sentinel マッチング前の最小待機(ms)
188
+ maxResponseMs: 60_000, // 最大レスポンス待機時間(ms)、超過するとタイムアウト
189
+ poolSize: 0, // PTY プールサイズ(0=無効、N>0=N 個ウォームアップ維持)
190
+ poolMaxAgeMs: 600_000, // プールセッションの最大寿命(ms)
191
+ cwd: process.cwd(), // 作業ディレクトリ
192
+ env: {}, // 追加の環境変数
193
+ debug: false, // デバッグログを stderr に出力
194
+ });
195
+ ```
196
+
197
+ ### `runOneShot(req)`
198
+
199
+ 単一のプロンプトを Claude に送信し、レスポンスを待ちます。
200
+
201
+ ```js
202
+ const result = await driver.runOneShot({
203
+ prompt: '東京の現在の天気を教えてください',
204
+
205
+ // ── モデル / 動作 ────────────────────────────
206
+ model: 'sonnet', // モデル名
207
+ effort: 'high', // 'low' | 'medium' | 'high' | 'max'
208
+ thinking: 'adaptive', // 'enabled' | 'adaptive' | 'disabled'
209
+ maxTurns: 5, // 最大エージェントターン数(shim 強制)
210
+
211
+ // ── システムプロンプト ─────────────────────────
212
+ systemPrompt: 'あなたは天気の専門家です', // システムプロンプト全体を置換
213
+ appendSystemPrompt: '常に日本語で', // デフォルトプロンプトに追加
214
+
215
+ // ── 権限 / ツール ─────────────────────────────
216
+ dangerouslySkipPermissions: true, // 権限チェックをスキップ
217
+ allowedTools: ['WebSearch', 'Read'], // ツールホワイトリスト
218
+ disallowedTools: ['Bash'], // ツールブラックリスト
219
+
220
+ // ── セッション ────────────────────────────────
221
+ resume: 'a1b2c3d4-...', // 前のセッション UUID から再開
222
+ continue: false, // 最近のセッションを継続
223
+ forkSession: false, // 再開時に新しいセッション ID を作成
224
+
225
+ // ── 作業ディレクトリ ──────────────────────────
226
+ cwd: '/path/to/project',
227
+
228
+ // ── キャンセル ────────────────────────────────
229
+ abortSignal: controller.signal,
230
+
231
+ // ── リアルタイムイベントコールバック ──────────
232
+ onEvent(ev) {
233
+ // レスポンス生成中にリアルタイムで呼び出される
234
+ // 下記「イベントタイプ」セクション参照
235
+ if (ev.type === 'assistant-text') {
236
+ process.stdout.write(ev.text);
237
+ }
238
+ },
239
+ });
240
+ ```
241
+
242
+ ### 戻り値:OneShotResult
243
+
244
+ `runOneShot()` が resolve されると、以下の構造のオブジェクトを返します:
245
+
246
+ ```ts
247
+ {
248
+ // ── コア結果 ────────────────────────────────────────────────────
249
+ text: string,
250
+ // Claude の最終レスポンステキスト(TUI アーティファクト除去済み)。
251
+ // Claude が生成したままの生テキスト——markdown、HTML、コードブロックなど。
252
+ // レンダリング/パースは呼び出し側の責任。
253
+
254
+ sessionId: string | null,
255
+ // このリクエストに対応する Claude セッション UUID。
256
+ // --resume <sessionId> で会話を継続できる。
257
+ // バナーキャプチャ失敗時は ~/.claude/projects/ のファイルシステムにフォールバック。
258
+
259
+ isError: boolean,
260
+ // true = エラーまたはタイムアウトで完了
261
+
262
+ completionReason: string,
263
+ // 完了理由:
264
+ // 'sentinel' 正常完了(sentinel 文字列を検出)
265
+ // 'idle' レスポンス後の無音タイムアウト
266
+ // 'prompt-box' TUI 入力ボックスが再表示
267
+ // 'timeout' maxResponseMs を超過
268
+ // 'max-turns' maxTurns 制限に達した
269
+ // 'upstream-exited' claude プロセスが先に終了
270
+ // 'write-failed' PTY 書き込み失敗
271
+ // 'cancelled' AbortSignal でキャンセル
272
+
273
+ exitCode: number,
274
+ // 0 = 成功、1 = エラー
275
+
276
+ // ── イベント配列 ────────────────────────────────────────────────
277
+ events: Array<object>,
278
+ // パイプラインが生成した全イベント(onEvent コールバックと同じオブジェクト)。
279
+ // 下記「イベントタイプ」セクション参照。
280
+
281
+ // ── パフォーマンスメトリクス ─────────────────────────────────────
282
+ durationMs: number,
283
+ // 総経過時間(ms)
284
+
285
+ cost: { totalUsd: number | null, numTurns: number | null },
286
+ // 現在は null(PTY からコスト情報を直接取得できないため)。
287
+ // 正確なトークン/コストデータは JSONL セッションファイルから読み取ってください(下記参照)。
288
+
289
+ diagnostics: { rawBytes: number, strippedBytes: number },
290
+ // PTY から受信した生バイト数 / ANSI 除去後のバイト数
291
+ }
292
+ ```
293
+
294
+ ### イベントタイプ(onEvent コールバック)
295
+
296
+ `onEvent` コールバックと `result.events` 配列には以下のタイプのイベントが含まれます:
297
+
298
+ ```ts
299
+ // Claude がレスポンスを開始したとき(⏺ マーカー検出)
300
+ { type: 'assistant-region-entered', n: number }
301
+
302
+ // レスポンス領域が閉じたとき(hr または sentinel 検出)
303
+ { type: 'assistant-region-exited', n: number }
304
+
305
+ // レスポンステキストの 1 行(リアルタイムストリーミング)
306
+ {
307
+ type: 'assistant-text',
308
+ text: string, // 1 行のテキスト(生の markdown)
309
+ region: number // 何番目のレスポンス領域か(再開時に有用、番号が大きいほど新しい)
310
+ }
311
+
312
+ // Claude セッション UUID を検出(バナーまたは終了メッセージから)
313
+ { type: 'session-id', id: string }
314
+
315
+ // TUI スピナー(Claude が処理中であることを示す)
316
+ // label: "Searching the web...", "Reading file...", "Cogitated for 25s" など
317
+ { type: 'spinner', label: string }
318
+
319
+ // TUI 入力ボックスが画面に表示(完了シグナルの 1 つ)
320
+ { type: 'prompt-box-shown' }
321
+
322
+ // sentinel 文字列を検出(正常完了)
323
+ { type: 'sentinel' }
324
+ ```
325
+
326
+ #### イベント活用例
327
+
328
+ ```js
329
+ const result = await driver.runOneShot({
330
+ prompt: '長いドキュメントを分析して',
331
+ onEvent(ev) {
332
+ switch (ev.type) {
333
+ case 'assistant-text':
334
+ // リアルタイムストリーミング——行ごとに出力
335
+ process.stdout.write(ev.text + '\n');
336
+ break;
337
+
338
+ case 'spinner':
339
+ // スピナーラベル——ツール使用中に表示(例:"Searching the web...")
340
+ process.stderr.write(`\r⏳ ${ev.label} `);
341
+ break;
342
+
343
+ case 'session-id':
344
+ // セッション ID を早めに保存しておくとタイムアウト時も再開できる
345
+ saveSessionId(ev.id);
346
+ break;
347
+ }
348
+ },
349
+ });
350
+ ```
351
+
352
+ ---
353
+
354
+ ## セッション管理
355
+
356
+ Claude は各セッションを UUID で識別し、それを使って以前の会話を再開できます。
357
+
358
+ ```js
359
+ // 1. 最初のリクエスト — 新しいセッションを開始
360
+ const result1 = await driver.runOneShot({
361
+ prompt: 'Python でフィボナッチ数列を実装して',
362
+ });
363
+ console.log('セッション ID:', result1.sessionId);
364
+ // → "a1b2c3d4-5678-..."
365
+
366
+ // 2. セッションの再開 — 以前の会話コンテキストが保持される
367
+ const result2 = await driver.runOneShot({
368
+ prompt: 'それを再帰ではなくイテレーションで書き直して',
369
+ resume: result1.sessionId,
370
+ });
371
+
372
+ // 3. セッションのフォーク — 元のセッションを保持しながら別の方向を探る
373
+ const result3 = await driver.runOneShot({
374
+ prompt: '代わりにジェネレータバージョンを作って',
375
+ resume: result1.sessionId,
376
+ forkSession: true, // 新しい UUID を割り当て、元のセッションを保持
377
+ });
378
+ ```
379
+
380
+ ---
381
+
382
+ ## JSONL セッションファイルの活用
383
+
384
+ Claude CLI は各セッションを以下のパスに JSONL ファイルとして保存します:
385
+
386
+ ```
387
+ ~/.claude/projects/<cwd をパスとしてエンコード>/<session-uuid>.jsonl
388
+ ```
389
+
390
+ 例:`cwd` が `/Users/alice/myproject` の場合:
391
+ → `~/.claude/projects/-Users-alice-myproject/<uuid>.jsonl`
392
+
393
+ これらのファイルには PTY 出力では得られない**トークン使用量、コスト、ツール使用メタデータ**が含まれています。
394
+
395
+ ```js
396
+ import { readFile } from 'node:fs/promises';
397
+ import path from 'node:path';
398
+ import os from 'node:os';
399
+
400
+ async function readSessionMeta(sessionId, cwd = process.cwd()) {
401
+ const key = path.resolve(cwd).replace(/\//g, '-');
402
+ const filePath = path.join(os.homedir(), '.claude', 'projects', key, `${sessionId}.jsonl`);
403
+ const lines = (await readFile(filePath, 'utf8')).split('\n').filter(Boolean);
404
+
405
+ // 最後の assistant メッセージから usage を抽出
406
+ for (let i = lines.length - 1; i >= 0; i--) {
407
+ try {
408
+ const ev = JSON.parse(lines[i]);
409
+ if (ev.message?.role === 'assistant') {
410
+ const textBlock = ev.message.content?.find(c => c.type === 'text');
411
+ return {
412
+ text: textBlock?.text, // クリーンな markdown テキスト(TUI アーティファクトなし)
413
+ usage: ev.message.usage, // { input_tokens, output_tokens, cache_read_input_tokens, ... }
414
+ timestamp: ev.timestamp,
415
+ };
416
+ }
417
+ } catch {}
418
+ }
419
+ return null;
420
+ }
421
+
422
+ const meta = await readSessionMeta(result.sessionId);
423
+ // meta.usage.input_tokens → 入力トークン数
424
+ // meta.usage.output_tokens → 出力トークン数
425
+ // meta.usage.cache_read_input_tokens → キャッシュ読み取りトークン数
426
+ // meta.usage.server_tool_use.web_search_requests → Web 検索回数
427
+ ```
428
+
429
+ ### JSONL から取得できるもの
430
+
431
+ | 項目 | PTY result.text | JSONL |
432
+ |------|----------------|-------|
433
+ | レスポンステキスト | ✅(TUI アーティファクトが含まれる可能性あり) | ✅(クリーンな markdown) |
434
+ | 入力トークン数 | ❌ | ✅ |
435
+ | 出力トークン数 | ❌ | ✅ |
436
+ | キャッシュトークン数 | ❌ | ✅ |
437
+ | コスト計算 | ❌ | ✅(トークン × 単価) |
438
+ | Web 検索回数 | ❌ | ✅ |
439
+ | タイムスタンプ | ❌ | ✅ |
440
+ | ツール使用詳細 | 部分的(イベント) | ✅ |
441
+
442
+ ---
443
+
444
+ ## 出力パースについて
445
+
446
+ **`result.text` は Claude が生成した生の markdown/テキストです。**
447
+ オープンフォーマットなので、レンダリング、パース、表示方法は各プロジェクトで自分で実装してください。
448
+
449
+ ```
450
+ result.text の例:
451
+ ─────────────────────────────────────
452
+ # フィボナッチ数列
453
+
454
+ Python でフィボナッチ数列を実装する方法です:
455
+
456
+ ```python
457
+ def fib(n):
458
+ a, b = 0, 1
459
+ for _ in range(n):
460
+ a, b = b, a + b
461
+ return a
462
+ ```
463
+
464
+ - 時間計算量:O(n)
465
+ - 空間計算量:O(1)
466
+ ─────────────────────────────────────
467
+ ```
468
+
469
+ ### パース実装の参考
470
+
471
+ `sample/public/app.js` の `renderMarkdown()` 関数は Web UI 向けのパース例です。
472
+ ターゲット環境に合わせて自分で実装してください:
473
+
474
+ ```js
475
+ // Web UI → HTML レンダリング(例)
476
+ import { marked } from 'marked';
477
+ const html = marked.parse(result.text);
478
+
479
+ // ターミナル → ANSI カラーレンダリング(例)
480
+ import { renderMarkdown } from 'cli-markdown';
481
+ console.log(renderMarkdown(result.text));
482
+
483
+ // 別の LLM に渡す → そのまま使用
484
+ const nextPrompt = `前の回答:${result.text}\n\n次のステップに進んでください`;
485
+ ```
486
+
487
+ ### TUI アーティファクトについて
488
+
489
+ `result.text` は ocp が可能な限り TUI レンダリングの残留物を除去していますが、完璧ではない場合があります。
490
+ よりクリーンなテキストが必要な場合は、**JSONL セッションファイル**から読み取ることを推奨します(上記参照)。
491
+
492
+ ---
493
+
494
+ ## 環境変数
495
+
496
+ ### ドライバーオプション
497
+
498
+ | 変数 | 対応オプション | デフォルト |
499
+ |------|-------------|-----------|
500
+ | `OCP_CLAUDE_BIN` | `claudeBin` | `'claude'` |
501
+ | `OCP_WARMUP_MS` | `warmupMs` | `2500` |
502
+ | `OCP_REUSE_WARMUP_MS` | `reuseWarmupMs` | `200` |
503
+ | `OCP_IDLE_MS` | `idleMs` | `1500` |
504
+ | `OCP_PRE_IDLE_MS` | `preIdleMs` | `8000` |
505
+ | `OCP_MAX_RESPONSE_MS` | `maxResponseMs` | `60000` |
506
+ | `OCP_POOL_SIZE` | `poolSize` | `0` |
507
+ | `OCP_POOL_MAX_AGE_MS` | `poolMaxAgeMs` | `600000` |
508
+
509
+ ### デーモン(CLI のみ)
510
+
511
+ | 変数 | 説明 | デフォルト |
512
+ |------|------|-----------|
513
+ | `OCP_NO_DAEMON` | `1` に設定するとデーモンを無効化し、PTY を直接実行 | — |
514
+ | `OCP_DAEMON_IDLE_MS` | アイドル状態が続いた場合にデーモンを自動終了 | `600000` |
515
+ | `OCP_MAX_DAEMONS` | 同時に維持する最大デーモン数 | `30` |
516
+
517
+ ```bash
518
+ # レスポンスタイムアウトを 10 分に増やす
519
+ OCP_MAX_RESPONSE_MS=600000 ocp "複雑なタスク..."
520
+
521
+ # デーモンなしで単発実行
522
+ OCP_NO_DAEMON=1 ocp "一度だけ実行"
523
+ ```
524
+
525
+ ---
526
+
527
+ ## オプション全一覧
528
+
529
+ `runOneShot(req)` リクエストフィールドと CLI フラグの対応表:
530
+
531
+ | req フィールド | CLI フラグ | 型 | 説明 |
532
+ |-------------|---------|---|------|
533
+ | `model` | `--model` | string | モデル名(例:`sonnet`、`claude-sonnet-4-6`) |
534
+ | `systemPrompt` | `--system-prompt` | string | システムプロンプト全体を置換 |
535
+ | `appendSystemPrompt` | `--append-system-prompt` | string | デフォルトシステムプロンプトに追加 |
536
+ | `dangerouslySkipPermissions` | `--dangerously-skip-permissions` | boolean | 権限チェックをスキップ |
537
+ | `allowedTools` | `--allowed-tools` | string[] | ツールホワイトリスト |
538
+ | `disallowedTools` | `--disallowed-tools` | string[] | ツールブラックリスト |
539
+ | `resume` | `--resume` / `-r` | string | セッション UUID から再開 |
540
+ | `continue` | `--continue` / `-c` | boolean | 最近のセッションを継続 |
541
+ | `forkSession` | `--fork-session` | boolean | 再開時に新しいセッション ID を作成 |
542
+ | `sessionId` | `--session-id` | string | 新しいセッションに特定の UUID を指定 |
543
+ | `noSessionPersistence` | `--no-session-persistence` | boolean | セッション保存を無効化 |
544
+ | `effort` | `--effort` | enum | `low` \| `medium` \| `high` \| `max` |
545
+ | `thinking` | `--thinking` | enum | `enabled` \| `adaptive` \| `disabled` |
546
+ | `maxTurns` | `--max-turns` | number | 最大エージェントターン数 |
547
+ | `fallbackModel` | `--fallback-model` | string | プライマリモデル過負荷時のフォールバック |
548
+ | `permissionMode` | `--permission-mode` | string | `default` \| `plan` \| `acceptEdits` \| `bypassPermissions` |
549
+ | `mcpConfig` | `--mcp-config` | string[] | MCP 設定パス |
550
+ | `addDir` | `--add-dir` | string[] | ツールがアクセスできる追加ディレクトリ |
551
+ | `bare` | `--bare` | boolean | 最小モード(hooks、LSP、プラグインなどを無効化) |
552
+ | `debug` | `--debug` | boolean | デバッグログを stderr に出力 |
553
+ | `verbose` | `--verbose` | boolean | 詳細出力 |
554
+ | `cwd` | `--cwd` | string | PTY プロセスの作業ディレクトリ |
555
+ | `abortSignal` | — | AbortSignal | キャンセルシグナル |
556
+ | `onEvent` | — | function | リアルタイムイベントコールバック |
557
+ | `passThroughArgv` | — | string[] | claude にそのまま渡す追加 argv |
558
+
559
+ ---
560
+
561
+ ## サンプルアプリ
562
+
563
+ `sample/` ディレクトリには ocp を使って構築した Web ベースのチャット UI が含まれています。
564
+
565
+ ### 実行
566
+
567
+ ```bash
568
+ cd sample
569
+ node server.js
570
+ # → http://localhost:3000
571
+ ```
572
+
573
+ ### サンプルアプリの構造
574
+
575
+ ```
576
+ sample/
577
+ server.js Express サーバー — ocp ドライバーをラップ、SSE ストリーミング
578
+ data/
579
+ conversations.json 会話履歴(自動生成)
580
+ public/
581
+ index.html チャット UI
582
+ app.js クライアントサイド JavaScript
583
+ style.css スタイルシート
584
+ ```
585
+
586
+ ### サンプルサーバー API
587
+
588
+ | エンドポイント | メソッド | 説明 |
589
+ |------------|--------|------|
590
+ | `/api/conversations` | GET | 会話一覧 |
591
+ | `/api/conversations/:id` | GET | 会話詳細(全メッセージ) |
592
+ | `/api/conversations/:id` | DELETE | 会話を削除 |
593
+ | `/api/chat` | POST | メッセージ送信(SSE ストリーミング) |
594
+ | `/api/monitor` | GET | PTY イベントモニター(SSE) |
595
+ | `/api/skills` | GET | `~/.claude/skills/` のスキル一覧 |
596
+ | `/api/processes` | GET | 進行中のリクエスト一覧(`id`、`prompt`、`elapsedMs`) |
597
+ | `/api/processes/:id` | DELETE | 特定のリクエストを中断(`all` で全て中断) |
598
+
599
+ ### `/api/chat` SSE イベント
600
+
601
+ チャットリクエスト(`POST /api/chat`)は Server-Sent Events でレスポンスをストリーミングします:
602
+
603
+ ```js
604
+ // クライアントリクエスト
605
+ const resp = await fetch('/api/chat', {
606
+ method: 'POST',
607
+ headers: { 'Content-Type': 'application/json' },
608
+ body: JSON.stringify({
609
+ message: '東京の天気を教えて',
610
+ conversationId: null, // null で新しい会話を開始
611
+ skillName: 'my-skill', // オプション:~/.claude/skills/ のスキル名
612
+ }),
613
+ });
614
+
615
+ // SSE イベントの種類
616
+ { type: 'spinner', label: 'Searching the web...' } // 処理中の状態
617
+ { type: 'text', text: 'こんにちは...' } // ストリーミングテキスト(チャンク)
618
+ { type: 'error', error: 'エラーメッセージ' } // エラー
619
+ {
620
+ type: 'done',
621
+ conversationId: 'uuid', // 会話 ID(保存済み)
622
+ text: '完全な最終レスポンス', // 完全な最終テキスト(JSONL からのクリーンな markdown)
623
+ isNew: true, // 新しい会話かどうか
624
+ meta: {
625
+ elapsedMs: 4200, // 経過時間(ms)
626
+ inputTokens: 1500, // 入力トークン(キャッシュ含む)
627
+ outputTokens: 320, // 出力トークン
628
+ costUsd: 0.0042, // コスト(USD)
629
+ tools: ['WebSearch'], // 使用したツール
630
+ }
631
+ }
632
+ ```
633
+
634
+ ### サンプルの Markdown パース
635
+
636
+ サンプルアプリ(`sample/public/app.js`)は `renderMarkdown()` 関数を通じて `result.text` を HTML に変換します。
637
+
638
+ **このパースコードはサンプル専用です。** 実際のプロジェクトでは:
639
+ - Web:`marked`、`markdown-it` など
640
+ - ターミナル:`cli-markdown`、`terminal-link` など
641
+ - React:`react-markdown`
642
+ - LLM 入力:そのまま使用
643
+
644
+ ### プロセスマネージャー(`ocp-ps`)
645
+
646
+ サンプルアプリには `/api/processes` API を使って進行中のリクエストを一覧表示・キャンセルできる CLI ツールが含まれています。
647
+
648
+ ```bash
649
+ cd sample
650
+
651
+ node ocp-ps.js # 進行中のリクエスト一覧
652
+ node ocp-ps.js kill <id> # 特定のリクエストを中断
653
+ node ocp-ps.js kill all # 全て中断
654
+ node ocp-ps.js watch # 1 秒ごとに自動更新
655
+ ```
656
+
657
+ > **注意**:`ocp-ps` はサンプルアプリの HTTP API(`/api/processes`)を使うサンプル実装です。
658
+ > ocp ライブラリを使って独自のサーバーを構築する際は、同じパターンでプロセス管理 API を実装できます。
659
+
660
+ ### スキル呼び出し(`/スキル名`)
661
+
662
+ チャット入力欄で `/` を入力すると `~/.claude/skills/` のスキルがドロップダウンで表示されます。
663
+
664
+ ```
665
+ ユーザー入力:/my-skill この PRD を分析して関連リポジトリを見つけて
666
+
667
+ サーバー:SKILL.md の内容を appendSystemPrompt として注入
668
+
669
+ Claude:スキルの指示に従って実行
670
+ ```
671
+
672
+ ---
673
+
674
+ ## モジュール構造
675
+
676
+ ```
677
+ src/
678
+ index.js ライブラリ公開 API(createDriver、runOneShot)
679
+ options/
680
+ spec.js 全オプション定義(単一ソース)
681
+ parse-argv.js CLI argv パーサー
682
+ validate.js クロスオプション検証
683
+ parsers/
684
+ ansi-strip.js ANSI エスケープ除去
685
+ tui-frame.js TUI フレームパーサー(イベント生成)
686
+ sentinel.js 完了 sentinel 検出
687
+ pipeline.js パーサーパイプライン合成
688
+ output/
689
+ text.js --output-format text アダプター
690
+ json.js --output-format json アダプター
691
+ stream-json.js --output-format stream-json アダプター
692
+ pty/
693
+ session.js 単一 PTY セッションのライフサイクル
694
+ pool.js ウォームアップ済み PTY プール
695
+ completion/
696
+ detector.js 完了検出(sentinel + idle + prompt-box)
697
+ bin/
698
+ cli.js ocp CLI エントリーポイント
699
+ sample/
700
+ server.js サンプル Web サーバー
701
+ public/ チャット UI
702
+ ```
703
+
704
+ ---
705
+
706
+ ## ライセンス
707
+
708
+ MIT