u-foo 1.0.2 → 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 +1995 -263
  42. package/src/cli.js +658 -95
  43. package/src/config.js +23 -4
  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 +38 -3
  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
@@ -29,18 +29,28 @@ if [[ ! -d ".ufoo/bus" ]]; then
29
29
  fi
30
30
  ```
31
31
 
32
- ### 2. Check if already joined bus
32
+ ### 2. Get or create subscriber ID
33
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"):
34
+ **IMPORTANT**: Always check for existing subscriber ID first to avoid creating duplicates.
37
35
 
38
36
  ```bash
39
- SUBSCRIBER=$(ufoo bus join | tail -n 1)
40
- echo "Joined event bus: $SUBSCRIBER"
41
- # Example output: codex:0e293156 (nickname: codex-1)
37
+ # Check environment variable first (set by launcher/daemon)
38
+ if [ -n "$UFOO_SUBSCRIBER_ID" ]; then
39
+ SUBSCRIBER="$UFOO_SUBSCRIBER_ID"
40
+ echo "Using existing subscriber ID: $SUBSCRIBER"
41
+ else
42
+ # Not launched via uclaude/ucodex, need to join manually
43
+ SUBSCRIBER=$(ufoo bus join | tail -n 1)
44
+ echo "Joined event bus: $SUBSCRIBER"
45
+ # Example output: codex:0e293156 (nickname: codex-1)
46
+ fi
42
47
  ```
43
48
 
49
+ **Why this matters**:
50
+ - `uclaude`/`ucodex` automatically set `UFOO_SUBSCRIBER_ID` during launch
51
+ - Re-joining creates a new ID, causing message loss
52
+ - Always reuse existing ID when available
53
+
44
54
  To join with a custom nickname:
45
55
 
46
56
  ```bash
@@ -127,7 +137,7 @@ Output (now includes nicknames):
127
137
  ```
128
138
  === Event Bus Status ===
129
139
  My identity: claude-code:xyz789
130
- Online subscribers: 2
140
+ Online agents: 2
131
141
  - claude-code:abc123 (architect)
132
142
  - claude-code:xyz789 (dev-lead)
133
143
  Recent events: 5
@@ -1,24 +1,12 @@
1
1
  # context
2
2
 
3
- A collaboration protocol that constrains AI behavior through explicit, versioned files.
3
+ Decision-only context module for ufoo.
4
4
 
5
- ## Why
6
-
7
- AI coding agents (Claude Code, Codex, Cursor, etc.) have two fundamental problems:
8
-
9
- 1. **No shared memory** Decisions made in one session are invisible to other agents or future sessions. Each agent starts fresh, repeating mistakes or contradicting prior work.
10
-
11
- This protocol solves it by treating decisions and context as files:
12
- - **Decisions as files** — Persistent, versioned, reviewable. All agents read the same truth.
13
-
14
- ## Core Principle
15
-
16
- ```
17
- Global context defines the law.
18
- Project context defines the truth.
19
- ```
20
-
21
- **Files are truth, not memory.**
5
+ Purpose:
6
+ - Persist decisions in project workspaces
7
+ - Keep decision format canonical in `uctx` skill
8
+
9
+ Bus handles communication; context handles durable decision truth.
22
10
 
23
11
  ## Quick Start
24
12
 
@@ -34,42 +22,32 @@ This repository is the `context` module. The recommended entrypoint is `ufoo`.
34
22
 
35
23
  Global modules live under `~/.ufoo/modules/`.
36
24
 
37
- ### Project: `<project>/.context/` (writable)
25
+ ### Project: `<project>/.ufoo/context/` (writable)
38
26
 
39
27
  ```
40
- .context/
41
- ├── README.md # Entry point / how to use this context
42
- ├── SYSTEM.md # Project architecture
43
- ├── CONSTRAINTS.md # Non-negotiable rules
44
- ├── ASSUMPTIONS.md # Current assumptions
45
- ├── TERMINOLOGY.md # Shared vocabulary
46
- └── DECISIONS/ # Append-only log
28
+ .ufoo/context/
29
+ ├── decisions/ # Append-only decision log (decision-only mode)
30
+ └── decisions.jsonl # Decision index (ts/type/file/author)
47
31
  ```
48
32
 
49
- Must be in Git. Must be reviewable. Truth.
33
+ Should be in the project workspace and writable by agents.
34
+ Versioning is optional but recommended for auditability.
50
35
 
51
- ## Protocol Structure
36
+ ## Module Structure
52
37
 
53
38
  ```
54
39
  context/ # This repo
55
- ├── SYSTEM.md # Protocol definition
56
- ├── RULES.md # Collaboration rules
57
- ├── CONSTRAINTS.md # Protocol constraints
58
- ├── DECISION-PROTOCOL.md # How to write decisions
59
- ├── HANDOFF.md # Session handoff rules
60
- ├── CONTEXT-STRUCTURE.md # Project structure spec
61
- ├── TEMPLATES/ # AI behavior constraint
62
- ├── SKILLS/ # tool skill docs (module-local)
63
- └── .context/ # Local project context for this repo (ignored; not part of protocol distribution)
40
+ ├── README.md # This file
41
+ ├── SKILLS/uctx/SKILL.md # Canonical decision format + workflow
42
+ └── .ufoo/context/ # Local project context for this repo (ignored; not part of protocol distribution)
64
43
  ```
65
44
 
66
45
  ## For AI Agents
67
46
 
68
47
  1. Read installed module from `~/.ufoo/modules/context/`
69
- 2. Read/write context from `<project>/.context/`
48
+ 2. Read/write decisions in `<project>/.ufoo/context/decisions/`
70
49
  3. **Never write to global** — only to project
71
- 4. When unsure, write a decision
72
- 5. Do not modify TEMPLATES/ without a decision
50
+ 4. Follow the decision format in `SKILLS/uctx/SKILL.md`
73
51
 
74
52
  ## Validate
75
53
 
@@ -14,12 +14,72 @@ Fast context check for daily use. Run at session start or anytime.
14
14
 
15
15
  Pre-flight reminder:
16
16
  - If the user is asking for evaluation/recommendation/plan, write a decision before replying.
17
+ Use: `ufoo ctx decisions new "<Title>"`
18
+
19
+ ## Decision format (canonical)
20
+
21
+ Project context is decision-only. Decisions live at:
22
+ `<project>/.ufoo/context/decisions/`
23
+
24
+ Decision index (JSONL):
25
+ `<project>/.ufoo/context/decisions.jsonl`
26
+
27
+ Generate/update the index:
28
+ ```bash
29
+ ufoo ctx decisions index
30
+ ```
31
+
32
+ Each JSONL row includes:
33
+ - `ts` (ISO timestamp)
34
+ - `type` (`decision` or `decision_status`)
35
+ - `file` (decision filename)
36
+ - `author` (decision author or resolver)
37
+
38
+ Create a new decision (recommended before replying when required):
39
+ ```bash
40
+ ufoo ctx decisions new "Short Title"
41
+ ```
42
+
43
+ **File naming:** `NNNN-short-title.md` (4-digit prefix + kebab-case slug).
44
+
45
+ **Template for new decisions:**
46
+ ```yaml
47
+ ---
48
+ status: open
49
+ ---
50
+ # DECISION NNNN: <Title>
51
+
52
+ Date: YYYY-MM-DD
53
+ Author: <agent>
54
+
55
+ Context:
56
+ What led to this decision?
57
+
58
+ Decision:
59
+ What is now considered true?
60
+
61
+ Implications:
62
+ What must follow from this?
63
+ ```
64
+
65
+ **Status updates (only edit frontmatter):**
66
+ ```yaml
67
+ ---
68
+ status: resolved
69
+ resolved_by: <agent>
70
+ resolved_at: YYYY-MM-DD
71
+ ---
72
+ ```
73
+
74
+ Rules:
75
+ - Decisions are append-only. Do not rewrite past content.
76
+ - Only update the frontmatter when changing status.
17
77
 
18
78
  ## Workflow
19
79
 
20
80
  ### 1. Verify structure exists
21
81
 
22
- Check `.ufoo/context/` exists. If missing, tell user to run `uinit` (ufoo init CLI).
82
+ Check `.ufoo/context/decisions/` exists. If missing, tell user to run `ufoo init`.
23
83
 
24
84
  ### 2. List all decisions
25
85
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u-foo",
3
- "version": "1.0.2",
3
+ "version": "1.0.6",
4
4
  "description": "Multi-Agent Workspace Protocol. Just add u. claude → uclaude, codex → ucodex.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://ufoo.dev",
@@ -17,13 +17,14 @@
17
17
  ],
18
18
  "bin": {
19
19
  "ufoo": "bin/ufoo.js",
20
- "uclaude": "bin/uclaude",
21
- "ucodex": "bin/ucodex"
20
+ "uclaude": "bin/uclaude.js",
21
+ "ucodex": "bin/ucodex.js"
22
22
  },
23
23
  "files": [
24
24
  "bin/",
25
25
  "src/",
26
26
  "scripts/",
27
+ "SKILLS/",
27
28
  "modules/",
28
29
  "LICENSE",
29
30
  "README.md"
@@ -32,9 +33,20 @@
32
33
  "engines": {
33
34
  "node": ">=18"
34
35
  },
36
+ "scripts": {
37
+ "postinstall": "node scripts/postinstall.js",
38
+ "test": "jest",
39
+ "test:watch": "jest --watch",
40
+ "test:coverage": "jest --coverage"
41
+ },
35
42
  "dependencies": {
36
43
  "blessed": "^0.1.81",
37
44
  "chalk": "^4.1.2",
38
- "commander": "^13.1.0"
45
+ "commander": "^13.1.0",
46
+ "gray-matter": "^4.0.3",
47
+ "node-pty": "^1.1.0"
48
+ },
49
+ "devDependencies": {
50
+ "jest": "^30.2.0"
39
51
  }
40
52
  }
@@ -0,0 +1,46 @@
1
+ # 已迁移到 JavaScript 的脚本
2
+
3
+ 这些 bash 脚本已经完全迁移到 JavaScript 模块。
4
+
5
+ ## 迁移对照表
6
+
7
+ | Bash 脚本 | JavaScript 模块 | 状态 |
8
+ |----------|----------------|------|
9
+ | `bus.sh` | `src/bus/index.js` + 7个子模块 | ✅ 完全替换 |
10
+ | `bus-daemon.sh` | `src/bus/daemon.js` | ✅ 完全替换 |
11
+ | `bus-inject.sh` | `src/bus/inject.js` | ✅ 完全替换 |
12
+ | `status.sh` | `src/status/index.js` | ✅ 完全替换 |
13
+ | `skills.sh` | `src/skills/index.js` | ✅ 完全替换 |
14
+ | `init.sh` | `src/init/index.js` | ✅ 完全替换 |
15
+
16
+ ## 迁移完成日期
17
+
18
+ 2026-02-04
19
+
20
+ ## 使用方式
21
+
22
+ 所有命令接口保持不变,直接使用即可:
23
+
24
+ ```bash
25
+ ufoo bus status
26
+ ufoo status
27
+ ufoo skills list
28
+ ufoo init
29
+ ```
30
+
31
+ ## 保留原因
32
+
33
+ 这些脚本被归档保留用于:
34
+ 1. 历史参考
35
+ 2. 性能对比
36
+ 3. 回退备份(如有必要)
37
+
38
+ ## 性能对比
39
+
40
+ | 指标 | Bash | JavaScript | 差异 |
41
+ |------|------|------------|------|
42
+ | 消息延迟 | 45ms | 51ms | +13% |
43
+ | 并发安全 | ✅ | ✅ | 相同 |
44
+ | 功能完整 | 100% | 100% | 相同 |
45
+
46
+ 性能差异在可接受范围内(<15%),换取更好的可维护性和跨平台支持。
@@ -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
@@ -54,18 +54,50 @@ else
54
54
  fi
55
55
 
56
56
  # Detect terminal type from tty path or environment
57
- # iTerm2 uses "write text", Terminal.app uses "do script"
57
+ # Priority: 1) tmux (via stored pane ID), 2) iTerm2, 3) Terminal.app
58
+
59
+ # Try to get tmux pane info from bus.json (more reliable than tty lookup)
60
+ TMUX_PANE=""
61
+ if command -v jq &>/dev/null && [[ -f "$BUS_DIR/bus.json" ]]; then
62
+ TMUX_PANE=$(jq -r --arg id "$SUBSCRIBER" '.subscribers[$id].tmux_pane // ""' "$BUS_DIR/bus.json" 2>/dev/null || echo "")
63
+ fi
64
+
65
+ # Check if this is a tmux session
66
+ USE_TMUX=0
67
+ if [[ -n "$TMUX_PANE" ]] && command -v tmux &>/dev/null; then
68
+ # Verify the pane still exists
69
+ if tmux list-panes -a -F '#{pane_id}' 2>/dev/null | grep -q "^${TMUX_PANE}$"; then
70
+ USE_TMUX=1
71
+ else
72
+ echo "[inject] Warning: tmux pane $TMUX_PANE no longer exists" >&2
73
+ # Fallback: try to find pane by tty
74
+ TMUX_PANE=$(tmux list-panes -a -F '#{pane_id} #{pane_tty}' 2>/dev/null | grep " ${TARGET_TTY}$" | awk '{print $1}' | head -1)
75
+ if [[ -n "$TMUX_PANE" ]]; then
76
+ USE_TMUX=1
77
+ echo "[inject] Found alternative tmux pane by tty: $TMUX_PANE" >&2
78
+ fi
79
+ fi
80
+ fi
58
81
 
59
82
  # Check if iTerm2 is running and has this tty
60
83
  USE_ITERM=0
61
- if pgrep -q "iTerm2"; then
84
+ if [[ "$USE_TMUX" == "0" ]] && pgrep -q "iTerm2"; then
62
85
  # Check if iTerm2 has a session with this tty
63
86
  if osascript -e 'tell application "iTerm2" to get tty of current session of current window' 2>/dev/null | grep -q "$TARGET_TTY"; then
64
87
  USE_ITERM=1
65
88
  fi
66
89
  fi
67
90
 
68
- if [[ "$USE_ITERM" == "1" ]]; then
91
+ if [[ "$USE_TMUX" == "1" ]]; then
92
+ echo "[inject] Using tmux send-keys method for pane: $TMUX_PANE"
93
+ # Only send C-c if UFOO_INJECT_INTERRUPT is set (to avoid disrupting running agents)
94
+ if [[ "${UFOO_INJECT_INTERRUPT:-0}" == "1" ]]; then
95
+ echo "[inject] Sending interrupt (C-c) before command"
96
+ tmux send-keys -t "$TMUX_PANE" C-c 2>/dev/null || true
97
+ sleep 0.1
98
+ fi
99
+ tmux send-keys -t "$TMUX_PANE" "$INJECT_CMD" Enter
100
+ elif [[ "$USE_ITERM" == "1" ]]; then
69
101
  echo "[inject] Using iTerm2 write text method"
70
102
  osascript <<EOF
71
103
  tell application "iTerm2"
@@ -295,6 +295,7 @@ cmd_join() {
295
295
  --arg ts "$(get_timestamp)" \
296
296
  --arg pid "${UFOO_PARENT_PID:-$PPID}" \
297
297
  --arg cwd "$(pwd)" \
298
+ --arg launch_mode "${UFOO_LAUNCH_MODE:-unknown}" \
298
299
  '
299
300
  .subscribers[$name] = {
300
301
  "agent_type": $agent_type,
@@ -306,7 +307,8 @@ cmd_join() {
306
307
  "pid": ($pid | tonumber),
307
308
  "joined_at": $ts,
308
309
  "status": "active",
309
- "last_heartbeat": $ts
310
+ "last_heartbeat": $ts,
311
+ "launch_mode": $launch_mode
310
312
  }
311
313
  |
312
314
  .agent_types[$agent_type].instances = (
package/scripts/banner.sh CHANGED
@@ -1,89 +1,2 @@
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
1
+ #!/bin/bash
2
+ exit 1
@@ -0,0 +1,59 @@
1
+ /* eslint-disable no-console */
2
+ const path = require("path");
3
+ const fs = require("fs");
4
+ const { spawnSync } = require("child_process");
5
+
6
+ function run(args) {
7
+ const bin = path.join(__dirname, "..", "bin", "ufoo.js");
8
+ const res = spawnSync(process.execPath, [bin, ...args], {
9
+ stdio: "ignore",
10
+ });
11
+ return res.status === 0;
12
+ }
13
+
14
+ function tryInstall(args, label) {
15
+ try {
16
+ run(args);
17
+ } catch (err) {
18
+ console.warn(`[postinstall] ${label} failed: ${err.message || String(err)}`);
19
+ }
20
+ }
21
+
22
+ // Fix node-pty spawn-helper permissions on macOS (both arm64 and x64)
23
+ function fixNodePtyPermissions() {
24
+ const platforms = ["darwin-arm64", "darwin-x64"];
25
+
26
+ for (const platform of platforms) {
27
+ try {
28
+ const spawnHelperPath = path.join(
29
+ __dirname,
30
+ "..",
31
+ "node_modules",
32
+ "node-pty",
33
+ "prebuilds",
34
+ platform,
35
+ "spawn-helper"
36
+ );
37
+
38
+ if (fs.existsSync(spawnHelperPath)) {
39
+ const stats = fs.statSync(spawnHelperPath);
40
+ // Check if executable bit is missing
41
+ if ((stats.mode & 0o111) === 0) {
42
+ fs.chmodSync(spawnHelperPath, 0o755);
43
+ console.log(`[postinstall] Fixed node-pty spawn-helper permissions (${platform})`);
44
+ }
45
+ }
46
+ } catch {
47
+ // Silently ignore errors - not critical for non-macOS or if node-pty not installed
48
+ }
49
+ }
50
+ }
51
+
52
+ fixNodePtyPermissions();
53
+
54
+ const skills = ["ufoo", "ubus", "uctx"];
55
+
56
+ for (const skill of skills) {
57
+ tryInstall(["skills", "install", skill, "--codex"], `install ${skill} skill (codex)`);
58
+ tryInstall(["skills", "install", skill], `install ${skill} skill (claude)`);
59
+ }