u-foo 1.0.3 → 1.0.6

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 (91) hide show
  1. package/README.md +67 -8
  2. package/README.zh-CN.md +9 -7
  3. package/SKILLS/ufoo/SKILL.md +117 -0
  4. package/SKILLS/uinit/SKILL.md +73 -0
  5. package/SKILLS/ustatus/SKILL.md +36 -0
  6. package/bin/uclaude.js +13 -0
  7. package/bin/ucodex.js +13 -0
  8. package/bin/ufoo +9 -31
  9. package/bin/ufoo.js +13 -0
  10. package/modules/AGENTS.template.md +15 -7
  11. package/modules/bus/README.md +28 -23
  12. package/modules/bus/SKILLS/ubus/SKILL.md +18 -8
  13. package/modules/context/README.md +18 -40
  14. package/modules/context/SKILLS/uctx/SKILL.md +61 -1
  15. package/package.json +16 -4
  16. package/scripts/.archived/bash-to-js-migration/README.md +46 -0
  17. package/scripts/.archived/bash-to-js-migration/banner.sh +89 -0
  18. package/scripts/{bus-inject.sh → .archived/bash-to-js-migration/bus-inject.sh} +35 -3
  19. package/scripts/{bus.sh → .archived/bash-to-js-migration/bus.sh} +3 -1
  20. package/scripts/banner.sh +2 -89
  21. package/scripts/postinstall.js +59 -0
  22. package/src/agent/cliRunner.js +33 -5
  23. package/src/agent/internalRunner.js +78 -51
  24. package/src/agent/launcher.js +702 -0
  25. package/src/agent/notifier.js +200 -0
  26. package/src/agent/ptyRunner.js +377 -0
  27. package/src/agent/ptyWrapper.js +354 -0
  28. package/src/agent/readyDetector.js +159 -0
  29. package/src/agent/ufooAgent.js +37 -42
  30. package/src/bus/API_DESIGN.md +204 -0
  31. package/src/bus/activate.js +156 -0
  32. package/src/bus/daemon.js +308 -0
  33. package/src/bus/index.js +785 -0
  34. package/src/bus/inject.js +285 -0
  35. package/src/bus/message.js +302 -0
  36. package/src/bus/nickname.js +86 -0
  37. package/src/bus/queue.js +131 -0
  38. package/src/bus/shake.js +26 -0
  39. package/src/bus/subscriber.js +296 -0
  40. package/src/bus/utils.js +357 -0
  41. package/src/chat/index.js +1842 -249
  42. package/src/cli.js +658 -95
  43. package/src/config.js +9 -2
  44. package/src/context/decisions.js +314 -0
  45. package/src/context/doctor.js +183 -0
  46. package/src/context/index.js +38 -0
  47. package/src/daemon/index.js +749 -94
  48. package/src/daemon/ops.js +395 -51
  49. package/src/daemon/providerSessions.js +291 -0
  50. package/src/daemon/run.js +34 -1
  51. package/src/daemon/status.js +24 -7
  52. package/src/doctor/index.js +50 -0
  53. package/src/init/index.js +264 -0
  54. package/src/skills/index.js +159 -0
  55. package/src/status/index.js +252 -0
  56. package/src/terminal/detect.js +64 -0
  57. package/src/terminal/index.js +8 -0
  58. package/src/terminal/iterm2.js +126 -0
  59. package/src/ufoo/agentsStore.js +41 -0
  60. package/src/ufoo/paths.js +46 -0
  61. package/src/utils/banner.js +73 -0
  62. package/bin/uclaude +0 -65
  63. package/bin/ucodex +0 -65
  64. package/modules/bus/scripts/bus-alert.sh +0 -185
  65. package/modules/bus/scripts/bus-listen.sh +0 -117
  66. package/modules/context/ASSUMPTIONS.md +0 -7
  67. package/modules/context/CONSTRAINTS.md +0 -7
  68. package/modules/context/CONTEXT-STRUCTURE.md +0 -49
  69. package/modules/context/DECISION-PROTOCOL.md +0 -62
  70. package/modules/context/HANDOFF.md +0 -33
  71. package/modules/context/RULES.md +0 -15
  72. package/modules/context/SKILLS/README.md +0 -14
  73. package/modules/context/SYSTEM.md +0 -18
  74. package/modules/context/TEMPLATES/assumptions.md +0 -4
  75. package/modules/context/TEMPLATES/constraints.md +0 -4
  76. package/modules/context/TEMPLATES/decision.md +0 -16
  77. package/modules/context/TEMPLATES/project-context-readme.md +0 -6
  78. package/modules/context/TEMPLATES/system.md +0 -3
  79. package/modules/context/TEMPLATES/terminology.md +0 -4
  80. package/modules/context/TERMINOLOGY.md +0 -10
  81. /package/scripts/{bus-alert.sh → .archived/bash-to-js-migration/bus-alert.sh} +0 -0
  82. /package/scripts/{bus-autotrigger.sh → .archived/bash-to-js-migration/bus-autotrigger.sh} +0 -0
  83. /package/scripts/{bus-daemon.sh → .archived/bash-to-js-migration/bus-daemon.sh} +0 -0
  84. /package/scripts/{bus-listen.sh → .archived/bash-to-js-migration/bus-listen.sh} +0 -0
  85. /package/scripts/{context-decisions.sh → .archived/bash-to-js-migration/context-decisions.sh} +0 -0
  86. /package/scripts/{context-doctor.sh → .archived/bash-to-js-migration/context-doctor.sh} +0 -0
  87. /package/scripts/{context-lint.sh → .archived/bash-to-js-migration/context-lint.sh} +0 -0
  88. /package/scripts/{doctor.sh → .archived/bash-to-js-migration/doctor.sh} +0 -0
  89. /package/scripts/{init.sh → .archived/bash-to-js-migration/init.sh} +0 -0
  90. /package/scripts/{skills.sh → .archived/bash-to-js-migration/skills.sh} +0 -0
  91. /package/scripts/{status.sh → .archived/bash-to-js-migration/status.sh} +0 -0
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Terminal type detection
3
+ *
4
+ * Detects the terminal emulator and its capabilities from environment variables.
5
+ * Results are cached for the lifetime of the process.
6
+ */
7
+
8
+ const TERMINAL_TYPES = {
9
+ ITERM2: "iterm2",
10
+ APPLE_TERMINAL: "apple-terminal",
11
+ KITTY: "kitty",
12
+ WEZTERM: "wezterm",
13
+ ALACRITTY: "alacritty",
14
+ UNKNOWN: "unknown",
15
+ };
16
+
17
+ let cached = null;
18
+
19
+ /**
20
+ * Detect the current terminal emulator.
21
+ * @returns {{ type: string, version: string, truecolor: boolean }}
22
+ */
23
+ function detect() {
24
+ if (cached) return cached;
25
+
26
+ const prog = process.env.TERM_PROGRAM || "";
27
+ const ver = process.env.TERM_PROGRAM_VERSION || "";
28
+ const colorterm = (process.env.COLORTERM || "").toLowerCase();
29
+ const truecolor = colorterm === "truecolor" || colorterm === "24bit";
30
+
31
+ let type = TERMINAL_TYPES.UNKNOWN;
32
+
33
+ if (prog === "iTerm.app" || process.env.ITERM_SESSION_ID) {
34
+ type = TERMINAL_TYPES.ITERM2;
35
+ } else if (prog === "Apple_Terminal") {
36
+ type = TERMINAL_TYPES.APPLE_TERMINAL;
37
+ } else if (prog === "kitty" || process.env.KITTY_PID) {
38
+ type = TERMINAL_TYPES.KITTY;
39
+ } else if (prog === "WezTerm") {
40
+ type = TERMINAL_TYPES.WEZTERM;
41
+ } else if (prog === "Alacritty") {
42
+ type = TERMINAL_TYPES.ALACRITTY;
43
+ }
44
+
45
+ cached = { type, version: ver, truecolor };
46
+ return cached;
47
+ }
48
+
49
+ function isITerm2() {
50
+ return detect().type === TERMINAL_TYPES.ITERM2;
51
+ }
52
+
53
+ function isAppleTerminal() {
54
+ return detect().type === TERMINAL_TYPES.APPLE_TERMINAL;
55
+ }
56
+
57
+ /**
58
+ * Reset cached detection (for testing).
59
+ */
60
+ function resetCache() {
61
+ cached = null;
62
+ }
63
+
64
+ module.exports = { detect, isITerm2, isAppleTerminal, resetCache, TERMINAL_TYPES };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Terminal detection and feature modules.
3
+ */
4
+
5
+ const detect = require("./detect");
6
+ const iterm2 = require("./iterm2");
7
+
8
+ module.exports = { ...detect, iterm2 };
@@ -0,0 +1,126 @@
1
+ /**
2
+ * iTerm2-specific terminal features via OSC escape sequences.
3
+ *
4
+ * All functions are guarded — they no-op when not running in iTerm2
5
+ * or when stdout is not a TTY.
6
+ *
7
+ * References:
8
+ * https://iterm2.com/documentation-escape-codes.html
9
+ */
10
+
11
+ const { isITerm2 } = require("./detect");
12
+
13
+ const ESC = "\x1b";
14
+ const BEL = "\x07";
15
+ const OSC = `${ESC}]`;
16
+
17
+ function canWrite() {
18
+ return isITerm2() && process.stdout && process.stdout.isTTY;
19
+ }
20
+
21
+ /**
22
+ * Notify iTerm2 of the current working directory (OSC 1337).
23
+ * Enables the Shell Integration "Recent Directories" feature and
24
+ * makes "Open in Finder" point to the correct location.
25
+ */
26
+ function setCwd(cwd) {
27
+ if (!canWrite() || !cwd) return;
28
+ process.stdout.write(`${OSC}1337;CurrentDir=${cwd}${BEL}`);
29
+ }
30
+
31
+ /**
32
+ * Set a user-defined badge in the upper-right of the session.
33
+ * Supports iTerm2 interpolated-string variables like \(session.name).
34
+ */
35
+ function setBadge(text) {
36
+ if (!canWrite()) return;
37
+ const encoded = Buffer.from(text || "").toString("base64");
38
+ process.stdout.write(`${OSC}1337;SetBadgeFormat=${encoded}${BEL}`);
39
+ }
40
+
41
+ /**
42
+ * Clear the session badge.
43
+ */
44
+ function clearBadge() {
45
+ if (!canWrite()) return;
46
+ process.stdout.write(`${OSC}1337;SetBadgeFormat=${BEL}`);
47
+ }
48
+
49
+ /**
50
+ * Post a macOS notification through iTerm2 (OSC 9).
51
+ * Only fires when the session tab is NOT focused, so it
52
+ * naturally avoids spamming the user.
53
+ */
54
+ function notify(message) {
55
+ if (!canWrite() || !message) return;
56
+ process.stdout.write(`${OSC}9;${message}${BEL}`);
57
+ }
58
+
59
+ /**
60
+ * Emit a shell-integration prompt mark (OSC 133).
61
+ * A = start of prompt B = end of prompt / start of command
62
+ * C = start of output D = end of output (with exit status)
63
+ */
64
+ function promptMark(code) {
65
+ if (!canWrite()) return;
66
+ const valid = ["A", "B", "C", "D"];
67
+ if (!valid.includes(code)) return;
68
+ process.stdout.write(`${OSC}133;${code}${BEL}`);
69
+ }
70
+
71
+ /**
72
+ * Set cursor shape.
73
+ * 0 = block 1 = vertical bar 2 = underline
74
+ */
75
+ function setCursorShape(shape) {
76
+ if (!canWrite()) return;
77
+ if (![0, 1, 2].includes(shape)) return;
78
+ process.stdout.write(`${OSC}1337;CursorShape=${shape}${BEL}`);
79
+ }
80
+
81
+ /**
82
+ * Set the tab color for this session (RGB).
83
+ * Pass null/undefined to reset to default.
84
+ */
85
+ function setTabColor(r, g, b) {
86
+ if (!canWrite()) return;
87
+ if (r == null) {
88
+ // reset
89
+ process.stdout.write(`${OSC}6;1;bg;*;default${BEL}`);
90
+ return;
91
+ }
92
+ process.stdout.write(`${OSC}6;1;bg;red;brightness;${r}${BEL}`);
93
+ process.stdout.write(`${OSC}6;1;bg;green;brightness;${g}${BEL}`);
94
+ process.stdout.write(`${OSC}6;1;bg;blue;brightness;${b}${BEL}`);
95
+ }
96
+
97
+ /**
98
+ * Add an annotation at the current cursor position.
99
+ */
100
+ function annotation(message) {
101
+ if (!canWrite() || !message) return;
102
+ const len = message.length;
103
+ process.stdout.write(`${OSC}1337;AddAnnotation=${len}|${message}${BEL}`);
104
+ }
105
+
106
+ /**
107
+ * Report current directory using OSC 7 (semantic URL).
108
+ * Understood by iTerm2, Terminal.app, and VTE-based terminals.
109
+ */
110
+ function reportCwd(cwd) {
111
+ if (!process.stdout || !process.stdout.isTTY || !cwd) return;
112
+ const hostname = require("os").hostname();
113
+ process.stdout.write(`${OSC}7;file://${hostname}${cwd}${BEL}`);
114
+ }
115
+
116
+ module.exports = {
117
+ setCwd,
118
+ setBadge,
119
+ clearBadge,
120
+ notify,
121
+ promptMark,
122
+ setCursorShape,
123
+ setTabColor,
124
+ annotation,
125
+ reportCwd,
126
+ };
@@ -0,0 +1,41 @@
1
+ const { getTimestamp, readJSON, writeJSON } = require("../bus/utils");
2
+
3
+ const AGENTS_SCHEMA_VERSION = 1;
4
+
5
+ function normalizeAgentsData(data) {
6
+ const base = data && typeof data === "object" ? { ...data } : {};
7
+ const { subscribers: _legacy, ...rest } = base;
8
+ const agents = base.agents && typeof base.agents === "object"
9
+ ? base.agents
10
+ : {};
11
+ const createdAt = typeof base.created_at === "string" && base.created_at
12
+ ? base.created_at
13
+ : getTimestamp();
14
+
15
+ return {
16
+ ...rest,
17
+ schema_version: AGENTS_SCHEMA_VERSION,
18
+ created_at: createdAt,
19
+ agents,
20
+ };
21
+ }
22
+
23
+ function loadAgentsData(filePath) {
24
+ const data = readJSON(filePath, null);
25
+ if (!data) {
26
+ return normalizeAgentsData({});
27
+ }
28
+ return normalizeAgentsData(data);
29
+ }
30
+
31
+ function saveAgentsData(filePath, data) {
32
+ const normalized = normalizeAgentsData(data);
33
+ writeJSON(filePath, normalized);
34
+ }
35
+
36
+ module.exports = {
37
+ AGENTS_SCHEMA_VERSION,
38
+ loadAgentsData,
39
+ saveAgentsData,
40
+ normalizeAgentsData,
41
+ };
@@ -0,0 +1,46 @@
1
+ const path = require("path");
2
+
3
+ function getUfooPaths(projectRoot) {
4
+ const ufooDir = path.join(projectRoot, ".ufoo");
5
+ const busDir = path.join(ufooDir, "bus");
6
+ const agentDir = path.join(ufooDir, "agent");
7
+ const agentsFile = path.join(agentDir, "all-agents.json");
8
+
9
+ const busQueuesDir = path.join(busDir, "queues");
10
+ const busEventsDir = path.join(busDir, "events");
11
+ const busLogsDir = path.join(busDir, "logs");
12
+ const busOffsetsDir = path.join(busDir, "offsets");
13
+
14
+ const busDaemonDir = path.join(ufooDir, "daemon");
15
+ const busDaemonPid = path.join(busDaemonDir, "daemon.pid");
16
+ const busDaemonLog = path.join(busDaemonDir, "daemon.log");
17
+ const busDaemonCountsDir = path.join(busDaemonDir, "counts");
18
+
19
+ const runDir = path.join(ufooDir, "run");
20
+ const ufooDaemonPid = path.join(runDir, "ufoo-daemon.pid");
21
+ const ufooDaemonLog = path.join(runDir, "ufoo-daemon.log");
22
+ const ufooSock = path.join(runDir, "ufoo.sock");
23
+
24
+ return {
25
+ ufooDir,
26
+ busDir,
27
+ agentDir,
28
+ agentsFile,
29
+ busQueuesDir,
30
+ busEventsDir,
31
+ busLogsDir,
32
+ busOffsetsDir,
33
+ busDaemonDir,
34
+ busDaemonPid,
35
+ busDaemonLog,
36
+ busDaemonCountsDir,
37
+ runDir,
38
+ ufooDaemonPid,
39
+ ufooDaemonLog,
40
+ ufooSock,
41
+ };
42
+ }
43
+
44
+ module.exports = {
45
+ getUfooPaths,
46
+ };
@@ -0,0 +1,73 @@
1
+ const chalk = require("chalk");
2
+
3
+ /**
4
+ * 显示 agent 启动横幅
5
+ */
6
+ function showBanner(options) {
7
+ const { agentType, sessionId, nickname, daemonStatus } = options;
8
+
9
+ // Compact logo (3 行)
10
+ const logo = [
11
+ "█ █ █▀▀ █▀█ █▀█",
12
+ "█ █ █▀ █ █ █ █",
13
+ "▀▀▀ ▀ ▀▀▀ ▀▀▀",
14
+ ];
15
+
16
+ // 准备右侧信息行
17
+ const infoLines = [];
18
+ if (nickname) {
19
+ infoLines.push(`${chalk.dim("Nickname:")} ${chalk.cyan.bold(nickname)}`);
20
+ }
21
+ infoLines.push(`${chalk.dim("Agent:")} ${chalk.green.bold(agentType)}${chalk.dim(":")}${chalk.yellow(sessionId)}`);
22
+ if (daemonStatus) {
23
+ const statusColor = daemonStatus === "running" ? chalk.green : chalk.blue;
24
+ infoLines.push(`${chalk.dim("Daemon:")} ${statusColor(daemonStatus)}`);
25
+ }
26
+
27
+ // 计算垂直居中偏移
28
+ const verticalOffset = Math.floor((logo.length - infoLines.length) / 2);
29
+
30
+ // 输出:Logo 和信息并排显示
31
+ console.log("");
32
+ logo.forEach((line, index) => {
33
+ const logoLine = chalk.cyan(line);
34
+ const infoIndex = index - verticalOffset;
35
+ const infoLine = (infoIndex >= 0 && infoIndex < infoLines.length)
36
+ ? infoLines[infoIndex]
37
+ : "";
38
+ console.log(` ${logoLine} ${infoLine}`);
39
+ });
40
+ console.log("");
41
+ }
42
+
43
+ /**
44
+ * 显示 ufoo 主命令横幅
45
+ */
46
+ function showUfooBanner(options = {}) {
47
+ const { version = "1.0.0" } = options;
48
+
49
+ // Compact logo (3 行)
50
+ const logo = [
51
+ "█ █ █▀▀ █▀█ █▀█",
52
+ "█ █ █▀ █ █ █ █",
53
+ "▀▀▀ ▀ ▀▀▀ ▀▀▀",
54
+ ];
55
+
56
+ // 右侧信息
57
+ const infoLines = [
58
+ `${chalk.cyan.bold(`v${version}`)} ${chalk.gray("Multi-Agent Workspace Protocol")}`,
59
+ "",
60
+ chalk.dim("uclaude") + chalk.gray(" · ") + chalk.dim("ucodex") + chalk.gray(" · ") + chalk.dim("ufoo init") + chalk.gray(" · ") + chalk.dim("ufoo ctx") + chalk.gray(" · ") + chalk.dim("ufoo bus"),
61
+ ];
62
+
63
+ // 输出:Logo 和信息并排显示
64
+ console.log("");
65
+ logo.forEach((line, index) => {
66
+ const logoLine = chalk.cyan(line);
67
+ const infoLine = (index < infoLines.length) ? infoLines[index] : "";
68
+ console.log(` ${logoLine} ${infoLine}`);
69
+ });
70
+ console.log("");
71
+ }
72
+
73
+ module.exports = { showBanner, showUfooBanner };
package/bin/uclaude DELETED
@@ -1,65 +0,0 @@
1
- #!/usr/bin/env bash
2
- # uclaude: Launch Claude Code and auto-join event bus
3
- #
4
- # Usage: uclaude [claude args...]
5
-
6
- set -euo pipefail
7
-
8
- # Resolve symlinks to get real script location
9
- SCRIPT_PATH="${BASH_SOURCE[0]}"
10
- while [[ -L "$SCRIPT_PATH" ]]; do
11
- SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
12
- SCRIPT_PATH="$(readlink "$SCRIPT_PATH")"
13
- [[ "$SCRIPT_PATH" != /* ]] && SCRIPT_PATH="$SCRIPT_DIR/$SCRIPT_PATH"
14
- done
15
- SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
16
- PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
17
-
18
- # Source banner
19
- source "$PROJECT_ROOT/scripts/banner.sh" 2>/dev/null || true
20
-
21
- # Check and initialize .ufoo (silent)
22
- if [[ ! -d ".ufoo/bus" ]]; then
23
- ufoo init --modules context,bus &>/dev/null || true
24
- fi
25
-
26
- # Check if AGENTS.md has ufoo template (inject if not)
27
- if [[ -f "AGENTS.md" ]] && ! grep -q "<!-- ufoo -->" "AGENTS.md" 2>/dev/null; then
28
- ufoo init --modules context,bus &>/dev/null || true
29
- fi
30
-
31
- # Generate or reuse session ID
32
- if [[ -z "${CLAUDE_SESSION_ID:-}" ]]; then
33
- export CLAUDE_SESSION_ID="$(date +%s%N | shasum | head -c 8)"
34
- fi
35
-
36
- # Join event bus (using fixed session ID)
37
- # Export our PID so bus.sh registers the correct process
38
- export UFOO_PARENT_PID=$$
39
- NICKNAME="${UFOO_NICKNAME:-}"
40
- SUBSCRIBER=$(ufoo bus join "$CLAUDE_SESSION_ID" claude-code "$NICKNAME" 2>/dev/null | tail -1)
41
-
42
- # Auto-start project-level daemon (if not running)
43
- DAEMON_STATUS=""
44
- PID_FILE=".ufoo/bus/.daemon.pid"
45
- if [[ -f "$PID_FILE" ]]; then
46
- existing="$(cat "$PID_FILE" 2>/dev/null || true)"
47
- if [[ -n "$existing" ]] && kill -0 "$existing" 2>/dev/null; then
48
- DAEMON_STATUS="running"
49
- else
50
- ufoo bus daemon --daemon &>/dev/null && DAEMON_STATUS="started"
51
- fi
52
- else
53
- ufoo bus daemon --daemon &>/dev/null && DAEMON_STATUS="started"
54
- fi
55
-
56
- # Show banner
57
- if type show_banner &>/dev/null; then
58
- show_banner "claude" "$CLAUDE_SESSION_ID" "$SUBSCRIBER" "$DAEMON_STATUS"
59
- else
60
- echo "[uclaude] Connected -> $SUBSCRIBER"
61
- echo "[uclaude] Daemon: $DAEMON_STATUS"
62
- echo ""
63
- fi
64
-
65
- exec claude "$@"
package/bin/ucodex DELETED
@@ -1,65 +0,0 @@
1
- #!/usr/bin/env bash
2
- # ucodex: Launch Codex and auto-join event bus
3
- #
4
- # Usage: ucodex [codex args...]
5
-
6
- set -euo pipefail
7
-
8
- # Resolve symlinks to get real script location
9
- SCRIPT_PATH="${BASH_SOURCE[0]}"
10
- while [[ -L "$SCRIPT_PATH" ]]; do
11
- SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
12
- SCRIPT_PATH="$(readlink "$SCRIPT_PATH")"
13
- [[ "$SCRIPT_PATH" != /* ]] && SCRIPT_PATH="$SCRIPT_DIR/$SCRIPT_PATH"
14
- done
15
- SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
16
- PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
17
-
18
- # Source banner
19
- source "$PROJECT_ROOT/scripts/banner.sh" 2>/dev/null || true
20
-
21
- # Check and initialize .ufoo (silent)
22
- if [[ ! -d ".ufoo/bus" ]]; then
23
- ufoo init --modules context,bus &>/dev/null || true
24
- fi
25
-
26
- # Check if AGENTS.md has ufoo template (inject if not)
27
- if [[ -f "AGENTS.md" ]] && ! grep -q "<!-- ufoo -->" "AGENTS.md" 2>/dev/null; then
28
- ufoo init --modules context,bus &>/dev/null || true
29
- fi
30
-
31
- # Generate or reuse session ID
32
- if [[ -z "${CODEX_SESSION_ID:-}" ]]; then
33
- export CODEX_SESSION_ID="$(date +%s%N | shasum | head -c 8)"
34
- fi
35
-
36
- # Join event bus (using fixed session ID)
37
- # Export our PID so bus.sh registers the correct process
38
- export UFOO_PARENT_PID=$$
39
- NICKNAME="${UFOO_NICKNAME:-}"
40
- SUBSCRIBER=$(ufoo bus join "$CODEX_SESSION_ID" codex "$NICKNAME" 2>/dev/null | tail -1)
41
-
42
- # Auto-start project-level daemon (if not running)
43
- DAEMON_STATUS=""
44
- PID_FILE=".ufoo/bus/.daemon.pid"
45
- if [[ -f "$PID_FILE" ]]; then
46
- existing="$(cat "$PID_FILE" 2>/dev/null || true)"
47
- if [[ -n "$existing" ]] && kill -0 "$existing" 2>/dev/null; then
48
- DAEMON_STATUS="running"
49
- else
50
- ufoo bus daemon --daemon &>/dev/null && DAEMON_STATUS="started"
51
- fi
52
- else
53
- ufoo bus daemon --daemon &>/dev/null && DAEMON_STATUS="started"
54
- fi
55
-
56
- # Show banner
57
- if type show_banner &>/dev/null; then
58
- show_banner "codex" "$CODEX_SESSION_ID" "$SUBSCRIBER" "$DAEMON_STATUS"
59
- else
60
- echo "[ucodex] Connected -> $SUBSCRIBER"
61
- echo "[ucodex] Daemon: $DAEMON_STATUS"
62
- echo ""
63
- fi
64
-
65
- exec codex "$@"
@@ -1,185 +0,0 @@
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