create-merlin-brain 5.3.3 → 5.3.5
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/bin/install.cjs +25 -5
- package/dist/server/tools/route-helpers.d.ts +6 -1
- package/dist/server/tools/route-helpers.d.ts.map +1 -1
- package/dist/server/tools/route-helpers.js +61 -3
- package/dist/server/tools/route-helpers.js.map +1 -1
- package/files/agents/android-expert.md +109 -0
- package/files/agents/animation-expert.md +88 -0
- package/files/agents/apple-swift-expert.md +91 -0
- package/files/agents/desktop-app-expert.md +91 -0
- package/files/agents/marketing-automation.md +140 -0
- package/files/agents/orchestrator.md +115 -0
- package/files/agents/ui-builder.md +108 -0
- package/files/agents/ui-designer.md +122 -0
- package/files/merlin/skills/TASK-OPTIMIZER.json +45 -0
- package/files/scripts/codex-as.sh +35 -4
- package/files/scripts/duo-codex-call.sh +29 -10
- package/files/scripts/duo-mode-read.sh +40 -40
- package/files/scripts/merlin-codex.sh +35 -2
- package/files/scripts/task-optimize.sh +44 -0
- package/files/scripts/with-timeout.sh +2 -2
- package/package.json +2 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# duo-mode-read.sh — reads duo-mode.json, applies 24h auto-expire (read-time only, never modifies file)
|
|
3
3
|
# Without --pair: prints exactly "enabled" or "disabled" to stdout
|
|
4
|
-
# With --pair: prints the pair value: "claude+codex", "claude+claude", or "none"
|
|
4
|
+
# With --pair: prints the pair value: "claude+codex", "claude+claude", "codex+codex", or "none"
|
|
5
5
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
|
|
@@ -24,48 +24,48 @@ if [[ ! -f "$STATE_FILE" ]]; then
|
|
|
24
24
|
exit 0
|
|
25
25
|
fi
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
# Parse JSON using portable bash + sed/grep (no jq, no python required)
|
|
28
|
+
enabled=$(grep -o '"enabled"[[:space:]]*:[[:space:]]*\(true\|false\)' "$STATE_FILE" 2>/dev/null | grep -o 'true\|false' || echo "false")
|
|
29
|
+
since=$(grep -o '"sinceISO"[[:space:]]*:[[:space:]]*"[^"]*"' "$STATE_FILE" 2>/dev/null | sed 's/.*"\([^"]*\)"/\1/' || echo "")
|
|
30
|
+
pair=$(grep -o '"pair"[[:space:]]*:[[:space:]]*"[^"]*"' "$STATE_FILE" 2>/dev/null | sed 's/.*"\([^"]*\)"/\1/' || echo "")
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
# If not enabled, or sinceISO is missing/null, return default
|
|
33
|
+
[[ "$enabled" != "true" ]] && { if [[ "$PAIR_MODE" == "true" ]]; then echo "none"; else echo "disabled"; fi; exit 0; }
|
|
34
|
+
[[ -z "$since" || "$since" == "null" ]] && { if [[ "$PAIR_MODE" == "true" ]]; then echo "none"; else echo "disabled"; fi; exit 0; }
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
# Check 24h expiry using portable date commands (macOS has date -j, Linux has date -d)
|
|
37
|
+
since_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$since" "+%s" 2>/dev/null || date -d "$since" "+%s" 2>/dev/null || echo "")
|
|
38
|
+
if [[ -z "$since_epoch" ]]; then
|
|
39
|
+
# Unparseable timestamp — treat as expired
|
|
40
|
+
if [[ "$PAIR_MODE" == "true" ]]; then
|
|
41
|
+
echo "none"
|
|
42
|
+
else
|
|
43
|
+
echo "disabled"
|
|
44
|
+
fi
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
sys.exit(0)
|
|
48
|
+
now_epoch=$(date +%s)
|
|
49
|
+
age=$((now_epoch - since_epoch))
|
|
49
50
|
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
# Unparseable timestamp — treat as expired
|
|
60
|
-
print("none" if pair_mode else "disabled")
|
|
61
|
-
sys.exit(0)
|
|
51
|
+
# 24 hours = 86400 seconds
|
|
52
|
+
if [[ $age -gt 86400 ]]; then
|
|
53
|
+
if [[ "$PAIR_MODE" == "true" ]]; then
|
|
54
|
+
echo "none"
|
|
55
|
+
else
|
|
56
|
+
echo "disabled"
|
|
57
|
+
fi
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
# Still enabled and within 24h
|
|
62
|
+
if [[ "$PAIR_MODE" == "true" ]]; then
|
|
64
63
|
# Legacy state (no pair field) when enabled defaults to claude+codex
|
|
65
|
-
if pair
|
|
66
|
-
|
|
67
|
-
else
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
if [[ -z "$pair" || "$pair" == "null" ]]; then
|
|
65
|
+
echo "claude+codex"
|
|
66
|
+
else
|
|
67
|
+
echo "$pair"
|
|
68
|
+
fi
|
|
69
|
+
else
|
|
70
|
+
echo "enabled"
|
|
71
|
+
fi
|
|
@@ -72,10 +72,43 @@ fi
|
|
|
72
72
|
# Otherwise prepend to the full prompt string.
|
|
73
73
|
if [ "${FIRST_ARG}" = "exec" ]; then
|
|
74
74
|
shift
|
|
75
|
-
#
|
|
75
|
+
# Parse out flags from prompt args (preserve them for codex exec)
|
|
76
|
+
CODEX_FLAGS=()
|
|
77
|
+
while [[ $# -gt 0 ]]; do
|
|
78
|
+
case "$1" in
|
|
79
|
+
-s|--sandbox|--model|--cd|-c)
|
|
80
|
+
# Flag with value
|
|
81
|
+
CODEX_FLAGS+=("$1" "$2")
|
|
82
|
+
shift 2
|
|
83
|
+
;;
|
|
84
|
+
--skip-git-repo-check|--full-auto|--dangerously-bypass-approvals-and-sandbox)
|
|
85
|
+
# Boolean flag
|
|
86
|
+
CODEX_FLAGS+=("$1")
|
|
87
|
+
shift
|
|
88
|
+
;;
|
|
89
|
+
--*=*)
|
|
90
|
+
# Flag with =value
|
|
91
|
+
CODEX_FLAGS+=("$1")
|
|
92
|
+
shift
|
|
93
|
+
;;
|
|
94
|
+
--*|-*)
|
|
95
|
+
# Unknown flag — pass through with value if next arg doesn't look like a flag
|
|
96
|
+
CODEX_FLAGS+=("$1")
|
|
97
|
+
shift
|
|
98
|
+
if [[ $# -gt 0 && "$1" != -* ]]; then
|
|
99
|
+
CODEX_FLAGS+=("$1")
|
|
100
|
+
shift
|
|
101
|
+
fi
|
|
102
|
+
;;
|
|
103
|
+
*)
|
|
104
|
+
# First non-flag arg = prompt; break and treat rest as prompt
|
|
105
|
+
break
|
|
106
|
+
;;
|
|
107
|
+
esac
|
|
108
|
+
done
|
|
76
109
|
ORIGINAL_PROMPT="${*:-}"
|
|
77
110
|
FULL_PROMPT="${MERLIN_PROMPT}"$'\n\n'"---USER---"$'\n\n'"${ORIGINAL_PROMPT}"
|
|
78
|
-
exec codex exec "${FULL_PROMPT}"
|
|
111
|
+
exec codex exec "${CODEX_FLAGS[@]}" "${FULL_PROMPT}"
|
|
79
112
|
else
|
|
80
113
|
# Treat remaining args as a single prompt string
|
|
81
114
|
ORIGINAL_PROMPT="${*:-}"
|
|
@@ -93,6 +93,14 @@ TEST_CASES = [
|
|
|
93
93
|
("React component refactor", "react", "merlin-frontend", 25),
|
|
94
94
|
("useState hook pattern", "react", "merlin-frontend", 25),
|
|
95
95
|
("Next.js server component", "react", "merlin-frontend", 25),
|
|
96
|
+
("build Android app with Kotlin", "android", "android-expert", 25),
|
|
97
|
+
("Jetpack Compose UI for Material You", "android", "android-expert", 25),
|
|
98
|
+
("iOS SwiftUI development", "apple", "apple-swift-expert", 25),
|
|
99
|
+
("macOS AppKit interface", "apple", "apple-swift-expert", 25),
|
|
100
|
+
("Electron app development", "desktop", "desktop-app-expert", 25),
|
|
101
|
+
("Tauri system tray application", "desktop", "desktop-app-expert", 25),
|
|
102
|
+
("email marketing campaign setup", "marketing", "marketing-automation", 25),
|
|
103
|
+
("drip sequence for onboarding", "marketing", "marketing-automation", 25),
|
|
96
104
|
("fix the database query", "none", "", 0),
|
|
97
105
|
("rename a variable", "none", "", 0),
|
|
98
106
|
("update README", "none", "", 0),
|
|
@@ -161,6 +169,42 @@ def main():
|
|
|
161
169
|
print(f"FAIL: '{task[:50]}...' => {reason}")
|
|
162
170
|
failed += 1
|
|
163
171
|
|
|
172
|
+
# Wiring integrity: every agent in registry must have a corresponding agent file
|
|
173
|
+
print("\n" + "-" * 60)
|
|
174
|
+
print("Wiring Integrity Check")
|
|
175
|
+
print("-" * 60)
|
|
176
|
+
try:
|
|
177
|
+
with open(registry_path, 'r') as f:
|
|
178
|
+
registry = json.load(f)
|
|
179
|
+
|
|
180
|
+
registry_dir = os.path.dirname(registry_path)
|
|
181
|
+
agent_dir = os.path.join(registry_dir, '../../agents') # files/merlin/skills/TASK-OPTIMIZER.json → files/agents/
|
|
182
|
+
if os.path.isabs(registry_dir):
|
|
183
|
+
agent_dir = os.path.abspath(agent_dir)
|
|
184
|
+
|
|
185
|
+
# Collect all agents referenced in registry
|
|
186
|
+
agents_in_registry = set()
|
|
187
|
+
for skill in registry.get('skills', []):
|
|
188
|
+
agent = skill.get('agent', '')
|
|
189
|
+
if agent:
|
|
190
|
+
agents_in_registry.add(agent)
|
|
191
|
+
|
|
192
|
+
integrity_fail = 0
|
|
193
|
+
for agent in sorted(agents_in_registry):
|
|
194
|
+
agent_file = os.path.join(agent_dir, f'{agent}.md')
|
|
195
|
+
if not os.path.isfile(agent_file):
|
|
196
|
+
print(f"✗ WIRING FAIL: agent '{agent}' referenced in registry but not in {agent_dir}/")
|
|
197
|
+
integrity_fail += 1
|
|
198
|
+
|
|
199
|
+
if integrity_fail > 0:
|
|
200
|
+
print(f"✗ WIRING INTEGRITY CHECK FAILED ({integrity_fail} agents missing)")
|
|
201
|
+
failed += 1
|
|
202
|
+
else:
|
|
203
|
+
total_agents = len(agents_in_registry)
|
|
204
|
+
print(f"✓ wiring integrity: all {total_agents} agents in registry exist on disk")
|
|
205
|
+
except Exception as e:
|
|
206
|
+
print(f"⚠ wiring integrity check skipped: {e}")
|
|
207
|
+
|
|
164
208
|
print("\n" + "=" * 60)
|
|
165
209
|
print(f"RESULTS: {passed} passed, {failed} failed")
|
|
166
210
|
print("=" * 60)
|
|
@@ -19,9 +19,9 @@ if ! [[ "$SECS" =~ ^[0-9]+$ ]]; then
|
|
|
19
19
|
fi
|
|
20
20
|
|
|
21
21
|
if command -v gtimeout >/dev/null 2>&1; then
|
|
22
|
-
exec gtimeout --
|
|
22
|
+
exec gtimeout --signal=TERM "${SECS}s" "$@"
|
|
23
23
|
elif command -v timeout >/dev/null 2>&1; then
|
|
24
|
-
exec timeout --
|
|
24
|
+
exec timeout --signal=TERM "${SECS}s" "$@"
|
|
25
25
|
else
|
|
26
26
|
# Perl fork+alarm fallback (always available on macOS+Linux).
|
|
27
27
|
# Must fork — exec replaces the perl process and discards the SIGALRM handler,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-merlin-brain",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.5",
|
|
4
4
|
"description": "Merlin - The Ultimate AI Brain for Claude Code, Codex, and other AI CLIs. One install: workflows, agents, loop, and Sights MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/server/index.js",
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
"typecheck": "tsc --noEmit",
|
|
18
18
|
"test": "vitest run",
|
|
19
19
|
"test:watch": "vitest",
|
|
20
|
-
"prepublishOnly": "npm run build"
|
|
21
|
-
"postinstall": "node bin/install.cjs"
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
22
21
|
},
|
|
23
22
|
"keywords": [
|
|
24
23
|
"claude",
|