setup-cc-room 0.2.1 → 0.2.3

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.
@@ -7,9 +7,10 @@ import {
7
7
  stopOrphanDaemon,
8
8
  stopSystemd,
9
9
  uninstall
10
- } from "./chunk-AVDOC76K.js";
10
+ } from "./chunk-URM67EVS.js";
11
+ import "./chunk-DXODI3WA.js";
12
+ import "./chunk-OYW7MM4W.js";
11
13
  import "./chunk-WCEABGOA.js";
12
- import "./chunk-ETJNYBPF.js";
13
14
  export {
14
15
  getLaunchdPlistPath,
15
16
  getSystemdUnitPath,
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  printResults,
3
3
  validateAll
4
- } from "./chunk-NJES6PZ3.js";
4
+ } from "./chunk-RYVZHEHF.js";
5
+ import "./chunk-OYW7MM4W.js";
5
6
  export {
6
7
  printResults,
7
8
  validateAll
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "setup-cc-room",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Install cc-room — bring your Claude Code into a shared LAN meeting room",
5
5
  "license": "MIT",
6
6
  "author": "Takanori Suzuki",
@@ -18,7 +18,7 @@
18
18
  "vendor"
19
19
  ],
20
20
  "scripts": {
21
- "build": "tsup src/index.ts src/installer.ts src/settings-merger.ts src/register-service.ts src/validators.ts src/daemon-resolver.ts src/paths.ts src/uninstaller.ts --format esm --dts",
21
+ "build": "tsup src/index.ts src/installer.ts src/settings-merger.ts src/register-service.ts src/validators.ts src/daemon-resolver.ts src/paths.ts src/uninstaller.ts src/i18n.ts --format esm --dts",
22
22
  "dev": "tsup src/index.ts --format esm --watch",
23
23
  "pack:vendor": "node scripts/pack-vendor.mjs",
24
24
  "prepack": "pnpm run build && pnpm run pack:vendor",
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: private
3
+ description: Toggle local (Private) mode. When turning off, always choose share or drop for pending work.
4
+ ---
5
+
6
+ Toggle between local (Private) and public. While public (Private OFF), summaries and artifacts flow to the Primary room automatically. Work done during Private ON accumulates as pending and stays invisible to others.
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
+ ### `/private` (no args) — status
22
+
23
+ 1. `GET $BASE_URL/status`.
24
+ 2. Display:
25
+ ```
26
+ 🔒 Private ON (local mode) / 📡 Live (Private OFF)
27
+ Primary: <primary_room_name>
28
+ Pending local work: <pending_turns + pending_files> items
29
+ ```
30
+
31
+ ### `/private on` — enter local mode
32
+
33
+ 1. `POST $BASE_URL/private` with `{ "mode": "on" }`.
34
+ 2. On success:
35
+ ```
36
+ 🔒 Switched to local mode. Your work is hidden (use /private off to go public)
37
+ ```
38
+
39
+ ### `/private off` — return to public
40
+
41
+ 1. `POST $BASE_URL/private` with `{ "mode": "off" }`.
42
+ 2. Branch on the response:
43
+ - If `needs_choice: true`, **always ask the user** (never auto-share):
44
+ ```
45
+ You have <pending> pending items. Send them to Primary (<primary_room_name>)?
46
+ → share (send, then go public) / drop (discard, then go public)
47
+ ```
48
+ Then run `/private share` or `/private drop` based on their choice.
49
+ - If no `needs_choice` (0 pending):
50
+ ```
51
+ 📡 Back to public. Work summaries will flow to Primary (<primary_room_name>)
52
+ ```
53
+
54
+ ### `/private share` — share pending, then go public
55
+
56
+ 1. `POST $BASE_URL/private` with `{ "mode": "share" }`.
57
+ 2. On success:
58
+ ```
59
+ 📡 Shared <shared> pending items to Primary (<primary_room_name>) and returned to public
60
+ ```
61
+
62
+ ### `/private drop` — discard pending, then go public
63
+
64
+ 1. `POST $BASE_URL/private` with `{ "mode": "drop" }`.
65
+ 2. On success:
66
+ ```
67
+ 🗑 Discarded <dropped> pending items and returned to public
68
+ ```
69
+
70
+ ## Errors
71
+
72
+ - If the daemon is not running:
73
+ "cc-room daemon is not running."
74
+ - If not in a room:
75
+ "You are not in a room. Use `/room open` or `/room join` first."
@@ -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`."