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.
- package/LICENSE +35 -0
- package/README.md +163 -0
- package/README.zh-CN.md +163 -0
- package/bin/uclaude +65 -0
- package/bin/ucodex +65 -0
- package/bin/ufoo +93 -0
- package/bin/ufoo.js +35 -0
- package/modules/AGENTS.template.md +87 -0
- package/modules/bus/README.md +132 -0
- package/modules/bus/SKILLS/ubus/SKILL.md +209 -0
- package/modules/bus/scripts/bus-alert.sh +185 -0
- package/modules/bus/scripts/bus-listen.sh +117 -0
- package/modules/context/ASSUMPTIONS.md +7 -0
- package/modules/context/CONSTRAINTS.md +7 -0
- package/modules/context/CONTEXT-STRUCTURE.md +49 -0
- package/modules/context/DECISION-PROTOCOL.md +62 -0
- package/modules/context/HANDOFF.md +33 -0
- package/modules/context/README.md +82 -0
- package/modules/context/RULES.md +15 -0
- package/modules/context/SKILLS/README.md +14 -0
- package/modules/context/SKILLS/uctx/SKILL.md +91 -0
- package/modules/context/SYSTEM.md +18 -0
- package/modules/context/TEMPLATES/assumptions.md +4 -0
- package/modules/context/TEMPLATES/constraints.md +4 -0
- package/modules/context/TEMPLATES/decision.md +16 -0
- package/modules/context/TEMPLATES/project-context-readme.md +6 -0
- package/modules/context/TEMPLATES/system.md +3 -0
- package/modules/context/TEMPLATES/terminology.md +4 -0
- package/modules/context/TERMINOLOGY.md +10 -0
- package/modules/resources/ICONS/README.md +12 -0
- package/modules/resources/ICONS/libraries/README.md +17 -0
- package/modules/resources/ICONS/libraries/heroicons/LICENSE +22 -0
- package/modules/resources/ICONS/libraries/heroicons/README.md +15 -0
- package/modules/resources/ICONS/libraries/heroicons/arrow-right.svg +4 -0
- package/modules/resources/ICONS/libraries/heroicons/check.svg +4 -0
- package/modules/resources/ICONS/libraries/heroicons/chevron-down.svg +4 -0
- package/modules/resources/ICONS/libraries/heroicons/cog-6-tooth.svg +5 -0
- package/modules/resources/ICONS/libraries/heroicons/magnifying-glass.svg +4 -0
- package/modules/resources/ICONS/libraries/heroicons/x-mark.svg +4 -0
- package/modules/resources/ICONS/libraries/lucide/LICENSE +40 -0
- package/modules/resources/ICONS/libraries/lucide/README.md +15 -0
- package/modules/resources/ICONS/libraries/lucide/arrow-right.svg +15 -0
- package/modules/resources/ICONS/libraries/lucide/check.svg +14 -0
- package/modules/resources/ICONS/libraries/lucide/chevron-down.svg +14 -0
- package/modules/resources/ICONS/libraries/lucide/search.svg +15 -0
- package/modules/resources/ICONS/libraries/lucide/settings.svg +15 -0
- package/modules/resources/ICONS/libraries/lucide/x.svg +15 -0
- package/modules/resources/ICONS/rules.md +7 -0
- package/modules/resources/README.md +9 -0
- package/modules/resources/UI/ANTI-PATTERNS.md +6 -0
- package/modules/resources/UI/TONE.md +6 -0
- package/package.json +40 -0
- package/scripts/banner.sh +89 -0
- package/scripts/bus-alert.sh +6 -0
- package/scripts/bus-autotrigger.sh +6 -0
- package/scripts/bus-daemon.sh +231 -0
- package/scripts/bus-inject.sh +144 -0
- package/scripts/bus-listen.sh +6 -0
- package/scripts/bus.sh +984 -0
- package/scripts/context-decisions.sh +167 -0
- package/scripts/context-doctor.sh +72 -0
- package/scripts/context-lint.sh +110 -0
- package/scripts/doctor.sh +22 -0
- package/scripts/init.sh +247 -0
- package/scripts/skills.sh +113 -0
- package/scripts/status.sh +125 -0
- package/src/agent/cliRunner.js +190 -0
- package/src/agent/internalRunner.js +212 -0
- package/src/agent/normalizeOutput.js +41 -0
- package/src/agent/ufooAgent.js +222 -0
- package/src/chat/index.js +1603 -0
- package/src/cli.js +349 -0
- package/src/config.js +37 -0
- package/src/daemon/index.js +501 -0
- package/src/daemon/ops.js +120 -0
- package/src/daemon/run.js +41 -0
- package/src/daemon/status.js +78 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2026 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2026.
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
The MIT License (MIT) (for portions derived from Feather)
|
|
20
|
+
|
|
21
|
+
Copyright (c) 2013-2026 Cole Bemis
|
|
22
|
+
|
|
23
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
24
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
25
|
+
in the Software without restriction, including without limitation the rights
|
|
26
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
27
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
28
|
+
furnished to do so, subject to the following conditions:
|
|
29
|
+
|
|
30
|
+
The above copyright notice and this permission notice shall be included in all
|
|
31
|
+
copies or substantial portions of the Software.
|
|
32
|
+
|
|
33
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
34
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
35
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
36
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
37
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
38
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
39
|
+
SOFTWARE.
|
|
40
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Lucide (Minimal Subset)
|
|
2
|
+
|
|
3
|
+
Source:
|
|
4
|
+
- Repository: https://github.com/lucide-icons/lucide
|
|
5
|
+
- Path: `icons/`
|
|
6
|
+
- License: ISC (see `LICENSE`)
|
|
7
|
+
- Fetched: 2026-01-27
|
|
8
|
+
|
|
9
|
+
Included icons:
|
|
10
|
+
- `arrow-right.svg`
|
|
11
|
+
- `chevron-down.svg`
|
|
12
|
+
- `search.svg`
|
|
13
|
+
- `settings.svg`
|
|
14
|
+
- `check.svg`
|
|
15
|
+
- `x.svg`
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3
|
+
width="24"
|
|
4
|
+
height="24"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
stroke="currentColor"
|
|
8
|
+
stroke-width="2"
|
|
9
|
+
stroke-linecap="round"
|
|
10
|
+
stroke-linejoin="round"
|
|
11
|
+
>
|
|
12
|
+
<path d="M5 12h14" />
|
|
13
|
+
<path d="m12 5 7 7-7 7" />
|
|
14
|
+
</svg>
|
|
15
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3
|
+
width="24"
|
|
4
|
+
height="24"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
stroke="currentColor"
|
|
8
|
+
stroke-width="2"
|
|
9
|
+
stroke-linecap="round"
|
|
10
|
+
stroke-linejoin="round"
|
|
11
|
+
>
|
|
12
|
+
<path d="m21 21-4.34-4.34" />
|
|
13
|
+
<circle cx="11" cy="11" r="8" />
|
|
14
|
+
</svg>
|
|
15
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3
|
+
width="24"
|
|
4
|
+
height="24"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
stroke="currentColor"
|
|
8
|
+
stroke-width="2"
|
|
9
|
+
stroke-linecap="round"
|
|
10
|
+
stroke-linejoin="round"
|
|
11
|
+
>
|
|
12
|
+
<path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" />
|
|
13
|
+
<circle cx="12" cy="12" r="3" />
|
|
14
|
+
</svg>
|
|
15
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3
|
+
width="24"
|
|
4
|
+
height="24"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
stroke="currentColor"
|
|
8
|
+
stroke-width="2"
|
|
9
|
+
stroke-linecap="round"
|
|
10
|
+
stroke-linejoin="round"
|
|
11
|
+
>
|
|
12
|
+
<path d="M18 6 6 18" />
|
|
13
|
+
<path d="m6 6 12 12" />
|
|
14
|
+
</svg>
|
|
15
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# resources
|
|
2
|
+
|
|
3
|
+
Optional resources for AI-assisted coding workflows.
|
|
4
|
+
|
|
5
|
+
This repository intentionally contains non-core materials such as:
|
|
6
|
+
- `UI/` tone + anti-pattern references
|
|
7
|
+
- `ICONS/` reference icon subsets (with licenses)
|
|
8
|
+
|
|
9
|
+
It is designed to be installed and managed by `ufoo` under `~/.ufoo/modules/resources`.
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "u-foo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Multi-Agent Workspace Protocol. Just add u. claude → uclaude, codex → ucodex.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"homepage": "https://ufoo.dev",
|
|
7
|
+
"author": "UFOO Team",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"cli",
|
|
10
|
+
"ai",
|
|
11
|
+
"agent",
|
|
12
|
+
"multi-agent",
|
|
13
|
+
"claude",
|
|
14
|
+
"codex",
|
|
15
|
+
"orchestration",
|
|
16
|
+
"workspace"
|
|
17
|
+
],
|
|
18
|
+
"bin": {
|
|
19
|
+
"ufoo": "bin/ufoo.js",
|
|
20
|
+
"uclaude": "bin/uclaude",
|
|
21
|
+
"ucodex": "bin/ucodex"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"bin/",
|
|
25
|
+
"src/",
|
|
26
|
+
"scripts/",
|
|
27
|
+
"modules/",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"type": "commonjs",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"blessed": "^0.1.81",
|
|
37
|
+
"chalk": "^4.1.2",
|
|
38
|
+
"commander": "^13.1.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# banner.sh - TUI startup banner for ufoo agents
|
|
3
|
+
|
|
4
|
+
# Colors
|
|
5
|
+
RST='\033[0m'
|
|
6
|
+
BLD='\033[1m'
|
|
7
|
+
DIM='\033[2m'
|
|
8
|
+
CYN='\033[0;36m'
|
|
9
|
+
GRN='\033[0;32m'
|
|
10
|
+
MAG='\033[0;35m'
|
|
11
|
+
WHT='\033[1;37m'
|
|
12
|
+
YLW='\033[0;33m'
|
|
13
|
+
|
|
14
|
+
show_banner() {
|
|
15
|
+
local agent_type="${1:-claude}"
|
|
16
|
+
local session_id="${2:-unknown}"
|
|
17
|
+
local subscriber="${3:-}"
|
|
18
|
+
local daemon_status="${4:-}"
|
|
19
|
+
|
|
20
|
+
local ACOL
|
|
21
|
+
if [[ "$agent_type" == "codex" ]]; then
|
|
22
|
+
ACOL="$GRN"
|
|
23
|
+
else
|
|
24
|
+
ACOL="$MAG"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Width matches Codex CLI (inner content = 52)
|
|
28
|
+
local INNER=52
|
|
29
|
+
local W=$INNER
|
|
30
|
+
|
|
31
|
+
# Helper: print line with exact padding
|
|
32
|
+
line() {
|
|
33
|
+
local prefix="$1"
|
|
34
|
+
local value="$2"
|
|
35
|
+
local color="$3"
|
|
36
|
+
local prefix_len=${#prefix}
|
|
37
|
+
local value_len=${#value}
|
|
38
|
+
local pad_len=$((INNER - prefix_len - value_len))
|
|
39
|
+
printf "${DIM}│${RST}${prefix}${color}${value}${RST}"
|
|
40
|
+
printf "%${pad_len}s" ""
|
|
41
|
+
printf "${DIM}│${RST}\n"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
echo ""
|
|
45
|
+
printf "${DIM}╭"; printf '─%.0s' $(seq 1 $W); printf "╮${RST}\n"
|
|
46
|
+
|
|
47
|
+
# Title line with icon (compute padding from plain text lengths)
|
|
48
|
+
local icon_plain="<○>"
|
|
49
|
+
local title="UFOO · Multi-Agent Protocol"
|
|
50
|
+
local title_pad=$((INNER - 1 - ${#icon_plain} - 1 - ${#title}))
|
|
51
|
+
printf "${DIM}│${RST} ${WHT}${icon_plain}${RST} ${CYN}${BLD}UFOO${RST}${DIM} · Multi-Agent Protocol${RST}"
|
|
52
|
+
printf "%${title_pad}s" ""
|
|
53
|
+
printf "${DIM}│${RST}\n"
|
|
54
|
+
|
|
55
|
+
printf "${DIM}├"; printf '─%.0s' $(seq 1 $W); printf "┤${RST}\n"
|
|
56
|
+
|
|
57
|
+
# Agent
|
|
58
|
+
if [[ -n "$subscriber" ]]; then
|
|
59
|
+
local sub="$subscriber"
|
|
60
|
+
[[ ${#sub} -gt 36 ]] && sub="${sub:0:33}..."
|
|
61
|
+
line " Agent " "$sub" "${ACOL}${BLD}"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Daemon
|
|
65
|
+
if [[ -n "$daemon_status" ]]; then
|
|
66
|
+
line " Daemon " "$daemon_status" "${GRN}"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# Online agents (daemon handles cleanup of dead agents)
|
|
70
|
+
if [[ -f ".ufoo/bus/bus.json" ]]; then
|
|
71
|
+
local online
|
|
72
|
+
online=$(jq -r '.subscribers | to_entries[] | select(.value.status == "active") | .key' .ufoo/bus/bus.json 2>/dev/null | grep -v "^${subscriber}$" 2>/dev/null | head -5 || true)
|
|
73
|
+
if [[ -n "$online" ]]; then
|
|
74
|
+
printf "${DIM}├"; printf '─%.0s' $(seq 1 $W); printf "┤${RST}\n"
|
|
75
|
+
line " Online " "" ""
|
|
76
|
+
while IFS= read -r agent; do
|
|
77
|
+
[[ -z "$agent" ]] && continue
|
|
78
|
+
local a="$agent"
|
|
79
|
+
[[ ${#a} -gt 44 ]] && a="${a:0:41}..."
|
|
80
|
+
line " · " "$a" "${YLW}"
|
|
81
|
+
done <<< "$online"
|
|
82
|
+
fi
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
printf "${DIM}╰"; printf '─%.0s' $(seq 1 $W); printf "╯${RST}\n"
|
|
86
|
+
echo ""
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export -f show_banner 2>/dev/null || true
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# bus-daemon.sh
|
|
5
|
+
# Daemon that watches for new messages and injects /bus into target terminals.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# bash scripts/bus-daemon.sh [--interval <seconds>] [--daemon]
|
|
9
|
+
#
|
|
10
|
+
# This script monitors ALL subscribers' pending queues and injects /bus
|
|
11
|
+
# into their corresponding Terminal.app tabs when new messages arrive.
|
|
12
|
+
#
|
|
13
|
+
# Requirements:
|
|
14
|
+
# - macOS Accessibility permission for Terminal.app
|
|
15
|
+
# - Each subscriber's terminal should have title containing [bus:<instance-id>]
|
|
16
|
+
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
BUS_DIR=".ufoo/bus"
|
|
19
|
+
INTERVAL=2
|
|
20
|
+
DAEMON_MODE=0
|
|
21
|
+
PID_FILE="$BUS_DIR/.daemon.pid"
|
|
22
|
+
LOG_FILE="$BUS_DIR/logs/daemon.log"
|
|
23
|
+
|
|
24
|
+
usage() {
|
|
25
|
+
cat <<'USAGE'
|
|
26
|
+
bus-daemon.sh - Watch for new messages and inject /bus
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
bash scripts/bus-daemon.sh [options]
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--interval <n> Poll interval in seconds (default: 2)
|
|
33
|
+
--daemon Run in background
|
|
34
|
+
--stop Stop running daemon
|
|
35
|
+
--status Check daemon status
|
|
36
|
+
-h, --help Show this help
|
|
37
|
+
|
|
38
|
+
Notes:
|
|
39
|
+
- Requires macOS Accessibility permission
|
|
40
|
+
- Terminals must have title containing [bus:<instance-id>]
|
|
41
|
+
- Use `ufoo bus join` to set terminal title
|
|
42
|
+
USAGE
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Handle --help before other parsing
|
|
46
|
+
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
|
47
|
+
usage
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
while [[ $# -gt 0 ]]; do
|
|
52
|
+
case "$1" in
|
|
53
|
+
--interval)
|
|
54
|
+
INTERVAL="$2"
|
|
55
|
+
shift 2
|
|
56
|
+
;;
|
|
57
|
+
--daemon)
|
|
58
|
+
DAEMON_MODE=1
|
|
59
|
+
shift
|
|
60
|
+
;;
|
|
61
|
+
--stop)
|
|
62
|
+
if [[ -f "$PID_FILE" ]]; then
|
|
63
|
+
pid="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
64
|
+
if [[ -n "$pid" ]]; then
|
|
65
|
+
kill "$pid" 2>/dev/null && echo "[daemon] Stopped (pid=$pid)" || echo "[daemon] Not running"
|
|
66
|
+
fi
|
|
67
|
+
rm -f "$PID_FILE"
|
|
68
|
+
else
|
|
69
|
+
echo "[daemon] Not running"
|
|
70
|
+
fi
|
|
71
|
+
exit 0
|
|
72
|
+
;;
|
|
73
|
+
--status)
|
|
74
|
+
if [[ -f "$PID_FILE" ]]; then
|
|
75
|
+
pid="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
76
|
+
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
|
77
|
+
echo "[daemon] Running (pid=$pid)"
|
|
78
|
+
else
|
|
79
|
+
echo "[daemon] Not running (stale pid file)"
|
|
80
|
+
rm -f "$PID_FILE"
|
|
81
|
+
fi
|
|
82
|
+
else
|
|
83
|
+
echo "[daemon] Not running"
|
|
84
|
+
fi
|
|
85
|
+
exit 0
|
|
86
|
+
;;
|
|
87
|
+
*)
|
|
88
|
+
echo "Unknown option: $1" >&2
|
|
89
|
+
usage >&2
|
|
90
|
+
exit 1
|
|
91
|
+
;;
|
|
92
|
+
esac
|
|
93
|
+
done
|
|
94
|
+
|
|
95
|
+
if [[ ! -d "$BUS_DIR" ]]; then
|
|
96
|
+
echo "[daemon] Error: $BUS_DIR not found. Run ufoo init first." >&2
|
|
97
|
+
exit 1
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
mkdir -p "$BUS_DIR/logs"
|
|
101
|
+
|
|
102
|
+
if [[ "$DAEMON_MODE" == "1" ]]; then
|
|
103
|
+
# Check if already running
|
|
104
|
+
if [[ -f "$PID_FILE" ]]; then
|
|
105
|
+
existing="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
106
|
+
if [[ -n "$existing" ]] && kill -0 "$existing" 2>/dev/null; then
|
|
107
|
+
echo "[daemon] Already running (pid=$existing)"
|
|
108
|
+
exit 0
|
|
109
|
+
fi
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
echo "[daemon] Starting in background (log: $LOG_FILE)"
|
|
113
|
+
nohup bash "$0" --interval "$INTERVAL" >> "$LOG_FILE" 2>&1 &
|
|
114
|
+
echo $! > "$PID_FILE"
|
|
115
|
+
echo "[daemon] Started (pid=$!)"
|
|
116
|
+
exit 0
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# Record PID
|
|
120
|
+
echo $$ > "$PID_FILE"
|
|
121
|
+
trap 'rm -f "$PID_FILE" 2>/dev/null || true; rm -rf "$COUNTS_DIR" 2>/dev/null || true' EXIT
|
|
122
|
+
|
|
123
|
+
echo "[daemon] Started (pid=$$, interval=${INTERVAL}s)"
|
|
124
|
+
echo "[daemon] Watching: $BUS_DIR/queues/*/pending.jsonl"
|
|
125
|
+
|
|
126
|
+
# Use temp directory to track last known counts (bash 3.x compatible)
|
|
127
|
+
COUNTS_DIR="$BUS_DIR/.daemon-counts.$$"
|
|
128
|
+
mkdir -p "$COUNTS_DIR"
|
|
129
|
+
|
|
130
|
+
get_last_count() {
|
|
131
|
+
local safe_name="$1"
|
|
132
|
+
local count_file="$COUNTS_DIR/$safe_name"
|
|
133
|
+
if [[ -f "$count_file" ]]; then
|
|
134
|
+
cat "$count_file"
|
|
135
|
+
else
|
|
136
|
+
echo "0"
|
|
137
|
+
fi
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
set_last_count() {
|
|
141
|
+
local safe_name="$1"
|
|
142
|
+
local count="$2"
|
|
143
|
+
echo "$count" > "$COUNTS_DIR/$safe_name"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# Cleanup dead agents (check PID and mark offline)
|
|
147
|
+
cleanup_dead_agents() {
|
|
148
|
+
if [[ ! -f "$BUS_DIR/bus.json" ]]; then
|
|
149
|
+
return
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
local changed=0
|
|
153
|
+
local tmp_file
|
|
154
|
+
tmp_file=$(mktemp)
|
|
155
|
+
|
|
156
|
+
# Get all active subscribers with their PIDs
|
|
157
|
+
while IFS='|' read -r subscriber pid; do
|
|
158
|
+
[[ -z "$subscriber" || -z "$pid" ]] && continue
|
|
159
|
+
|
|
160
|
+
# Check if PID is still running AND is a claude/codex/node process
|
|
161
|
+
local proc_cmd
|
|
162
|
+
proc_cmd=$(ps -p "$pid" -o comm= 2>/dev/null || true)
|
|
163
|
+
if [[ -z "$proc_cmd" ]] || ! echo "$proc_cmd" | grep -qiE "(claude|codex|node)"; then
|
|
164
|
+
echo "[daemon] $(date '+%H:%M:%S') Agent $subscriber (pid=$pid) is dead, marking offline"
|
|
165
|
+
|
|
166
|
+
# Mark as offline in bus.json
|
|
167
|
+
jq --arg name "$subscriber" \
|
|
168
|
+
'.subscribers[$name].status = "offline"' \
|
|
169
|
+
"$BUS_DIR/bus.json" > "$tmp_file" && mv "$tmp_file" "$BUS_DIR/bus.json"
|
|
170
|
+
tmp_file=$(mktemp)
|
|
171
|
+
|
|
172
|
+
# Clean up queue directory
|
|
173
|
+
local safe_name="${subscriber//:/_}"
|
|
174
|
+
rm -rf "$BUS_DIR/queues/${safe_name}" 2>/dev/null || true
|
|
175
|
+
rm -f "$BUS_DIR/offsets/${safe_name}.offset" 2>/dev/null || true
|
|
176
|
+
|
|
177
|
+
changed=1
|
|
178
|
+
fi
|
|
179
|
+
done < <(jq -r '.subscribers | to_entries[] | select(.value.status == "active") | "\(.key)|\(.value.pid)"' "$BUS_DIR/bus.json" 2>/dev/null)
|
|
180
|
+
|
|
181
|
+
rm -f "$tmp_file" 2>/dev/null || true
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
CLEANUP_COUNTER=0
|
|
185
|
+
CLEANUP_INTERVAL=5 # Run cleanup every 5 cycles (10 seconds with default interval)
|
|
186
|
+
|
|
187
|
+
while true; do
|
|
188
|
+
# Periodic cleanup of dead agents
|
|
189
|
+
((CLEANUP_COUNTER++)) || true
|
|
190
|
+
if [[ $CLEANUP_COUNTER -ge $CLEANUP_INTERVAL ]]; then
|
|
191
|
+
cleanup_dead_agents
|
|
192
|
+
CLEANUP_COUNTER=0
|
|
193
|
+
fi
|
|
194
|
+
# Find all subscriber queue files
|
|
195
|
+
for queue_file in "$BUS_DIR/queues"/*/pending.jsonl; do
|
|
196
|
+
if [[ ! -f "$queue_file" ]]; then
|
|
197
|
+
continue
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# Extract subscriber from path: .ufoo/bus/queues/claude-code_abc123/pending.jsonl
|
|
201
|
+
dir_name="$(basename "$(dirname "$queue_file")")"
|
|
202
|
+
# Convert back to subscriber format: claude-code_abc123 -> claude-code:abc123
|
|
203
|
+
subscriber="${dir_name/_/:}"
|
|
204
|
+
|
|
205
|
+
# Get current count
|
|
206
|
+
if [[ -s "$queue_file" ]]; then
|
|
207
|
+
count="$(wc -l < "$queue_file" | tr -d ' ')"
|
|
208
|
+
else
|
|
209
|
+
count=0
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
# Get last known count
|
|
213
|
+
last="$(get_last_count "$dir_name")"
|
|
214
|
+
|
|
215
|
+
# If count increased, inject /bus
|
|
216
|
+
if [[ "$count" -gt "$last" ]]; then
|
|
217
|
+
echo "[daemon] $(date '+%H:%M:%S') New message for $subscriber ($last -> $count)"
|
|
218
|
+
|
|
219
|
+
# Try to inject
|
|
220
|
+
if bash "$SCRIPT_DIR/bus-inject.sh" "$subscriber" 2>&1; then
|
|
221
|
+
echo "[daemon] Injected /bus into $subscriber"
|
|
222
|
+
else
|
|
223
|
+
echo "[daemon] Failed to inject (window not found or no permission)"
|
|
224
|
+
fi
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
set_last_count "$dir_name" "$count"
|
|
228
|
+
done
|
|
229
|
+
|
|
230
|
+
sleep "$INTERVAL"
|
|
231
|
+
done
|