create-merlin-brain 3.2.0 → 3.3.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 (48) hide show
  1. package/bin/install.cjs +125 -12
  2. package/bin/merlin-cli.cjs +26 -0
  3. package/files/agents/code-organization-supervisor.md +1 -0
  4. package/files/agents/context-guardian.md +1 -0
  5. package/files/agents/docs-keeper.md +2 -1
  6. package/files/agents/dry-refactor.md +2 -1
  7. package/files/agents/elite-code-refactorer.md +1 -0
  8. package/files/agents/hardening-guard.md +2 -1
  9. package/files/agents/implementation-dev.md +2 -1
  10. package/files/agents/merlin-api-designer.md +1 -0
  11. package/files/agents/merlin-codebase-mapper.md +1 -0
  12. package/files/agents/merlin-debugger.md +1 -0
  13. package/files/agents/merlin-executor.md +1 -0
  14. package/files/agents/merlin-frontend.md +1 -0
  15. package/files/agents/merlin-integration-checker.md +1 -0
  16. package/files/agents/merlin-migrator.md +1 -0
  17. package/files/agents/merlin-milestone-auditor.md +1 -0
  18. package/files/agents/merlin-performance.md +1 -0
  19. package/files/agents/merlin-planner.md +2 -0
  20. package/files/agents/merlin-researcher.md +1 -0
  21. package/files/agents/merlin-reviewer.md +2 -0
  22. package/files/agents/merlin-security.md +2 -0
  23. package/files/agents/merlin-verifier.md +2 -0
  24. package/files/agents/merlin-work-verifier.md +1 -0
  25. package/files/agents/merlin.md +1 -0
  26. package/files/agents/ops-railway.md +2 -1
  27. package/files/agents/orchestrator-retrofit.md +1 -0
  28. package/files/agents/product-spec.md +3 -1
  29. package/files/agents/system-architect.md +3 -1
  30. package/files/agents/tests-qa.md +2 -1
  31. package/files/hooks/agent-sync.sh +44 -0
  32. package/files/hooks/lib/analytics.sh +74 -0
  33. package/files/hooks/lib/sights-check.sh +58 -0
  34. package/files/hooks/post-edit-logger.sh +33 -0
  35. package/files/hooks/pre-edit-sights-check.sh +38 -0
  36. package/files/hooks/session-end.sh +27 -0
  37. package/files/hooks/session-start.sh +31 -0
  38. package/files/hooks/stop-check.md +14 -0
  39. package/files/hooks/subagent-context.sh +36 -0
  40. package/files/hooks/task-completed-verify.md +14 -0
  41. package/files/hooks/task-completed-verify.sh +46 -0
  42. package/files/hooks/teammate-idle-verify.sh +41 -0
  43. package/files/loop/lib/sights.sh +15 -4
  44. package/files/loop/lib/teams.sh +143 -0
  45. package/files/loop/merlin-loop.sh +16 -0
  46. package/files/merlin/agents-sync.sh +163 -0
  47. package/files/merlin/analytics.sh +159 -0
  48. package/package.json +1 -1
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env bash
2
+ # Merlin CLI: agents-sync.sh — sync, list, info for Merlin agents
3
+ # Usage: merlin agents [sync|list|info <name>]
4
+ set -euo pipefail
5
+
6
+ AGENTS_DIR="${HOME}/.claude/agents"
7
+ MERLIN_DIR="${HOME}/.claude/merlin"
8
+ LAST_SYNC="${MERLIN_DIR}/.last-agent-sync"
9
+ API_URL="${MERLIN_API_URL:-https://api.merlin.build}"
10
+
11
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
12
+ CYAN='\033[0;36m'; MAGENTA='\033[0;35m'; BOLD='\033[1m'
13
+ DIM='\033[2m'; RESET='\033[0m'
14
+
15
+ die() { echo -e "${RED}Error: $*${RESET}" >&2; exit 1; }
16
+
17
+ file_hash() {
18
+ md5sum "${1}" 2>/dev/null | cut -c1-8 || md5 -q "${1}" 2>/dev/null | cut -c1-8 || echo "unknown"
19
+ }
20
+
21
+ json_field() {
22
+ echo "${1}" | python3 -c "import sys,json;print(json.load(sys.stdin).get('${2}',''))" 2>/dev/null || echo ""
23
+ }
24
+
25
+ json_agent_entry() {
26
+ echo "${1}" | python3 -c "
27
+ import sys,json
28
+ data=json.load(sys.stdin)
29
+ for a in data.get('agents',[]):
30
+ if a['name']=='${2}': print(json.dumps(a)); break
31
+ " 2>/dev/null || echo ""
32
+ }
33
+
34
+ cmd_sync() {
35
+ echo -e "\n${MAGENTA}${BOLD} 🔮 Merlin Agent Sync${RESET}\n"
36
+ echo -e "${DIM} Checking ${API_URL} for updates...${RESET}\n"
37
+ [ -d "${AGENTS_DIR}" ] || mkdir -p "${AGENTS_DIR}"
38
+
39
+ local installed="" count=0
40
+ for f in "${AGENTS_DIR}"/*.md; do
41
+ [ -f "${f}" ] || continue
42
+ local name hash
43
+ name=$(basename "${f}" .md); hash=$(file_hash "${f}")
44
+ installed="${installed}${name}:${hash},"; count=$((count + 1))
45
+ done
46
+ echo -e " ${DIM}Installed: ${count} agents${RESET}"
47
+
48
+ local response
49
+ response=$(curl -s --max-time 10 "${API_URL}/api/agents-sync/check?installed=${installed}" 2>/dev/null) || die "Cannot reach ${API_URL}"
50
+ local stale_count
51
+ stale_count=$(json_field "${response}" "count")
52
+
53
+ if [ "${stale_count:-0}" = "0" ]; then
54
+ echo -e "\n ${GREEN}✓ All agents are up to date!${RESET}\n"
55
+ date +%s > "${LAST_SYNC}"; return 0
56
+ fi
57
+
58
+ echo -e " ${YELLOW}${stale_count} agent(s) need updating${RESET}\n"
59
+ local stale_names
60
+ stale_names=$(echo "${response}" | python3 -c "
61
+ import sys,json
62
+ for a in json.load(sys.stdin).get('stale',[]): print(a['name'])
63
+ " 2>/dev/null) || die "Failed to parse response"
64
+
65
+ local updated=0 failed=0
66
+ for agent_name in ${stale_names}; do
67
+ printf " Syncing %-35s " "${agent_name}..."
68
+ local content md_content
69
+ content=$(curl -s --max-time 10 "${API_URL}/api/agents-sync/${agent_name}" 2>/dev/null) || { echo -e "${RED}FAIL${RESET}"; failed=$((failed+1)); continue; }
70
+ md_content=$(json_field "${content}" "content")
71
+ if [ -n "${md_content}" ]; then
72
+ echo "${md_content}" > "${AGENTS_DIR}/${agent_name}.md"
73
+ echo -e "${GREEN}✓${RESET}"; updated=$((updated+1))
74
+ else
75
+ echo -e "${RED}EMPTY${RESET}"; failed=$((failed+1))
76
+ fi
77
+ done
78
+
79
+ echo -e "\n ${GREEN}Updated: ${updated}${RESET}"
80
+ [ "${failed}" -gt 0 ] && echo -e " ${RED}Failed: ${failed}${RESET}"
81
+ echo ""; date +%s > "${LAST_SYNC}"
82
+ }
83
+
84
+ cmd_list() {
85
+ echo -e "\n${MAGENTA}${BOLD} 🔮 Merlin Agents${RESET}\n"
86
+ [ -d "${AGENTS_DIR}" ] || die "No agents directory at ${AGENTS_DIR}"
87
+
88
+ local manifest=""
89
+ manifest=$(curl -s --max-time 5 "${API_URL}/api/agents-sync" 2>/dev/null) || manifest=""
90
+
91
+ local count=0
92
+ for f in "${AGENTS_DIR}"/*.md; do
93
+ [ -f "${f}" ] || continue
94
+ local name hash
95
+ name=$(basename "${f}" .md); hash=$(file_hash "${f}"); count=$((count+1))
96
+
97
+ local icon="${GREEN}●${RESET}"
98
+ if [ -n "${manifest}" ]; then
99
+ local entry cloud_hash
100
+ entry=$(json_agent_entry "${manifest}" "${name}")
101
+ if [ -n "${entry}" ]; then
102
+ cloud_hash=$(json_field "${entry}" "hash")
103
+ [ "${cloud_hash}" != "${hash}" ] && icon="${YELLOW}●${RESET}"
104
+ fi
105
+ fi
106
+
107
+ local desc
108
+ desc=$(grep -m1 '^[^#<-]' "${f}" 2>/dev/null | head -c 60 || echo "")
109
+ printf " ${icon} ${BOLD}%-35s${RESET} ${DIM}%s${RESET}\n" "${name}" "${desc}"
110
+ done
111
+
112
+ echo -e "\n ${DIM}Total: ${count} agents${RESET}"
113
+ echo -e " ${GREEN}●${RESET} up to date ${YELLOW}●${RESET} update available\n"
114
+ }
115
+
116
+ cmd_info() {
117
+ local name="${1:-}"
118
+ [ -n "${name}" ] || die "Usage: merlin agents info <name>"
119
+ local agent_file="${AGENTS_DIR}/${name}.md"
120
+ [ -f "${agent_file}" ] || die "Agent '${name}' not found"
121
+
122
+ echo -e "\n${MAGENTA}${BOLD} 🔮 Agent: ${name}${RESET}\n"
123
+
124
+ local hash lines size
125
+ hash=$(file_hash "${agent_file}")
126
+ lines=$(wc -l < "${agent_file}" | tr -d ' ')
127
+ size=$(wc -c < "${agent_file}" | tr -d ' ')
128
+
129
+ echo -e " ${CYAN}Hash:${RESET} ${hash}"
130
+ echo -e " ${CYAN}Lines:${RESET} ${lines}"
131
+ echo -e " ${CYAN}Size:${RESET} ${size} bytes"
132
+ echo -e " ${CYAN}Path:${RESET} ${agent_file}"
133
+
134
+ local cloud_info cloud_hash
135
+ cloud_info=$(curl -s --max-time 5 "${API_URL}/api/agents-sync/${name}" 2>/dev/null) || cloud_info=""
136
+ if [ -n "${cloud_info}" ]; then
137
+ cloud_hash=$(json_field "${cloud_info}" "hash")
138
+ if [ -n "${cloud_hash}" ]; then
139
+ [ "${cloud_hash}" = "${hash}" ] && echo -e " ${CYAN}Cloud:${RESET} ${GREEN}up to date${RESET}" || echo -e " ${CYAN}Cloud:${RESET} ${YELLOW}update available${RESET}"
140
+ fi
141
+ else
142
+ echo -e " ${CYAN}Cloud:${RESET} ${DIM}unavailable${RESET}"
143
+ fi
144
+
145
+ echo -e "\n ${BOLD}Preview:${RESET}"
146
+ head -5 "${agent_file}" | sed 's/^/ /'
147
+ echo -e " ${DIM}...${RESET}\n"
148
+ }
149
+
150
+ subcmd="${1:-help}"
151
+ shift 2>/dev/null || true
152
+
153
+ case "${subcmd}" in
154
+ sync) cmd_sync ;;
155
+ list) cmd_list ;;
156
+ info) cmd_info "${1:-}" ;;
157
+ help|--help|-h)
158
+ echo -e "\n${BOLD}Usage:${RESET} merlin agents <command>\n"
159
+ echo -e " ${CYAN}sync${RESET} Sync agents from merlin.build cloud"
160
+ echo -e " ${CYAN}list${RESET} Show installed agents with update status"
161
+ echo -e " ${CYAN}info${RESET} <name> Show details for one agent\n" ;;
162
+ *) die "Unknown: ${subcmd}. Use: sync, list, info" ;;
163
+ esac
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Merlin Analytics CLI Viewer
4
+ # View, compare, upload, and clean session analytics data.
5
+ # Requires jq for JSON processing.
6
+ #
7
+ set -euo pipefail
8
+
9
+ ANALYTICS_DIR="${HOME}/.claude/merlin/analytics"
10
+ API_URL="${MERLIN_API_URL:-https://auth.merlin.build}"
11
+ API_KEY="${MERLIN_API_KEY:-}"
12
+
13
+ # Colors
14
+ R='\033[0m' G='\033[32m' B='\033[34m' Y='\033[33m' DIM='\033[2m' BOLD='\033[1m'
15
+ GRN() { printf "${G}%s${R}" "$1"; }
16
+ BLU() { printf "${B}%s${R}" "$1"; }
17
+ GRY() { printf "${DIM}%s${R}" "$1"; }
18
+
19
+ check_jq() {
20
+ if ! command -v jq >/dev/null 2>&1; then
21
+ echo "Error: jq is required. Install: brew install jq (macOS) or apt install jq (Linux)"
22
+ exit 1
23
+ fi
24
+ }
25
+
26
+ fmt_duration() {
27
+ local s="${1:-0}"
28
+ if [ "$s" -ge 3600 ]; then printf "%dh %dm" $((s/3600)) $(((s%3600)/60))
29
+ elif [ "$s" -ge 60 ]; then printf "%dm %ds" $((s/60)) $((s%60))
30
+ else printf "%ds" "$s"; fi
31
+ }
32
+
33
+ type_color() {
34
+ case "${1:-raw}" in
35
+ loop) GRN "$1" ;; hooks) BLU "$1" ;; *) GRY "$1" ;;
36
+ esac
37
+ }
38
+
39
+ # ─── Summary: Show last N sessions ──────────────────────────────────────────
40
+ cmd_summary() {
41
+ check_jq
42
+ local limit="${1:-10}"
43
+ local files
44
+ files=$(find "${ANALYTICS_DIR}" -name 'session-*.json' -not -name '*.uploaded.json' 2>/dev/null | sort -r | head -n "$limit")
45
+
46
+ if [ -z "$files" ]; then
47
+ echo "No analytics sessions found in ${ANALYTICS_DIR}"
48
+ return
49
+ fi
50
+
51
+ printf "${BOLD}%-20s %-6s %8s %6s %5s %5s${R}\n" "DATE" "TYPE" "DURATION" "SIGHT" "FILES" "TASKS"
52
+ printf "%-20s %-6s %8s %6s %5s %5s\n" "────────────────────" "──────" "────────" "──────" "─────" "─────"
53
+
54
+ echo "$files" | while IFS= read -r f; do
55
+ [ -f "$f" ] || continue
56
+ local row
57
+ row=$(jq -r '[
58
+ .startTime // "unknown",
59
+ .sessionType // "raw",
60
+ (.durationSeconds // 0 | tostring),
61
+ (.summary.sightsCallCount // (.events | map(select(.type=="sights_call")) | length) // 0 | tostring),
62
+ (.summary.filesChanged // (.events | map(select(.type=="file_edit")) | length) // 0 | tostring),
63
+ (.summary.tasksCompleted // (.events | map(select(.type=="task_complete")) | length) // 0 | tostring)
64
+ ] | @tsv' "$f" 2>/dev/null) || continue
65
+ local dt stype dur sight fchg tasks
66
+ IFS=$'\t' read -r dt stype dur sight fchg tasks <<< "$row"
67
+ local date_str="${dt:0:16}"
68
+ local dur_fmt; dur_fmt=$(fmt_duration "$dur")
69
+ printf "%-20s " "$date_str"
70
+ type_color "$stype"
71
+ printf "%*s %8s %6s %5s %5s\n" "$((6-${#stype}))" "" "$dur_fmt" "$sight" "$fchg" "$tasks"
72
+ done
73
+ }
74
+
75
+ # ─── Compare: Average metrics by session type ───────────────────────────────
76
+ cmd_compare() {
77
+ check_jq
78
+ local files
79
+ files=$(find "${ANALYTICS_DIR}" -name 'session-*.json' 2>/dev/null)
80
+ [ -z "$files" ] && { echo "No sessions found."; return; }
81
+
82
+ printf "\n${BOLD}Session Type Comparison${R}\n"
83
+ printf "%-8s %6s %8s %6s %5s %5s\n" "TYPE" "COUNT" "AVG DUR" "SIGHTS" "FILES" "TASKS"
84
+ printf "%-8s %6s %8s %6s %5s %5s\n" "────────" "──────" "────────" "──────" "─────" "─────"
85
+
86
+ for stype in loop hooks raw; do
87
+ local stats
88
+ stats=$(echo "$files" | xargs -I{} jq -r "select(.sessionType == \"$stype\") |
89
+ [(.durationSeconds // 0), (.summary.sightsCallCount // 0), (.summary.filesChanged // 0), (.summary.tasksCompleted // 0)]
90
+ | @tsv" {} 2>/dev/null | awk -F'\t' '
91
+ { n++; d+=$1; s+=$2; f+=$3; t+=$4 }
92
+ END { if(n>0) printf "%d\t%d\t%d\t%d\t%d", n, d/n, s/n, f/n, t/n; else printf "0\t0\t0\t0\t0" }
93
+ ')
94
+ local cnt avg_d avg_s avg_f avg_t
95
+ IFS=$'\t' read -r cnt avg_d avg_s avg_f avg_t <<< "$stats"
96
+ [ "$cnt" -eq 0 ] && continue
97
+ local dur_fmt; dur_fmt=$(fmt_duration "$avg_d")
98
+ type_color "$stype"
99
+ printf "%*s %6s %8s %6s %5s %5s\n" "$((8-${#stype}))" "" "$cnt" "$dur_fmt" "$avg_s" "$avg_f" "$avg_t"
100
+ done
101
+ echo ""
102
+ }
103
+
104
+ # ─── Upload: Send sessions to Merlin API ────────────────────────────────────
105
+ cmd_upload() {
106
+ check_jq
107
+ [ -z "${API_KEY}" ] && { echo "Error: MERLIN_API_KEY not set. Get your key at merlin.build/settings"; exit 1; }
108
+
109
+ local files
110
+ files=$(find "${ANALYTICS_DIR}" -name 'session-*.json' -not -name '*.uploaded.json' 2>/dev/null)
111
+ [ -z "$files" ] && { echo "No new sessions to upload."; return; }
112
+
113
+ local count; count=$(echo "$files" | wc -l | tr -d ' ')
114
+ echo "Uploading ${count} session(s)..."
115
+
116
+ local sessions="[]"
117
+ while IFS= read -r f; do
118
+ [ -f "$f" ] || continue
119
+ sessions=$(jq --slurpfile s "$f" '. + $s' <<< "$sessions" 2>/dev/null) || continue
120
+ done <<< "$files"
121
+
122
+ local resp
123
+ resp=$(curl -sS -X POST "${API_URL}/api/analytics/upload" \
124
+ -H "Authorization: Bearer ${API_KEY}" \
125
+ -H "Content-Type: application/json" \
126
+ -d "{\"sessions\": ${sessions}}" 2>&1) || { echo "Upload failed: $resp"; return 1; }
127
+
128
+ # Mark uploaded
129
+ echo "$files" | while IFS= read -r f; do
130
+ mv "$f" "${f%.json}.uploaded.json" 2>/dev/null || true
131
+ done
132
+ echo "Uploaded ${count} session(s) successfully."
133
+ }
134
+
135
+ # ─── Clean: Remove old sessions ─────────────────────────────────────────────
136
+ cmd_clean() {
137
+ local days="${1:-30}"
138
+ local count=0
139
+ find "${ANALYTICS_DIR}" -name 'session-*.json' -mtime +"$days" 2>/dev/null | while IFS= read -r f; do
140
+ rm -f "$f" && count=$((count + 1))
141
+ done
142
+ find "${ANALYTICS_DIR}" -name '*.uploaded.json' -mtime +"$days" 2>/dev/null -delete
143
+ echo "Cleaned sessions older than ${days} days."
144
+ }
145
+
146
+ # ─── Main ────────────────────────────────────────────────────────────────────
147
+ case "${1:-summary}" in
148
+ summary) cmd_summary "${2:-10}" ;;
149
+ compare) cmd_compare ;;
150
+ upload) cmd_upload ;;
151
+ clean) cmd_clean "${2:-30}" ;;
152
+ *)
153
+ echo "Usage: merlin analytics [summary|compare|upload|clean]"
154
+ echo " summary [N] Show last N sessions (default 10)"
155
+ echo " compare Compare metrics by session type"
156
+ echo " upload Upload sessions to merlin.build"
157
+ echo " clean [days] Remove sessions older than N days (default 30)"
158
+ ;;
159
+ esac
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-merlin-brain",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Merlin - The Ultimate AI Brain for Claude Code. One install: workflows, agents, loop, and Sights MCP server.",
5
5
  "type": "module",
6
6
  "main": "./dist/server/index.js",