setup-cc-room 0.2.0 → 0.2.2

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: Unified command for creating, joining, leaving, and switching Primary rooms, viewing status, and managing notes.
4
+ ---
5
+
6
+ Unified command for all room-related operations.
7
+
8
+ ## Getting the port
9
+
10
+ Before every HTTP request, obtain the port as follows:
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
+ In all subsequent steps, replace every occurrence of `http://127.0.0.1:7332` with `$BASE_URL`.
20
+
21
+ ## Subcommands
22
+
23
+ ### `/room` (no arguments) — View the whiteboard
24
+
25
+ 1. Obtain `BASE_URL` (see above).
26
+ 2. Check unread notifications, display them, and mark them as read:
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
+ # Extract and display lines newer than LAST_READ (unread)
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 "🔔 Unread notifications:"
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\", \"\")} joined {d.get(\"room\", \"\")}")
48
+ elif t == "leave":
49
+ print(f" 🚪 {d.get(\"identity\", \"\")} left {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" 🔒 Room {d.get(\"room\", \"\")} was closed")
54
+ elif t == "file_received":
55
+ print(f" 📎 {d.get(\"from\", \"\")} shared {d.get(\"filename\", \"\")}")
56
+ elif t == "dream_proposals":
57
+ print(f" 📋 {d.get(\"count\", 0)} proposal(s) for the team ({d.get(\"room\", \"\")})")
58
+ elif t == "dream_merged":
59
+ print(f" 🧠 Team memory was updated ({d.get(\"room\", \"\")})")
60
+ except Exception:
61
+ pass
62
+ '
63
+ echo ""
64
+ fi
65
+ # Mark as read: save the ts of the last line
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. Fetch the room list with `GET $BASE_URL/status`.
71
+ 4. Fetch all member summaries with `GET $BASE_URL/context`.
72
+ 5. Fetch the message list with `GET $BASE_URL/messages`.
73
+ 6. Display in the following format:
74
+
75
+ ```
76
+ 🏠 <room_name> ★ Primary / 👀 Watch 🔒 Private ON / 📡 Live
77
+ Members: <member1>, <member2>, ...
78
+ Notifications: ON (🔔) / OFF (🔕)
79
+
80
+ 📋 Whiteboard:
81
+ (display summaries from context if present)
82
+ <member1>: <summary>
83
+
84
+ 💬 Messages:
85
+ (display messages newest-first if present)
86
+ [time] <from>: <content>
87
+ ```
88
+
89
+ Display each room's role / Private state from `rooms[]` in `GET $BASE_URL/status`:
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 "(none)"
95
+ priv = "🔒 Private ON" if d.get("private") else "📡 Live"
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
+ Display notification state by reading `notifications.enabled` from `~/.cc-room/config.yaml`:
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 "Notifications: ON 🔔" || echo "Notifications: OFF 🔕"
125
+ ```
126
+
127
+ 7. If not joined to any room:
128
+ "You are not in a room. Create one with `/room open <name>` or join with `/room join`."
129
+
130
+ ### `/room open <name> [--quiet] [--dream-mine=...] [--dream-threshold=N] [--dream-silent=on|off]` — Open a room
131
+
132
+ 1. If `--quiet` is present, set `"quiet": true`; otherwise `false`.
133
+ 2. If dream options are present, include a `dream` object in the body (configurable only at room creation):
134
+ - `--dream-mine=every_stop|threshold|manual_only` → `"mine_trigger"`
135
+ - `--dream-threshold=N` → `"session_threshold": N` (when using threshold)
136
+ - `--dream-silent=on|off` → `"silent_merge": true/false`
137
+ 3. Send the following to `POST $BASE_URL/room/create`:
138
+ ```json
139
+ { "name": "<name>", "quiet": false, "dream": { "mine_trigger": "every_stop" } }
140
+ ```
141
+ `dream` is optional (global default is used if omitted).
142
+ 4. On success:
143
+ ```
144
+ ✅ Opened room '<name>'
145
+ PIN: <6-digit number>
146
+
147
+ Share the room name and PIN with your teammates.
148
+ They can join with /room join <name> <PIN>.
149
+
150
+ ★ This room becomes Primary (the room you write to)
151
+ 📡 Live / 🔒 Private ON (follow the private field in the response)
152
+
153
+ Team memory settings:
154
+ <dream_summary>
155
+ ```
156
+
157
+ Options:
158
+ - `--quiet`: Private ON from the moment you join (local mode)
159
+ - `--dream-mine=...`: Mine trigger (every_stop / threshold / manual_only)
160
+ - `--dream-threshold=N`: Number of Stop events when using threshold
161
+ - `--dream-silent=on|off`: 72h silent merge ON/OFF
162
+
163
+ ※ `--keep session|ttl|permanent` is planned for a future release
164
+
165
+ ### `/room join [name] [pin] [--quiet]` — Join a room
166
+
167
+ When called with no arguments:
168
+ 1. Fetch the list of rooms on the LAN with `GET $BASE_URL/room/discover`.
169
+ 2. Display the list:
170
+ ```
171
+ 📡 Rooms on the LAN:
172
+ • <room_name> (hosted by <identity>, <N> members)
173
+
174
+ Join with /room join <name> <PIN>
175
+ ```
176
+
177
+ When called with arguments:
178
+ 1. Send `{ "name": "<name>", "pin": "<pin>", "quiet": false }` to `POST $BASE_URL/room/join` (set `true` when `--quiet` is used).
179
+ 2. On success, display according to the `role` in the response:
180
+ - `role: "primary"` (first room):
181
+ ```
182
+ ✅ Joined '<name>'
183
+ ★ This room becomes Primary (the room you write to)
184
+ Receiving whiteboard content...
185
+ ```
186
+ - `role: "watch"` (second room and beyond):
187
+ ```
188
+ ✅ Joined '<name>' as Watch (read-only)
189
+ Switch Primary with /room switch <name> to write
190
+ ```
191
+
192
+ ### `/room leave` — Leave a room
193
+
194
+ 1. Fetch the room list with `GET $BASE_URL/status`.
195
+ 2. If you are the host and other members are connected:
196
+ "Guests are connected. [Hand off and leave / Close room for everyone]"
197
+ 3. Send `{ "room_id": "<id>" }` to `POST $BASE_URL/leave`.
198
+ 4. On success: "Left the room."
199
+
200
+ ### `/room pending` — List files awaiting approval
201
+
202
+ Display files received for sharing that have not yet been approved.
203
+
204
+ 1. Fetch `GET $BASE_URL/room/pending`.
205
+ 2. Display in the following format:
206
+ ```
207
+ 📥 Files awaiting approval:
208
+ [<id>] <type>: <filename>
209
+ from: <sender>
210
+ save path: <save_path (full path)>
211
+ ```
212
+ If the list is empty: "No files awaiting approval"
213
+
214
+ ### `/room accept <id>` — Approve a shared file
215
+
216
+ Accept a pending file and write it to the specified save path.
217
+
218
+ 1. Send the following to `POST $BASE_URL/room/accept`:
219
+ ```json
220
+ { "pending_id": "<id>" }
221
+ ```
222
+ 2. On success:
223
+ ```
224
+ ✅ Approved <filename>
225
+ Save path: <save_path (full path)>
226
+ ```
227
+ For `claude_md` type: "Saved as room-scoped. Run /room adopt to add it globally"
228
+
229
+ ### `/room reject <id>` — Reject a shared file
230
+
231
+ Discard a pending file.
232
+
233
+ 1. Send the following to `POST $BASE_URL/room/reject`:
234
+ ```json
235
+ { "pending_id": "<id>" }
236
+ ```
237
+ 2. On success: "🗑 Discarded <id>"
238
+
239
+ ### `/room adopt` — Promote room-scoped CLAUDE.md to global
240
+
241
+ Append the current room's CLAUDE.md to `~/.claude/CLAUDE.md`.
242
+
243
+ 1. Fetch the current room_id with `GET $BASE_URL/status`.
244
+ 2. Send the following to `POST $BASE_URL/room/adopt`:
245
+ ```json
246
+ { "room_id": "<room_id>" }
247
+ ```
248
+ 3. On success:
249
+ ```
250
+ ✅ Appended room-scoped CLAUDE.md to ~/.claude/CLAUDE.md
251
+ Path: <path>
252
+ ```
253
+
254
+ ### `/room notify on` / `/room notify off` — Toggle notifications
255
+
256
+ Enable or disable OS notifications for yourself. Settings are persisted in `~/.cc-room/config.yaml`.
257
+
258
+ 1. Send the following to `POST $BASE_URL/notify/toggle`:
259
+ - For `on`: `{ "enabled": true }`
260
+ - For `off`: `{ "enabled": false }`
261
+ 2. On success:
262
+ - `on`: "🔔 Notifications enabled"
263
+ - `off`: "🔕 Notifications disabled"
264
+
265
+ ### `/room switch <name>` — Switch Primary (the room you write to)
266
+
267
+ Switch the room targeted for summaries, artifacts, and Dream. The previous Primary is demoted to Watch.
268
+
269
+ 1. Send the following to `POST $BASE_URL/room/switch`:
270
+ ```json
271
+ { "name": "<name>" }
272
+ ```
273
+ 2. On success:
274
+ ```
275
+ ★ Primary: <name> (previous Primary is now Watch)
276
+ ```
277
+ 3. On failure (room name not joined): "Room '<name>' not found"
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` — Check Primary / Private / team memory settings
286
+
287
+ List each room's role (Primary/Watch), Private state, and effective dream settings.
288
+
289
+ 1. Fetch `GET $BASE_URL/status`.
290
+ 2. Display in the following format:
291
+
292
+ ```
293
+ Primary: <primary_room_name> 📡 Live / 🔒 Private ON
294
+
295
+ ★ auth-feature (primary)
296
+ mine: threshold (every 20 sessions) / silent ON / Mine only when live
297
+ 👀 side-project (watch)
298
+ mine: every_stop / silent OFF / Mine only when live
299
+ ```
300
+
301
+ `rooms[].dream` is the effective configuration (global default + room override resolved). Key fields:
302
+ - `mine_trigger` — `every_stop` / `threshold` / `manual_only`
303
+ - `session_threshold` — number of Stop events when using threshold
304
+ - `silent_merge` — 72h silent merge ON/OFF
305
+ - `require_show_on` — Mine only when live
306
+
307
+ Example implementation:
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("Not joined to any room")
314
+ sys.exit(0)
315
+ primary = d.get("primary_room_name") or "(unset — /room switch <name>)"
316
+ priv = "🔒 Private ON" if d.get("private") else "📡 Live"
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 (every {threshold} sessions)"
326
+ silent = "ON" if cfg.get("silent_merge") else "OFF"
327
+ show_req = "Mine only when live" if cfg.get("require_show_on") else "Mine always"
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>` — Mention teammates
340
+
341
+ When you send a message starting with `@` in the normal Claude Code input field, it reaches humans—not Claude. **This is not a command for Claude; the user types it directly.**
342
+
343
+ | Syntax | Recipients |
344
+ |---|---|
345
+ | `@akira JWT is done` | Only akira |
346
+ | `@here Anyone up for lunch?` | All members who are live (Private OFF)—everyone here right now |
347
+ | `@all Deploy is complete` | Everyone in the room (regardless of Private ON/OFF) |
348
+
349
+ For everyday chat, `@here` feels natural. Use `@all` for urgent interruptions since it reaches everyone forcefully.
350
+
351
+ **Internal behavior (handled by the UserPromptSubmit Hook)**:
352
+ 1. Detect the `@` prefix
353
+ 2. Match against the room member list:
354
+ - `@name` → send mention only if the member exists. Unknown names (`@dataclass`, etc.) are passed to Claude as normal input
355
+ - `@here` / `@all` → reserved words, so mention is always sent
356
+ - Case-insensitive (`@Akira` = `@akira`)
357
+ 3. Determine `context_summary` based on your public state:
358
+ - Live (Primary and Private OFF) → include the latest summary as `context_summary`
359
+ - Private ON or Watch room → omit `context_summary`
360
+ 4. Send a `mention` message to the destination daemon over WebSocket
361
+ 5. The prompt is still passed to Claude as-is (Claude can interpret the text with `@name`)
362
+
363
+ **On the receiving side**:
364
+ - Mentions are appended to `mentions.jsonl`
365
+ - The status line shows `📬 N`
366
+ - The next time you talk to Claude, a banner is inserted at the top of the prompt inside `<cc-room-context>` tags and marked as read at that point
367
+
368
+ To check unread mentions right now:
369
+ 1. Fetch the unread mention list with `GET $BASE_URL/unread`.
370
+ 2. Display in the following format:
371
+ ```
372
+ 📬 Unread mentions (<N>):
373
+ [<time>] <from>: <content>
374
+ context: <context_summary> ← shown only when the sender was live (Private OFF)
375
+ ```
376
+ If none: "📭 No unread mentions"
377
+
378
+ ### `/room remember <content>` — Post a sticky note (team memory)
379
+
380
+ Share to team memory immediately (always delivered instantly regardless of Private state). From v0.1 onward, generates `.md` files under `room-memory/` and updates the `MEMORY.md` index. Currently appends to `memory.md` (backward compatible).
381
+
382
+ 1. Send `{ "content": "<content>" }` to `POST $BASE_URL/memory`.
383
+ 2. Display the `message` field from the response to the user as-is (confirmation message with count).
384
+
385
+ ### `/room dream` — Team memory (auto-organize, propose, merge)
386
+
387
+ Refer to this as **"team memory"** with users—not "Dream". Operate it via subcommands.
388
+
389
+ #### `/room dream status` — Check settings and pending proposals
390
+
391
+ 1. Fetch effective settings with `GET $BASE_URL/dream/config` (add `?room_id=` if needed).
392
+ 2. Fetch your pending proposals with `GET $BASE_URL/dream/pending`.
393
+ 3. Display in the following format:
394
+
395
+ ```
396
+ 📋 Team memory settings (<room_name>):
397
+ mine_trigger: threshold (every 20 sessions)
398
+ silent_merge: ON (72h objection window)
399
+ require_show_on: ON (Mine only when live)
400
+
401
+ Your pending proposals: <N>
402
+ 1. [decision] Shorten JWT TTL to 1 day (deadline: <objection_deadline>)
403
+ 2. ...
404
+ ```
405
+
406
+ #### `/room dream objection [number|slug]` — Hold your proposal (stop merge)
407
+
408
+ 1. With no argument: if there is exactly one pending proposal, use it; if multiple, show the list and ask the user to choose.
409
+ 2. Specify number `N` or `slug`.
410
+ 3. Send to `POST $BASE_URL/dream/objection`:
411
+ ```json
412
+ { "proposal_slug": "<slug>", "reason": "まだ早い" }
413
+ ```
414
+ 4. Display the `message` from the response (e.g., ⏸ Proposal held).
415
+
416
+ #### `/room dream hold [number|slug]` — Extend the objection deadline by 72h
417
+
418
+ 1. Identify the target proposal (same as status).
419
+ 2. Send `{ "proposal_slug": "<slug>" }` to `POST $BASE_URL/dream/hold`.
420
+ 3. Display the `message` from the response.
421
+
422
+ #### `/room dream revert` — Undo the most recent silent merge
423
+
424
+ 1. Send `{}` or `{ "room_id": "<id>" }` to `POST $BASE_URL/dream/revert`.
425
+ 2. Display the `message` from the response (only the most recent merge within 72h).
426
+
427
+ #### `/room dream mine` — Manually extract knowledge candidates (v0.2 manual accept flow)
428
+
429
+ Extract candidates from the recent session; the user selects items to apply immediately (no silent merge).
430
+
431
+ 1. Run `POST $BASE_URL/dream`.
432
+ 2. Display `candidates` from the response as a numbered list.
433
+ 3. After choosing items to adopt, send `POST $BASE_URL/dream/accept` with `{ "indices": [0, 2] }` (use `{}` for all).
434
+ 4. Display the `message` from the response.
435
+
436
+ MCP: detailed search `room_memory_search(query)`, L2 source `room_memory_trace(entry_name)`.
437
+
438
+ ### `/room config dream [key=value ...]` — Room Mine/merge settings (host only)
439
+
440
+ Change dream settings for the Primary room. Non-hosts receive an error.
441
+
442
+ 1. Display current values with `GET $BASE_URL/dream/config`.
443
+ 2. If changes are requested, send a patch to `POST $BASE_URL/dream/config`:
444
+
445
+ | User input | JSON field | Value |
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 only when live) |
454
+ | `public=off` | `require_show_on` | `false` (Mine always) |
455
+
456
+ Example: `POST $BASE_URL/dream/config` `{ "mine_trigger": "every_stop", "silent_merge": false }`
457
+
458
+ 3. Display `summary` and `message` from the response.
459
+
460
+ **Example dialogue:**
461
+
462
+ ```
463
+ Current settings (auth-feature):
464
+ mine_trigger: threshold (every 20 sessions)
465
+ silent_merge: ON (72h objection window)
466
+ require_show_on: ON (Mine only when live)
467
+
468
+ /room config dream mine=every_stop
469
+ → ✅ Updated team memory settings for auth-feature
470
+ ```
471
+
472
+ ## Error handling
473
+
474
+ - If the daemon is not running:
475
+ "cc-room daemon is not running. Start `cc-room-daemon`."
@@ -0,0 +1,149 @@
1
+ ---
2
+ name: show
3
+ description: Explicit whiteboard posts and sharing of skills, commands, or CLAUDE.md. Use /private for local/public toggle.
4
+ ---
5
+
6
+ Write explicitly to the room whiteboard. **Use `/private` to toggle local/public** (the old `/show` toggle is removed).
7
+
8
+ ## Resolve the port
9
+
10
+ Before every HTTP request, resolve the port:
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
+ ## Subcommands
20
+
21
+ ### `/show` (no args) — deprecated
22
+
23
+ The old toggle is removed. Guide the user:
24
+
25
+ ```
26
+ Use /private to toggle local/public:
27
+ /private on — local mode (hide work)
28
+ /private off — back to public (choose share/drop for pending work)
29
+ To post a message, use /show "message".
30
+ ```
31
+
32
+ ### `/show <message>` — post a message
33
+
34
+ Post to the **Primary** room whiteboard.
35
+
36
+ 1. Check `private` via `GET $BASE_URL/status`.
37
+ 2. If Private ON, always confirm with the user:
38
+ "You are in local mode. This message will be posted to Primary (<primary_room_name>). Send it?"
39
+ → Continue only if they accept.
40
+ 3. `POST $BASE_URL/share` with:
41
+ ```json
42
+ { "message": "<message>" }
43
+ ```
44
+ (Posted to Primary only.)
45
+ 4. On success:
46
+ ```
47
+ 📡 [<room_name>] <message>
48
+ ```
49
+
50
+ ### `/show skill <name>` — share a skill
51
+
52
+ Share your skill file with everyone in the room (recipients must accept).
53
+
54
+ 1. Resolve the port (above).
55
+ 2. Load the skill file:
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 "Error: skill '$SKILL_NAME' not found"
72
+ exit 1
73
+ fi
74
+ SKILL_CONTENT=$(cat "$SKILL_FILE")
75
+ ```
76
+ 3. `POST $BASE_URL/show/file` with:
77
+ ```json
78
+ { "share_type": "skill", "filename": "<name>.md", "content": "<file_content>" }
79
+ ```
80
+ 4. On success:
81
+ ```
82
+ 📤 Shared skill '<name>' (recipients must approve with /room accept)
83
+ ```
84
+
85
+ ### `/show command <name>` — share a command
86
+
87
+ Share your command file with everyone in the room (recipients must accept).
88
+
89
+ 1. Resolve the port (above).
90
+ 2. Load the command file:
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 "Error: command '$CMD_NAME' not found"
100
+ exit 1
101
+ fi
102
+ CMD_CONTENT=$(cat "$CMD_FILE")
103
+ ```
104
+ 3. `POST $BASE_URL/show/file` with:
105
+ ```json
106
+ { "share_type": "command", "filename": "<name>.md", "content": "<file_content>" }
107
+ ```
108
+ 4. On success:
109
+ ```
110
+ 📤 Shared command '<name>' (recipients must approve with /room accept)
111
+ ```
112
+
113
+ ### `/show claude-md` — share CLAUDE.md
114
+
115
+ Share this project's CLAUDE.md with the room (after accept it is saved room-scoped; promote globally with `/room adopt`).
116
+
117
+ 1. Resolve the port (above).
118
+ 2. Load CLAUDE.md from the current directory:
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 "Error: CLAUDE.md not found"
127
+ exit 1
128
+ fi
129
+ fi
130
+ CLAUDE_MD_CONTENT=$(cat "$CLAUDE_MD_FILE")
131
+ ```
132
+ 3. `POST $BASE_URL/show/file` with:
133
+ ```json
134
+ { "share_type": "claude_md", "filename": "CLAUDE.md", "content": "<file_content>" }
135
+ ```
136
+ 4. On success:
137
+ ```
138
+ 📤 Shared CLAUDE.md (recipients approve with /room accept; saved room-scoped)
139
+ Use /room adopt to promote to ~/.claude/CLAUDE.md
140
+ ```
141
+
142
+ ## Errors
143
+
144
+ - If the daemon is not running:
145
+ "cc-room daemon is not running."
146
+ - If not in a room:
147
+ "You are not in a room. Use `/room open` or `/room join` first."
148
+ - If there is no Primary room:
149
+ "Pick a room to write with `/room switch <name>`."
File without changes
File without changes