setup-cc-room 0.2.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.
@@ -0,0 +1,475 @@
1
+ ---
2
+ name: room
3
+ description: 会議室の作成・参加・退出・Primary 切替・ステータス表示・メモを行う統合コマンド。
4
+ ---
5
+
6
+ 会議室に関する全ての操作を行う統合コマンド。
7
+
8
+ ## ポートの取得
9
+
10
+ すべての HTTP リクエスト前に、以下でポートを取得する:
11
+
12
+ ```bash
13
+ CC_ROOM_HOME="${CC_ROOM_HOME:-$HOME/.cc-room}"
14
+ HTTP_PORT=$(grep 'http_port:' "$CC_ROOM_HOME/config.yaml" 2>/dev/null | awk '{print $2}' | tr -d '[:space:]')
15
+ HTTP_PORT="${HTTP_PORT:-7332}"
16
+ BASE_URL="http://127.0.0.1:$HTTP_PORT"
17
+ ```
18
+
19
+ 以降の手順で `http://127.0.0.1:7332` とある箇所はすべて `$BASE_URL` に置き換える。
20
+
21
+ ## サブコマンド
22
+
23
+ ### `/room`(引数なし)— ホワイトボードを見る
24
+
25
+ 1. `BASE_URL` を取得する(上記参照)。
26
+ 2. 未読通知を確認して表示し、既読にマークする:
27
+ ```bash
28
+ NOTIF_FILE="$CC_ROOM_HOME/notifications.jsonl"
29
+ LAST_READ_FILE="$CC_ROOM_HOME/notifications_last_read"
30
+ LAST_READ=$(cat "$LAST_READ_FILE" 2>/dev/null || echo "")
31
+ if [ -f "$NOTIF_FILE" ]; then
32
+ # LAST_READ より新しい行(未読)を抽出して表示
33
+ UNREAD=$(awk -v last="$LAST_READ" '
34
+ { ts = substr($0, 8, 24); if (ts > last) print }
35
+ ' "$NOTIF_FILE")
36
+ if [ -n "$UNREAD" ]; then
37
+ echo "🔔 未読通知:"
38
+ echo "$UNREAD" | python3 -c '
39
+ import sys, json
40
+ for line in sys.stdin:
41
+ line = line.strip()
42
+ if not line: continue
43
+ try:
44
+ d = json.loads(line)
45
+ t = d.get("type")
46
+ if t == "join":
47
+ print(f" 👋 {d.get(\"identity\", \"\")} が {d.get(\"room\", \"\")} に入室しました")
48
+ elif t == "leave":
49
+ print(f" 🚪 {d.get(\"identity\", \"\")} が {d.get(\"room\", \"\")} から退出しました")
50
+ elif t == "message":
51
+ print(f" 💬 {d.get(\"from\", \"\")}: {d.get(\"content\", \"\")[:80]}")
52
+ elif t == "room_closed":
53
+ print(f" 🔒 会議室 {d.get(\"room\", \"\")} が閉じられました")
54
+ elif t == "file_received":
55
+ print(f" 📎 {d.get(\"from\", \"\")} が {d.get(\"filename\", \"\")} を共有しました")
56
+ elif t == "dream_proposals":
57
+ print(f" 📋 チームへの提案 {d.get(\"count\", 0)} 件 ({d.get(\"room\", \"\")})")
58
+ elif t == "dream_merged":
59
+ print(f" 🧠 チームの記憶が更新されました ({d.get(\"room\", \"\")})")
60
+ except Exception:
61
+ pass
62
+ '
63
+ echo ""
64
+ fi
65
+ # 既読マーク: 最終行の ts を保存
66
+ LATEST_TS=$(tail -1 "$NOTIF_FILE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('ts',''))" 2>/dev/null)
67
+ [ -n "$LATEST_TS" ] && echo "$LATEST_TS" > "$LAST_READ_FILE"
68
+ fi
69
+ ```
70
+ 3. `GET $BASE_URL/status` でルーム一覧を取得する。
71
+ 4. `GET $BASE_URL/context` で全メンバーのサマリーを取得する。
72
+ 5. `GET $BASE_URL/messages` でメッセージ一覧を取得する。
73
+ 6. 以下の形式で表示する:
74
+
75
+ ```
76
+ 🏠 <room_name> ★ Primary / 👀 Watch 🔒 Private ON / 📡 公開中
77
+ メンバー: <member1>, <member2>, ...
78
+ 通知: ON(🔔)/ OFF(🔕)
79
+
80
+ 📋 ホワイトボード:
81
+ (context にサマリーがあれば表示)
82
+ <member1>: <summary>
83
+
84
+ 💬 メッセージ:
85
+ (messages に内容があれば新しい順に表示)
86
+ [時刻] <from>: <content>
87
+ ```
88
+
89
+ 各ルームの role / Private は `GET $BASE_URL/status` の `rooms[]` から表示する:
90
+ ```bash
91
+ curl -s "$BASE_URL/status" | python3 -c '
92
+ import sys, json
93
+ d = json.load(sys.stdin)
94
+ primary = d.get("primary_room_name") or "(なし)"
95
+ priv = "🔒 Private ON" if d.get("private") else "📡 公開中"
96
+ print(f"Primary: {primary} {priv}")
97
+ for r in d.get("rooms", []):
98
+ mark = "★" if r.get("role") == "primary" else "👀"
99
+ print(f" {mark} {r[\"name\"]} ({r.get(\"role\", \"watch\")})")
100
+ '
101
+ ```
102
+
103
+ 通知状態は `~/.cc-room/config.yaml` の `notifications.enabled` を参照して表示する:
104
+ ```bash
105
+ CC_ROOM_HOME="${CC_ROOM_HOME:-$HOME/.cc-room}"
106
+ NOTIFY_ENABLED=$(python3 -c "
107
+ import sys
108
+ enabled = True
109
+ try:
110
+ with open('$CC_ROOM_HOME/config.yaml') as f:
111
+ in_sect = False
112
+ for line in f:
113
+ if line.startswith('notifications:'):
114
+ in_sect = True
115
+ elif in_sect and line.strip().startswith('enabled:'):
116
+ enabled = 'false' not in line.lower()
117
+ break
118
+ elif in_sect and line.strip() and not line.startswith(' '):
119
+ in_sect = False
120
+ except Exception:
121
+ pass
122
+ print('ON' if enabled else 'OFF')
123
+ " 2>/dev/null || echo "ON")
124
+ [ "$NOTIFY_ENABLED" = "ON" ] && echo "通知: ON 🔔" || echo "通知: OFF 🔕"
125
+ ```
126
+
127
+ 7. ルームに参加していない場合:
128
+ 「ルームに参加していません。`/room open <名前>` で作成するか `/room join` で参加してください。」
129
+
130
+ ### `/room open <name> [--quiet] [--dream-mine=...] [--dream-threshold=N] [--dream-silent=on|off]` — 会議室を開く
131
+
132
+ 1. `--quiet` がある場合は `"quiet": true`、なければ `false`。
133
+ 2. dream オプションがある場合、`dream` オブジェクトを body に含める(ルーム作成時のみ設定可):
134
+ - `--dream-mine=every_stop|threshold|manual_only` → `"mine_trigger"`
135
+ - `--dream-threshold=N` → `"session_threshold": N`(threshold 時)
136
+ - `--dream-silent=on|off` → `"silent_merge": true/false`
137
+ 3. `POST $BASE_URL/room/create` に以下を送信する:
138
+ ```json
139
+ { "name": "<name>", "quiet": false, "dream": { "mine_trigger": "every_stop" } }
140
+ ```
141
+ `dream` は省略可(グローバル default を使用)。
142
+ 4. 成功した場合:
143
+ ```
144
+ ✅ 会議室 '<name>' を開きました
145
+ PIN: <6桁の数字>
146
+
147
+ チームメイトに部屋名と PIN を伝えてください。
148
+ /room join <name> <PIN> で参加できます。
149
+
150
+ ★ この部屋が Primary(執筆するルーム)になります
151
+ 📡 公開中 / 🔒 Private ON(レスポンスの private に従う)
152
+
153
+ チームメモリ設定:
154
+ <dream_summary>
155
+ ```
156
+
157
+ オプション:
158
+ - `--quiet`: 入室時から Private ON(手元モード)
159
+ - `--dream-mine=...`: Mine トリガー(every_stop / threshold / manual_only)
160
+ - `--dream-threshold=N`: threshold 時の Stop 回数
161
+ - `--dream-silent=on|off`: 72h サイレントマージ ON/OFF
162
+
163
+ ※ `--keep session|ttl|permanent` は将来対応予定
164
+
165
+ ### `/room join [name] [pin] [--quiet]` — 会議室に入る
166
+
167
+ 引数なしの場合:
168
+ 1. `GET $BASE_URL/room/discover` で LAN 上の部屋一覧を取得する。
169
+ 2. 一覧を表示:
170
+ ```
171
+ 📡 LAN 上の部屋:
172
+ • <room_name> (hosted by <identity>, <N>人)
173
+
174
+ /room join <名前> <PIN> で参加
175
+ ```
176
+
177
+ 引数ありの場合:
178
+ 1. `POST $BASE_URL/room/join` に `{ "name": "<name>", "pin": "<pin>", "quiet": false }` を送信する(`--quiet` 時は `true`)。
179
+ 2. 成功した場合、レスポンスの `role` に応じて表示する:
180
+ - `role: "primary"`(最初のルーム):
181
+ ```
182
+ ✅ '<name>' に入室しました
183
+ ★ この部屋が Primary(執筆するルーム)になります
184
+ ホワイトボードの内容を受信中...
185
+ ```
186
+ - `role: "watch"`(2 部屋目以降):
187
+ ```
188
+ ✅ '<name>' に Watch(見るだけ)で入室しました
189
+ 執筆するには /room switch <name> で Primary を切り替えてください
190
+ ```
191
+
192
+ ### `/room leave` — 会議室を出る
193
+
194
+ 1. `GET $BASE_URL/status` でルーム一覧を取得する。
195
+ 2. ホストで他メンバーがいる場合:
196
+ 「ゲストが接続中です。[引き継いで退出 / 全員退出で閉鎖]」
197
+ 3. `POST $BASE_URL/leave` に `{ "room_id": "<id>" }` を送信する。
198
+ 4. 成功した場合: 「会議室を退出しました。」
199
+
200
+ ### `/room pending` — 承認待ちファイル一覧
201
+
202
+ 共有を受信したが、まだ承認していないファイルの一覧を表示する。
203
+
204
+ 1. `GET $BASE_URL/room/pending` を取得する。
205
+ 2. 以下の形式で表示する:
206
+ ```
207
+ 📥 承認待ちファイル:
208
+ [<id>] <type>: <filename>
209
+ from: <送信者>
210
+ 保存先: <save_path(フルパス)>
211
+ ```
212
+ 一覧が空の場合: 「承認待ちのファイルはありません」
213
+
214
+ ### `/room accept <id>` — 共有ファイルを承認
215
+
216
+ 承認待ちのファイルを受け入れ、指定された保存先に書き込む。
217
+
218
+ 1. `POST $BASE_URL/room/accept` に以下を送信する:
219
+ ```json
220
+ { "pending_id": "<id>" }
221
+ ```
222
+ 2. 成功した場合:
223
+ ```
224
+ ✅ <filename> を承認しました
225
+ 保存先: <save_path(フルパス)>
226
+ ```
227
+ `claude_md` タイプの場合: 「Room-scoped に保存されました。グローバルに追加するには /room adopt を実行してください」
228
+
229
+ ### `/room reject <id>` — 共有ファイルを拒否
230
+
231
+ 承認待ちのファイルを破棄する。
232
+
233
+ 1. `POST $BASE_URL/room/reject` に以下を送信する:
234
+ ```json
235
+ { "pending_id": "<id>" }
236
+ ```
237
+ 2. 成功した場合: 「🗑 <id> を破棄しました」
238
+
239
+ ### `/room adopt` — Room-scoped CLAUDE.md をグローバルへ昇格
240
+
241
+ 現在の部屋の CLAUDE.md を `~/.claude/CLAUDE.md` に追記する。
242
+
243
+ 1. `GET $BASE_URL/status` で現在の room_id を取得する。
244
+ 2. `POST $BASE_URL/room/adopt` に以下を送信する:
245
+ ```json
246
+ { "room_id": "<room_id>" }
247
+ ```
248
+ 3. 成功した場合:
249
+ ```
250
+ ✅ Room-scoped CLAUDE.md を ~/.claude/CLAUDE.md に追記しました
251
+ パス: <path>
252
+ ```
253
+
254
+ ### `/room notify on` / `/room notify off` — 通知を切り替える
255
+
256
+ 自分の OS 通知を有効化・無効化する。設定は `~/.cc-room/config.yaml` に永続保存される。
257
+
258
+ 1. `POST $BASE_URL/notify/toggle` に以下を送信する:
259
+ - `on` の場合: `{ "enabled": true }`
260
+ - `off` の場合: `{ "enabled": false }`
261
+ 2. 成功した場合:
262
+ - `on`: 「🔔 通知を有効にしました」
263
+ - `off`: 「🔕 通知を無効にしました」
264
+
265
+ ### `/room switch <name>` — Primary(執筆するルーム)を切り替える
266
+
267
+ サマリー・成果物・Dream の対象ルームを切り替える。旧 Primary は Watch に降格する。
268
+
269
+ 1. `POST $BASE_URL/room/switch` に以下を送信する:
270
+ ```json
271
+ { "name": "<name>" }
272
+ ```
273
+ 2. 成功した場合:
274
+ ```
275
+ ★ Primary: <name>(旧 Primary は Watch になりました)
276
+ ```
277
+ 3. 失敗(未参加の部屋名): 「部屋 '<name>' が見つかりません」
278
+
279
+ ```bash
280
+ curl -s -X POST "$BASE_URL/room/switch" \
281
+ -H "Content-Type: application/json" \
282
+ -d "{\"name\":\"<name>\"}" | python3 -m json.tool
283
+ ```
284
+
285
+ ### `/room status` — Primary / Private / チームメモリ設定を確認
286
+
287
+ 各ルームの role(Primary/Watch)、Private 状態、effective dream 設定を一覧表示する。
288
+
289
+ 1. `GET $BASE_URL/status` を取得する。
290
+ 2. 以下の形式で表示する:
291
+
292
+ ```
293
+ Primary: <primary_room_name> 📡 公開中 / 🔒 Private ON
294
+
295
+ ★ auth-feature (primary)
296
+ mine: threshold (20 セッションごと) / silent ON / 公開中のみ Mine
297
+ 👀 side-project (watch)
298
+ mine: every_stop / silent OFF / 公開中のみ Mine
299
+ ```
300
+
301
+ `rooms[].dream` は effective 設定(グローバル default + ルーム override 解決済み)。主要フィールド:
302
+ - `mine_trigger` — `every_stop` / `threshold` / `manual_only`
303
+ - `session_threshold` — threshold 時の Stop 回数
304
+ - `silent_merge` — 72h サイレントマージ ON/OFF
305
+ - `require_show_on` — 公開中のみ Mine
306
+
307
+ 実装例:
308
+ ```bash
309
+ curl -s "$BASE_URL/status" | python3 -c '
310
+ import sys, json
311
+ d = json.load(sys.stdin)
312
+ if not d.get("rooms"):
313
+ print("ルームに参加していません")
314
+ sys.exit(0)
315
+ primary = d.get("primary_room_name") or "(未設定 — /room switch <name>)"
316
+ priv = "🔒 Private ON" if d.get("private") else "📡 公開中"
317
+ print(f"Primary: {primary} {priv}\n")
318
+
319
+ def dream_line(cfg):
320
+ if not cfg:
321
+ return ""
322
+ mine = cfg.get("mine_trigger", "?")
323
+ if mine == "threshold":
324
+ threshold = cfg.get("session_threshold", "?")
325
+ mine = f"threshold ({threshold} セッションごと)"
326
+ silent = "ON" if cfg.get("silent_merge") else "OFF"
327
+ show_req = "公開中のみ Mine" if cfg.get("require_show_on") else "常に Mine"
328
+ return f" mine: {mine} / silent {silent} / {show_req}"
329
+
330
+ for r in d.get("rooms", []):
331
+ mark = "★" if r.get("role") == "primary" else "👀"
332
+ print(f" {mark} {r[\"name\"]} ({r.get(\"role\", \"watch\")})")
333
+ line = dream_line(r.get("dream"))
334
+ if line:
335
+ print(line)
336
+ '
337
+ ```
338
+
339
+ ### `@name <message>` / `@here <message>` / `@all <message>` — チームメイトにメンション
340
+
341
+ 通常の Claude Code 入力欄で `@` から始まるメッセージを送ると、Claude ではなく人間に届く。**このコマンドは Claude に指示するのではなく、ユーザーが直接入力するもの**。
342
+
343
+ | 構文 | 届く先 |
344
+ |---|---|
345
+ | `@akira JWT終わったよ` | akira だけに届く |
346
+ | `@here ランチ行く人いる?` | 公開中(Private OFF)のメンバー全員(今この場にいる人)|
347
+ | `@all デプロイ完了しました` | 部屋の全員(Private ON/OFF 問わず) |
348
+
349
+ 日常的なやりとりは `@here` が自然。`@all` は強制割り込みになるため緊急時に使う。
350
+
351
+ **内部動作(UserPromptSubmit Hook が処理)**:
352
+ 1. `@` プレフィックスを検出
353
+ 2. 部屋のメンバーリストと照合:
354
+ - `@name` → メンバーに存在する場合のみ mention 送信。存在しない名前(`@dataclass` 等)は通常入力として Claude に渡す
355
+ - `@here` / `@all` → 予約語のため常に mention 送信
356
+ - 大文字小文字無視(`@Akira` = `@akira`)
357
+ 3. 自分の公開状態に応じて context_summary を決定:
358
+ - 公開中(Primary かつ Private OFF)→ 直近サマリーを `context_summary` として同梱
359
+ - Private ON または Watch ルーム → `context_summary` は省略
360
+ 4. WebSocket 経由で宛先の daemon に `mention` メッセージを送信
361
+ 5. プロンプトはそのまま Claude にも渡る(`@name` 付きの文として Claude が解釈できる)
362
+
363
+ **受信側**:
364
+ - `mentions.jsonl` にメンションが追記される
365
+ - ステータスラインに `📬 N件` が表示される
366
+ - 次に Claude に話しかけた際、`<cc-room-context>` タグ付きバナーとしてプロンプト先頭に挿入され、その時点で既読になる
367
+
368
+ 未読メンションを今すぐ確認したい場合:
369
+ 1. `GET $BASE_URL/unread` で未読メンション一覧を取得する。
370
+ 2. 以下の形式で表示する:
371
+ ```
372
+ 📬 未読メンション (<N>件):
373
+ [<時刻>] <from>: <content>
374
+ 状況: <context_summary> ← 送信者が公開中(Private OFF)のときのみ表示
375
+ ```
376
+ 未読なし: 「📭 未読メンションはありません」
377
+
378
+ ### `/room remember <content>` — 付箋を貼る(チームメモリ)
379
+
380
+ チームメモリに即時共有する(Private 状態に関係なく常に即時配信)。v0.1 以降は `room-memory/` に `.md` 生成 + `MEMORY.md` 索引更新。現行は `memory.md` への追記(後方互換)。
381
+
382
+ 1. `POST $BASE_URL/memory` に `{ "content": "<content>" }` を送信する。
383
+ 2. レスポンスの `message` フィールドをそのままユーザーに表示する(件数つき確認メッセージ)。
384
+
385
+ ### `/room dream` — チームメモリ(自動整理・提案・マージ)
386
+
387
+ ユーザー向けには「Dream」と言わず **「チームメモリ」** と伝える。サブコマンドで操作する。
388
+
389
+ #### `/room dream status` — 設定と保留中の提案を確認
390
+
391
+ 1. `GET $BASE_URL/dream/config`(必要なら `?room_id=`)で effective 設定を取得。
392
+ 2. `GET $BASE_URL/dream/pending` で自分の保留提案一覧を取得。
393
+ 3. 以下の形式で表示:
394
+
395
+ ```
396
+ 📋 チームメモリ設定(<room_name>):
397
+ mine_trigger: threshold (20 セッションごと)
398
+ silent_merge: ON(72h 異議申し立て)
399
+ require_show_on: ON(公開中のみ Mine)
400
+
401
+ あなたの保留提案: <N> 件
402
+ 1. [decision] JWT TTL を 1 日に短縮(期限: <objection_deadline>)
403
+ 2. ...
404
+ ```
405
+
406
+ #### `/room dream objection [番号|slug]` — 自分の提案を保留(マージ停止)
407
+
408
+ 1. 引数なし: 保留提案が 1 件ならそれを対象。複数なら一覧を出して選ばせる。
409
+ 2. 番号 `N` または `slug` を指定。
410
+ 3. `POST $BASE_URL/dream/objection` に送信:
411
+ ```json
412
+ { "proposal_slug": "<slug>", "reason": "まだ早い" }
413
+ ```
414
+ 4. レスポンスの `message` を表示(例: ⏸ 提案を保留しました)。
415
+
416
+ #### `/room dream hold [番号|slug]` — 異議申し立て期限を 72h 延長
417
+
418
+ 1. 対象提案を特定(status と同様)。
419
+ 2. `POST $BASE_URL/dream/hold` に `{ "proposal_slug": "<slug>" }` を送信。
420
+ 3. レスポンスの `message` を表示。
421
+
422
+ #### `/room dream revert` — 直近のサイレントマージを取り消し
423
+
424
+ 1. `POST $BASE_URL/dream/revert` に `{}` または `{ "room_id": "<id>" }` を送信。
425
+ 2. レスポンスの `message` を表示(72h 以内の直近マージのみ)。
426
+
427
+ #### `/room dream mine` — 手動で知見候補を抽出(v0.2 手動 accept フロー)
428
+
429
+ 直近セッションから候補を抽出し、ユーザーが選んで即時反映する(サイレントマージなし)。
430
+
431
+ 1. `POST $BASE_URL/dream` を実行。
432
+ 2. レスポンスの `candidates` を番号付きリストで表示。
433
+ 3. 採用する項目を選んだら `POST $BASE_URL/dream/accept` に `{ "indices": [0, 2] }`(全件なら `{}`)。
434
+ 4. レスポンスの `message` を表示。
435
+
436
+ MCP: 詳細検索 `room_memory_search(query)`、L2 原典 `room_memory_trace(entry_name)`。
437
+
438
+ ### `/room config dream [key=value ...]` — ルームの Mine/マージ設定(ホストのみ)
439
+
440
+ Primary ルームの dream 設定を変更する。ホスト以外はエラー。
441
+
442
+ 1. `GET $BASE_URL/dream/config` で現在値を表示。
443
+ 2. 変更がある場合、`POST $BASE_URL/dream/config` にパッチを送信:
444
+
445
+ | ユーザー入力 | JSON フィールド | 値 |
446
+ |---|---|---|
447
+ | `mine=every_stop` | `mine_trigger` | `every_stop` |
448
+ | `mine=threshold` | `mine_trigger` | `threshold` |
449
+ | `mine=manual` | `mine_trigger` | `manual_only` |
450
+ | `threshold=10` | `session_threshold` | `10` |
451
+ | `silent=on` | `silent_merge` | `true` |
452
+ | `silent=off` | `silent_merge` | `false` |
453
+ | `public=on` | `require_show_on` | `true`(公開中のみ Mine) |
454
+ | `public=off` | `require_show_on` | `false`(常に Mine) |
455
+
456
+ 例: `POST $BASE_URL/dream/config` `{ "mine_trigger": "every_stop", "silent_merge": false }`
457
+
458
+ 3. レスポンスの `summary` と `message` を表示。
459
+
460
+ **対話例:**
461
+
462
+ ```
463
+ 現在の設定(auth-feature):
464
+ mine_trigger: threshold (20 セッションごと)
465
+ silent_merge: ON(72h 異議申し立て)
466
+ require_show_on: ON(公開中のみ Mine)
467
+
468
+ /room config dream mine=every_stop
469
+ → ✅ auth-feature のチームメモリ設定を更新しました
470
+ ```
471
+
472
+ ## エラー処理
473
+
474
+ - daemon が起動していない場合:
475
+ 「cc-room daemon が起動していません。`cc-room-daemon` を起動してください。」
@@ -0,0 +1,149 @@
1
+ ---
2
+ name: show
3
+ description: ホワイトボードへの明示投稿と、スキル・コマンド・CLAUDE.md の共有を行う。手元/公開の切替は /private。
4
+ ---
5
+
6
+ 会議室のホワイトボードへ明示的に書き込むコマンド。**手元/公開の切替は `/private` を使う**(旧 `/show` トグルは廃止)。
7
+
8
+ ## ポートの取得
9
+
10
+ すべての HTTP リクエスト前に、以下でポートを取得する:
11
+
12
+ ```bash
13
+ CC_ROOM_HOME="${CC_ROOM_HOME:-$HOME/.cc-room}"
14
+ HTTP_PORT=$(grep 'http_port:' "$CC_ROOM_HOME/config.yaml" 2>/dev/null | awk '{print $2}' | tr -d '[:space:]')
15
+ HTTP_PORT="${HTTP_PORT:-7332}"
16
+ BASE_URL="http://127.0.0.1:$HTTP_PORT"
17
+ ```
18
+
19
+ ## サブコマンド
20
+
21
+ ### `/show`(引数なし)— 非推奨
22
+
23
+ 旧トグルは廃止。以下を案内する:
24
+
25
+ ```
26
+ 手元/公開の切替は /private を使ってください:
27
+ /private on — 手元モード(作業を見せない)
28
+ /private off — 公開に戻る(溜まった作業は share/drop を選択)
29
+ メッセージ投稿は /show "メッセージ" です。
30
+ ```
31
+
32
+ ### `/show <message>` — メッセージ投稿
33
+
34
+ **Primary ルーム**のホワイトボードにメッセージを書く。
35
+
36
+ 1. `GET $BASE_URL/status` で `private` を確認する。
37
+ 2. Private ON の場合は必ずユーザーに確認する:
38
+ 「手元モード中です。このメッセージは Primary(<primary_room_name>)に公開されます。送りますか?」
39
+ → 承諾された場合のみ続行。
40
+ 3. `POST $BASE_URL/share` に以下を送信する:
41
+ ```json
42
+ { "message": "<message>" }
43
+ ```
44
+ (Primary ルームにのみ投稿される)
45
+ 4. 成功した場合:
46
+ ```
47
+ 📡 [<room_name>] <message>
48
+ ```
49
+
50
+ ### `/show skill <name>` — スキル共有
51
+
52
+ 自分のスキルファイルを部屋全員に共有する(受信側は承認が必要)。
53
+
54
+ 1. ポートを取得する(上記参照)。
55
+ 2. スキルファイルを読み込む:
56
+ ```bash
57
+ SKILL_NAME="<name>"
58
+ SKILL_BASE="${SKILL_NAME%.md}"
59
+ SKILL_FILE=""
60
+ CC_CLAUDE_HOME="${CC_CLAUDE_HOME:-$HOME/.claude}"
61
+ for dir in "$CC_CLAUDE_HOME/skills" "$CC_CLAUDE_HOME/commands"; do
62
+ if [ -f "$dir/$SKILL_BASE.md" ]; then
63
+ SKILL_FILE="$dir/$SKILL_BASE.md"
64
+ break
65
+ elif [ -f "$dir/$SKILL_BASE" ]; then
66
+ SKILL_FILE="$dir/$SKILL_BASE"
67
+ break
68
+ fi
69
+ done
70
+ if [ -z "$SKILL_FILE" ]; then
71
+ echo "エラー: スキル '$SKILL_NAME' が見つかりません"
72
+ exit 1
73
+ fi
74
+ SKILL_CONTENT=$(cat "$SKILL_FILE")
75
+ ```
76
+ 3. `POST $BASE_URL/show/file` に以下を送信する:
77
+ ```json
78
+ { "share_type": "skill", "filename": "<name>.md", "content": "<file_content>" }
79
+ ```
80
+ 4. 成功した場合:
81
+ ```
82
+ 📤 スキル '<name>' を共有しました(受信側は /room accept で承認が必要です)
83
+ ```
84
+
85
+ ### `/show command <name>` — コマンド共有
86
+
87
+ 自分のコマンドファイルを部屋全員に共有する(受信側は承認が必要)。
88
+
89
+ 1. ポートを取得する(上記参照)。
90
+ 2. コマンドファイルを読み込む:
91
+ ```bash
92
+ CMD_NAME="<name>"
93
+ CMD_BASE="${CMD_NAME%.md}"
94
+ CMD_FILE=""
95
+ for dir in "$HOME/.claude/commands"; do
96
+ if [ -f "$dir/$CMD_BASE.md" ]; then CMD_FILE="$dir/$CMD_BASE.md"; break; fi
97
+ done
98
+ if [ -z "$CMD_FILE" ]; then
99
+ echo "エラー: コマンド '$CMD_NAME' が見つかりません"
100
+ exit 1
101
+ fi
102
+ CMD_CONTENT=$(cat "$CMD_FILE")
103
+ ```
104
+ 3. `POST $BASE_URL/show/file` に以下を送信する:
105
+ ```json
106
+ { "share_type": "command", "filename": "<name>.md", "content": "<file_content>" }
107
+ ```
108
+ 4. 成功した場合:
109
+ ```
110
+ 📤 コマンド '<name>' を共有しました(受信側は /room accept で承認が必要です)
111
+ ```
112
+
113
+ ### `/show claude-md` — CLAUDE.md 共有
114
+
115
+ 自分のプロジェクトの CLAUDE.md を部屋全員に共有する(受信側は承認後 Room-scoped に保存され、グローバルへの昇格は `/room adopt` で行う)。
116
+
117
+ 1. ポートを取得する(上記参照)。
118
+ 2. カレントディレクトリの CLAUDE.md を読み込む:
119
+ ```bash
120
+ CLAUDE_MD_FILE="$(pwd)/CLAUDE.md"
121
+ if [ ! -f "$CLAUDE_MD_FILE" ]; then
122
+ GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
123
+ if [ -n "$GIT_ROOT" ] && [ -f "$GIT_ROOT/CLAUDE.md" ]; then
124
+ CLAUDE_MD_FILE="$GIT_ROOT/CLAUDE.md"
125
+ else
126
+ echo "エラー: CLAUDE.md が見つかりません"
127
+ exit 1
128
+ fi
129
+ fi
130
+ CLAUDE_MD_CONTENT=$(cat "$CLAUDE_MD_FILE")
131
+ ```
132
+ 3. `POST $BASE_URL/show/file` に以下を送信する:
133
+ ```json
134
+ { "share_type": "claude_md", "filename": "CLAUDE.md", "content": "<file_content>" }
135
+ ```
136
+ 4. 成功した場合:
137
+ ```
138
+ 📤 CLAUDE.md を共有しました(受信側は /room accept で承認後、Room-scoped に保存されます)
139
+ /room adopt で ~/.claude/CLAUDE.md に昇格できます
140
+ ```
141
+
142
+ ## エラー処理
143
+
144
+ - daemon が起動していない場合:
145
+ 「cc-room daemon が起動していません。」
146
+ - ルームに参加していない場合:
147
+ 「ルームに参加していません。`/room open` または `/room join` で参加してください。」
148
+ - Primary ルームがない場合:
149
+ 「`/room switch <name>` で執筆するルームを選んでください。」