u-foo 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.
Files changed (77) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +163 -0
  3. package/README.zh-CN.md +163 -0
  4. package/bin/uclaude +65 -0
  5. package/bin/ucodex +65 -0
  6. package/bin/ufoo +93 -0
  7. package/bin/ufoo.js +35 -0
  8. package/modules/AGENTS.template.md +87 -0
  9. package/modules/bus/README.md +132 -0
  10. package/modules/bus/SKILLS/ubus/SKILL.md +209 -0
  11. package/modules/bus/scripts/bus-alert.sh +185 -0
  12. package/modules/bus/scripts/bus-listen.sh +117 -0
  13. package/modules/context/ASSUMPTIONS.md +7 -0
  14. package/modules/context/CONSTRAINTS.md +7 -0
  15. package/modules/context/CONTEXT-STRUCTURE.md +49 -0
  16. package/modules/context/DECISION-PROTOCOL.md +62 -0
  17. package/modules/context/HANDOFF.md +33 -0
  18. package/modules/context/README.md +82 -0
  19. package/modules/context/RULES.md +15 -0
  20. package/modules/context/SKILLS/README.md +14 -0
  21. package/modules/context/SKILLS/uctx/SKILL.md +91 -0
  22. package/modules/context/SYSTEM.md +18 -0
  23. package/modules/context/TEMPLATES/assumptions.md +4 -0
  24. package/modules/context/TEMPLATES/constraints.md +4 -0
  25. package/modules/context/TEMPLATES/decision.md +16 -0
  26. package/modules/context/TEMPLATES/project-context-readme.md +6 -0
  27. package/modules/context/TEMPLATES/system.md +3 -0
  28. package/modules/context/TEMPLATES/terminology.md +4 -0
  29. package/modules/context/TERMINOLOGY.md +10 -0
  30. package/modules/resources/ICONS/README.md +12 -0
  31. package/modules/resources/ICONS/libraries/README.md +17 -0
  32. package/modules/resources/ICONS/libraries/heroicons/LICENSE +22 -0
  33. package/modules/resources/ICONS/libraries/heroicons/README.md +15 -0
  34. package/modules/resources/ICONS/libraries/heroicons/arrow-right.svg +4 -0
  35. package/modules/resources/ICONS/libraries/heroicons/check.svg +4 -0
  36. package/modules/resources/ICONS/libraries/heroicons/chevron-down.svg +4 -0
  37. package/modules/resources/ICONS/libraries/heroicons/cog-6-tooth.svg +5 -0
  38. package/modules/resources/ICONS/libraries/heroicons/magnifying-glass.svg +4 -0
  39. package/modules/resources/ICONS/libraries/heroicons/x-mark.svg +4 -0
  40. package/modules/resources/ICONS/libraries/lucide/LICENSE +40 -0
  41. package/modules/resources/ICONS/libraries/lucide/README.md +15 -0
  42. package/modules/resources/ICONS/libraries/lucide/arrow-right.svg +15 -0
  43. package/modules/resources/ICONS/libraries/lucide/check.svg +14 -0
  44. package/modules/resources/ICONS/libraries/lucide/chevron-down.svg +14 -0
  45. package/modules/resources/ICONS/libraries/lucide/search.svg +15 -0
  46. package/modules/resources/ICONS/libraries/lucide/settings.svg +15 -0
  47. package/modules/resources/ICONS/libraries/lucide/x.svg +15 -0
  48. package/modules/resources/ICONS/rules.md +7 -0
  49. package/modules/resources/README.md +9 -0
  50. package/modules/resources/UI/ANTI-PATTERNS.md +6 -0
  51. package/modules/resources/UI/TONE.md +6 -0
  52. package/package.json +40 -0
  53. package/scripts/banner.sh +89 -0
  54. package/scripts/bus-alert.sh +6 -0
  55. package/scripts/bus-autotrigger.sh +6 -0
  56. package/scripts/bus-daemon.sh +231 -0
  57. package/scripts/bus-inject.sh +144 -0
  58. package/scripts/bus-listen.sh +6 -0
  59. package/scripts/bus.sh +984 -0
  60. package/scripts/context-decisions.sh +167 -0
  61. package/scripts/context-doctor.sh +72 -0
  62. package/scripts/context-lint.sh +110 -0
  63. package/scripts/doctor.sh +22 -0
  64. package/scripts/init.sh +247 -0
  65. package/scripts/skills.sh +113 -0
  66. package/scripts/status.sh +125 -0
  67. package/src/agent/cliRunner.js +190 -0
  68. package/src/agent/internalRunner.js +212 -0
  69. package/src/agent/normalizeOutput.js +41 -0
  70. package/src/agent/ufooAgent.js +222 -0
  71. package/src/chat/index.js +1603 -0
  72. package/src/cli.js +349 -0
  73. package/src/config.js +37 -0
  74. package/src/daemon/index.js +501 -0
  75. package/src/daemon/ops.js +120 -0
  76. package/src/daemon/run.js +41 -0
  77. package/src/daemon/status.js +78 -0
@@ -0,0 +1,87 @@
1
+ <!-- ufoo -->
2
+ ## ufoo Protocol
3
+
4
+ This project uses **ufoo** for agent coordination. Read the full documentation at `.ufoo/docs/` (symlinked from ufoo installation).
5
+
6
+ ### Core Principles
7
+
8
+ 1. **Agents are autonomous** - Execute tasks without asking for permission
9
+ 2. **Communication via bus** - Use `ufoo bus` for inter-agent messaging
10
+ 3. **Decisions are recorded** - Use `ufoo ctx` for decision tracking
11
+ 4. **Context is shared** - All agents read from `.ufoo/context/`
12
+
13
+ ### Available Commands
14
+
15
+ | Command | Description |
16
+ |---------|-------------|
17
+ | `uinit` | Initialize/repair .ufoo directory |
18
+ | `uctx` | Check context status and decisions |
19
+ | `ustatus` | Unified status view (banner, unread bus, open decisions) |
20
+ | `ubus` | Check bus messages and **auto-execute** them |
21
+
22
+ ### Quick Reference
23
+
24
+ ```bash
25
+ # Context
26
+ ufoo ctx decisions -l # List all decisions
27
+ ufoo ctx decisions -n 1 # Show latest decision
28
+
29
+ # Bus
30
+ ufoo bus join # Join bus (auto by uclaude/ucodex)
31
+ ufoo bus check $SUBSCRIBER # Check pending messages
32
+ ufoo bus send "<id>" "<msg>" # Send message
33
+ ufoo bus status # Show bus status
34
+ ```
35
+
36
+ ---
37
+
38
+ ## ufoo context Protocol
39
+
40
+ On session start, check context status:
41
+ ```bash
42
+ ufoo ctx decisions -l
43
+ ufoo ctx decisions -n 1
44
+ ```
45
+
46
+ Key files in `.ufoo/context/`:
47
+ - `DECISIONS/` - Decision log (append-only)
48
+ - `SYSTEM.md` - System overview
49
+ - `CONSTRAINTS.md` - Non-negotiable rules
50
+
51
+ **Decision recording policy:**
52
+ - **Must record**: evaluations, architecture, naming, trade-offs
53
+ - Write decision **before replying** when applicable
54
+
55
+ ---
56
+
57
+ ## ufoo bus Protocol
58
+
59
+ ### CRITICAL: `ubus` Command Behavior
60
+
61
+ **When you receive `ubus`, you MUST:**
62
+ 1. Check pending messages: `ufoo bus check $SUBSCRIBER`
63
+ 2. **EXECUTE each task immediately** - Do NOT ask the user
64
+ 3. Reply to sender: `ufoo bus send "<publisher>" "<result>"`
65
+
66
+ **Rules:**
67
+ - Execute tasks immediately without asking
68
+ - Always reply to the sender
69
+ - Do NOT ask "Want me to...?" or "Should I...?"
70
+ - Do NOT wait for user confirmation
71
+
72
+ ### Message Format
73
+
74
+ ```
75
+ @you from claude-code:abc123
76
+ Type: message/targeted
77
+ Content: {"message":"Please analyze the project structure"}
78
+ ```
79
+
80
+ Extract sender ID from "from" field, use it to reply.
81
+
82
+ ### Example
83
+
84
+ 1. Receive: `@you from claude-code:bd36dda0 Content: {"message":"Please analyze the project structure"}`
85
+ 2. Execute: Analyze the project structure
86
+ 3. Reply: `ufoo bus send "claude-code:bd36dda0" "Project contains src/, scripts/, modules/"`
87
+ <!-- /ufoo -->
@@ -0,0 +1,132 @@
1
+ # bus
2
+
3
+ File-system based Agent event bus for async communication between multiple AI Coding Agents.
4
+
5
+ ## Overview
6
+
7
+ bus solves communication problems in multi-agent collaboration:
8
+
9
+ - Multiple Claude Code instances collaborating on the same project
10
+ - Communication between different AI tools (Claude Code, Cursor, Copilot)
11
+ - Task delegation and response
12
+ - Broadcast messages
13
+
14
+ ## Installation
15
+
16
+ Initialize via ufoo:
17
+
18
+ ```bash
19
+ ufoo init --modules context,bus
20
+ ```
21
+
22
+ ## Directory Structure
23
+
24
+ ```
25
+ .bus/
26
+ ├── bus.json # Bus metadata + subscriber status
27
+ ├── events/ # Event stream (JSONL, sharded by date)
28
+ ├── offsets/ # Each Agent's consumption progress
29
+ └── queues/ # Targeted event queues
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ### Join Bus
35
+
36
+ ```bash
37
+ SUBSCRIBER=$(bash scripts/bus.sh join)
38
+ # Output: claude-code:a1b2c3
39
+ ```
40
+
41
+ ### Check Pending Messages
42
+
43
+ ```bash
44
+ bash scripts/bus.sh check $SUBSCRIBER
45
+ ```
46
+
47
+ ### Send Messages
48
+
49
+ ```bash
50
+ # Send to specific instance
51
+ bash scripts/bus.sh send "claude-code:abc123" "Please help me review"
52
+
53
+ # Send to all instances of same type
54
+ bash scripts/bus.sh send "claude-code" "Everyone please review"
55
+
56
+ # Broadcast to all
57
+ bash scripts/bus.sh broadcast "I completed feature-x"
58
+ ```
59
+
60
+ ### View Status
61
+
62
+ ```bash
63
+ bash scripts/bus.sh status
64
+ ```
65
+
66
+ ## Notifications/Alerts (no key injection, recommended)
67
+
68
+ If you want to receive "new message alerts" while running Codex/Claude in another terminal, use **agent-side alert/listen** (avoids IME/accessibility permission/window positioning fragmentation issues):
69
+
70
+ ```bash
71
+ SUBSCRIBER=$(bash scripts/bus.sh join | tail -n 1)
72
+
73
+ # Background alert: title badge + bell + optional macOS notification center
74
+ bash scripts/bus-alert.sh "$SUBSCRIBER" 1 --notify --daemon
75
+
76
+ # Or: foreground continuous print of new messages (suitable for a side terminal)
77
+ bash scripts/bus-listen.sh "$SUBSCRIBER" --from-beginning
78
+ ```
79
+
80
+ ## Unattended Auto-Execute (recommended)
81
+
82
+ If you need **Claude A to notify Claude B / Codex C and have the target auto-execute** (e.g., auto-trigger `/ubus`), use `autotrigger`:
83
+
84
+ 1) First `join` in each terminal session (records `tty`, also records `TMUX_PANE` if in tmux):
85
+
86
+ ```bash
87
+ SUBSCRIBER=$(bash scripts/bus.sh join | tail -n 1)
88
+ ```
89
+
90
+ 2) Start autotrigger in the project (runs as background daemon):
91
+
92
+ ```bash
93
+ # backend=auto prefers tmux (if available), otherwise tries Terminal.app do script (pure Automation), finally Accessibility
94
+ bash scripts/bus-autotrigger.sh start --interval 1 --command "/ubus" --backend auto
95
+ ```
96
+
97
+ 3) After sending a message, autotrigger injects `/ubus` into the target session and presses Enter:
98
+ - tmux: `send-keys`
99
+ - Terminal.app (pure Automation): `do script` (no Accessibility needed, but requires Automation authorization; compatibility depends on whether target program accepts input)
100
+ - Terminal.app (Accessibility): System Events (needs Accessibility), injection sequence is Escape + paste + Return (avoids IME issues)
101
+
102
+ Tips:
103
+ - Terminal.app backend depends on `tty` in `bus.json`. Execute `join` in the target terminal session (ensure `tty` is not `not a tty`).
104
+ - Pure Automation backend needs one-time authorization: System Preferences → Privacy & Security → Automation (allow script to control Terminal).
105
+ - Accessibility backend needs one-time authorization: System Preferences → Privacy & Security → Accessibility (for Terminal / script host).
106
+
107
+ Stop/view status:
108
+
109
+ ```bash
110
+ bash scripts/bus-autotrigger.sh status
111
+ bash scripts/bus-autotrigger.sh stop
112
+ ```
113
+
114
+ ## Subscriber ID Format
115
+
116
+ ```
117
+ {agent_type}:{instance_id}
118
+
119
+ Examples:
120
+ claude-code:a1b2c3
121
+ cursor-ai:main
122
+ copilot:session1
123
+ ```
124
+
125
+ ## Relationship with context
126
+
127
+ | Module | Problem Solved |
128
+ |--------|----------------|
129
+ | context | Shared context, decision recording, knowledge persistence |
130
+ | bus | Real-time communication, task delegation, message passing |
131
+
132
+ Both are independent peer modules that can be used separately or together.
@@ -0,0 +1,209 @@
1
+ ---
2
+ name: ubus
3
+ description: |
4
+ Poll event bus, check pending messages.
5
+ Use when: (1) check if other Agents sent messages, (2) view bus status, (3) periodic polling.
6
+ If not yet joined bus, will auto-join.
7
+ ---
8
+
9
+ # /ubus - Event Bus Polling
10
+
11
+ Check pending messages on the event bus.
12
+
13
+ ## Arguments
14
+
15
+ - `/ubus` - Check messages and show status
16
+ - `/ubus watch` - Start background auto-notification (title badge + bell + notification center)
17
+ - `/ubus stop` - Stop background auto-notification
18
+ - `/ubus listen` - Foreground continuous listener, print new messages (suitable for side terminal)
19
+ - `/ubus auto` - Unattended auto-execute (auto-inject `/ubus` and press Enter)
20
+
21
+ ## Execution Flow
22
+
23
+ ### 1. Check if .ufoo/bus exists
24
+
25
+ ```bash
26
+ if [[ ! -d ".ufoo/bus" ]]; then
27
+ echo "Event bus not initialized, please run /uinit and select bus module"
28
+ exit
29
+ fi
30
+ ```
31
+
32
+ ### 2. Check if already joined bus
33
+
34
+ Read `.ufoo/bus/bus.json`, check if current session is registered.
35
+
36
+ If not, auto-join (will auto-generate a friendly nickname like "codex-1", "claude-1"):
37
+
38
+ ```bash
39
+ SUBSCRIBER=$(ufoo bus join | tail -n 1)
40
+ echo "Joined event bus: $SUBSCRIBER"
41
+ # Example output: codex:0e293156 (nickname: codex-1)
42
+ ```
43
+
44
+ To join with a custom nickname:
45
+
46
+ ```bash
47
+ ufoo bus join [session-id] [agent-type] "your-nickname"
48
+ # Example: ufoo bus join abc123 claude-code "architect"
49
+ ```
50
+
51
+ ### 3. Handle arguments
52
+
53
+ If argument is `watch`, use **Bash tool's `run_in_background: true`** to start background notification:
54
+
55
+ ```bash
56
+ # Title badge + bell + notification center (no accessibility permission needed)
57
+ ufoo bus alert "$SUBSCRIBER" 2 --notify --daemon
58
+ ```
59
+
60
+ If argument is `listen`, foreground blocking listener (no background task tool needed):
61
+
62
+ ```bash
63
+ ufoo bus listen "$SUBSCRIBER" --from-beginning
64
+ ```
65
+
66
+ If argument is `auto`, unattended auto-execute:
67
+
68
+ ```bash
69
+ # Start daemon (background resident), auto-inject /ubus + Enter on new message
70
+ ufoo bus daemon --daemon
71
+ ```
72
+
73
+ Tips:
74
+ - Need to use `uclaude`/`ucodex` wrapper to start Claude Code/Codex (auto-records tty)
75
+ - Terminal.app needs Accessibility permission (for keyboard input injection)
76
+
77
+ If argument is `stop`, stop background notification:
78
+
79
+ ```bash
80
+ ufoo bus alert "$SUBSCRIBER" --stop
81
+ ```
82
+
83
+ ### 4. Check pending events
84
+
85
+ ```bash
86
+ ufoo bus check "$SUBSCRIBER"
87
+ ```
88
+
89
+ If pending events exist, show:
90
+
91
+ ```
92
+ === Pending Messages ===
93
+
94
+ @you from claude-code:abc123
95
+ Type: task/delegate
96
+ Content: {"task":"review","file":"src/main.ts"}
97
+
98
+ ---
99
+ Please handle the above messages, after completion you can reply:
100
+ ufoo bus send "claude-code:abc123" "Review completed, found 2 issues..."
101
+ ```
102
+
103
+ ### 5. IMPORTANT: Acknowledge messages after handling
104
+
105
+ After you have read and processed the messages, you MUST acknowledge them to prevent repeated notifications:
106
+
107
+ ```bash
108
+ ufoo bus ack "$SUBSCRIBER"
109
+ ```
110
+
111
+ **This is critical** - if you don't ack, the daemon will keep injecting `/ubus` commands.
112
+
113
+ If there's nothing to do (no actionable task), just ack immediately without sending a reply.
114
+
115
+ ### 6. Routing Override
116
+
117
+ If the message explicitly instructs you to report to a specific PM/DEV/TEST ID, **send the result to that ID instead of the publisher**.
118
+
119
+ ### 5. Show bus status
120
+
121
+ ```bash
122
+ ufoo bus status
123
+ ```
124
+
125
+ Output (now includes nicknames):
126
+
127
+ ```
128
+ === Event Bus Status ===
129
+ My identity: claude-code:xyz789
130
+ Online subscribers: 2
131
+ - claude-code:abc123 (architect)
132
+ - claude-code:xyz789 (dev-lead)
133
+ Recent events: 5
134
+ ```
135
+
136
+ ## Managing Nicknames
137
+
138
+ ### View and Change Nicknames
139
+
140
+ ```bash
141
+ # Change an agent's nickname
142
+ ufoo bus rename <subscriber-id> "new-nickname"
143
+ # Example: ufoo bus rename claude-code:47b1d525 "backend-dev"
144
+
145
+ # Nickname alias command
146
+ ufoo bus nick <subscriber-id> "new-nickname"
147
+ ```
148
+
149
+ **Important Notes:**
150
+ - Nicknames must be globally unique
151
+ - Cannot change nickname during join (use `rename` command instead)
152
+ - Re-joining with same subscriber ID will reuse existing nickname
153
+ - Auto-generated nicknames: `codex-1`, `codex-2`, `claude-1`, `claude-2`, etc.
154
+
155
+ ## Handling Received Messages
156
+
157
+ When receiving targeted messages:
158
+
159
+ 1. **Understand request** - Read message content
160
+ 2. **Execute task** - If task delegation, execute it
161
+ 3. **Reply to sender** - Send response after completion
162
+
163
+ ```bash
164
+ ufoo bus send "<sender-id>" "<reply-content>"
165
+ ```
166
+
167
+ ## Sending Messages
168
+
169
+ ### Smart Routing (when you don't know the target ID)
170
+
171
+ If the user says "notify codex to do X" without specifying an ID, use smart routing:
172
+
173
+ ```bash
174
+ # Step 1: Find candidates
175
+ ufoo bus resolve "$SUBSCRIBER" codex
176
+
177
+ # Output shows:
178
+ # - If only 1 codex: directly shows the ID
179
+ # - If multiple: shows each with nickname and message history
180
+ ```
181
+
182
+ Based on the output:
183
+ - **Single match**: Use that ID directly
184
+ - **Multiple matches**: Analyze the message history to find the right target
185
+ - Look for context clues in previous conversations
186
+ - If still unclear, ask the user which one, or send to all of that type
187
+
188
+ ### Direct Send
189
+
190
+ ```bash
191
+ # Send to specific Agent by full ID
192
+ ufoo bus send "claude-code:abc123" "message content"
193
+
194
+ # Send to specific Agent by nickname (NEW!)
195
+ ufoo bus send "architect" "message content"
196
+ ufoo bus send "backend-dev" "message content"
197
+
198
+ # Send to all Agents of same type
199
+ ufoo bus send "codex" "message content"
200
+
201
+ # Broadcast to everyone
202
+ ufoo bus broadcast "message content"
203
+ ```
204
+
205
+ **Target Resolution Priority:**
206
+ 1. Exact subscriber ID (e.g., `claude-code:abc123`)
207
+ 2. Nickname match (e.g., `architect` → resolves to subscriber ID)
208
+ 3. Agent type (e.g., `codex` → all codex agents)
209
+ 4. Wildcard (`*` → all agents)
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # bus-alert.sh
5
+ # Background notification daemon for a single subscriber.
6
+ #
7
+ # Usage:
8
+ # bash bus-alert.sh <subscriber> [interval] [options]
9
+ #
10
+ # Options:
11
+ # --notify Enable macOS Notification Center
12
+ # --daemon Run in background
13
+ # --stop Stop running alert for this subscriber
14
+ # --no-title Disable terminal title badge
15
+ # --no-bell Disable terminal bell
16
+
17
+ BUS_DIR=".ufoo/bus"
18
+ INTERVAL=2
19
+ DAEMON_MODE=0
20
+ USE_NOTIFY=0
21
+ USE_TITLE=1
22
+ USE_BELL=1
23
+ STOP_MODE=0
24
+
25
+ usage() {
26
+ cat <<'USAGE'
27
+ bus-alert.sh - Background notification daemon for a single subscriber
28
+
29
+ Usage:
30
+ bus-alert.sh <subscriber> [interval] [options]
31
+
32
+ Options:
33
+ --notify Enable macOS Notification Center
34
+ --daemon Run in background
35
+ --stop Stop running alert for this subscriber
36
+ --no-title Disable terminal title badge
37
+ --no-bell Disable terminal bell
38
+ -h, --help Show this help
39
+ USAGE
40
+ }
41
+
42
+ # Handle --help first
43
+ if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
44
+ usage
45
+ exit 0
46
+ fi
47
+
48
+ SUBSCRIBER="${1:-}"
49
+ shift || true
50
+
51
+ if [[ -z "$SUBSCRIBER" ]]; then
52
+ usage >&2
53
+ exit 1
54
+ fi
55
+
56
+ # Parse interval if numeric
57
+ if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
58
+ INTERVAL="$1"
59
+ shift || true
60
+ fi
61
+
62
+ while [[ $# -gt 0 ]]; do
63
+ case "$1" in
64
+ --notify)
65
+ USE_NOTIFY=1
66
+ shift
67
+ ;;
68
+ --daemon)
69
+ DAEMON_MODE=1
70
+ shift
71
+ ;;
72
+ --stop)
73
+ STOP_MODE=1
74
+ shift
75
+ ;;
76
+ --no-title)
77
+ USE_TITLE=0
78
+ shift
79
+ ;;
80
+ --no-bell)
81
+ USE_BELL=0
82
+ shift
83
+ ;;
84
+ *)
85
+ shift
86
+ ;;
87
+ esac
88
+ done
89
+
90
+ # Sanitize subscriber for filename: claude-code:abc123 -> claude-code_abc123
91
+ SAFE_SUB="${SUBSCRIBER//:/_}"
92
+ PID_FILE="$BUS_DIR/pids/alert-${SAFE_SUB}.pid"
93
+ QUEUE_FILE="$BUS_DIR/queues/${SAFE_SUB}/pending.jsonl"
94
+
95
+ mkdir -p "$BUS_DIR/pids"
96
+
97
+ # Stop mode
98
+ if [[ "$STOP_MODE" == "1" ]]; then
99
+ if [[ -f "$PID_FILE" ]]; then
100
+ pid="$(cat "$PID_FILE" 2>/dev/null || true)"
101
+ if [[ -n "$pid" ]]; then
102
+ kill "$pid" 2>/dev/null && echo "[alert] Stopped $SUBSCRIBER (pid=$pid)" || echo "[alert] Not running"
103
+ fi
104
+ rm -f "$PID_FILE"
105
+ else
106
+ echo "[alert] Not running for $SUBSCRIBER"
107
+ fi
108
+ exit 0
109
+ fi
110
+
111
+ # Daemon mode - fork to background
112
+ if [[ "$DAEMON_MODE" == "1" ]]; then
113
+ # Check if already running
114
+ if [[ -f "$PID_FILE" ]]; then
115
+ existing="$(cat "$PID_FILE" 2>/dev/null || true)"
116
+ if [[ -n "$existing" ]] && kill -0 "$existing" 2>/dev/null; then
117
+ echo "[alert] Already running for $SUBSCRIBER (pid=$existing)"
118
+ exit 0
119
+ fi
120
+ fi
121
+
122
+ LOG_FILE="$BUS_DIR/logs/alert-${SAFE_SUB}.log"
123
+ mkdir -p "$BUS_DIR/logs"
124
+
125
+ args=("$SUBSCRIBER" "$INTERVAL")
126
+ [[ "$USE_NOTIFY" == "1" ]] && args+=("--notify")
127
+ [[ "$USE_TITLE" == "0" ]] && args+=("--no-title")
128
+ [[ "$USE_BELL" == "0" ]] && args+=("--no-bell")
129
+
130
+ nohup bash "$0" "${args[@]}" >> "$LOG_FILE" 2>&1 &
131
+ echo $! > "$PID_FILE"
132
+ echo "[alert] Started for $SUBSCRIBER (pid=$!, log=$LOG_FILE)"
133
+ exit 0
134
+ fi
135
+
136
+ # Record PID for foreground mode too
137
+ echo $$ > "$PID_FILE"
138
+ trap 'rm -f "$PID_FILE" 2>/dev/null || true' EXIT
139
+
140
+ echo "[alert] Watching $SUBSCRIBER (interval=${INTERVAL}s)"
141
+
142
+ LAST_COUNT=0
143
+
144
+ # Get initial count
145
+ if [[ -f "$QUEUE_FILE" ]] && [[ -s "$QUEUE_FILE" ]]; then
146
+ LAST_COUNT="$(wc -l < "$QUEUE_FILE" | tr -d ' ')"
147
+ fi
148
+
149
+ while true; do
150
+ # Get current count
151
+ if [[ -f "$QUEUE_FILE" ]] && [[ -s "$QUEUE_FILE" ]]; then
152
+ count="$(wc -l < "$QUEUE_FILE" | tr -d ' ')"
153
+ else
154
+ count=0
155
+ fi
156
+
157
+ # New messages arrived
158
+ if [[ "$count" -gt "$LAST_COUNT" ]]; then
159
+ new_count=$((count - LAST_COUNT))
160
+ echo "[alert] $(date '+%H:%M:%S') +${new_count} new message(s)"
161
+
162
+ # Terminal bell
163
+ if [[ "$USE_BELL" == "1" ]]; then
164
+ printf '\a'
165
+ fi
166
+
167
+ # Terminal title badge
168
+ if [[ "$USE_TITLE" == "1" ]]; then
169
+ printf '\033]0;[%d] %s\007' "$count" "$SUBSCRIBER"
170
+ fi
171
+
172
+ # macOS notification
173
+ if [[ "$USE_NOTIFY" == "1" ]]; then
174
+ osascript -e "display notification \"${new_count} new message(s)\" with title \"ufoo bus\" subtitle \"$SUBSCRIBER\"" 2>/dev/null || true
175
+ fi
176
+ fi
177
+
178
+ # Update title even if no new messages (show current count)
179
+ if [[ "$USE_TITLE" == "1" ]] && [[ "$count" -gt 0 ]]; then
180
+ printf '\033]0;[%d] %s\007' "$count" "$SUBSCRIBER"
181
+ fi
182
+
183
+ LAST_COUNT="$count"
184
+ sleep "$INTERVAL"
185
+ done