create-merlin-brain 5.3.1 → 5.3.3
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
CHANGED
|
@@ -1609,6 +1609,78 @@ async function install() {
|
|
|
1609
1609
|
quiet: true, // step 2 already printed the detection summary
|
|
1610
1610
|
});
|
|
1611
1611
|
|
|
1612
|
+
// Step 13.4: Allowlist codex Bash patterns (when Codex is installed)
|
|
1613
|
+
if (!process.env.MERLIN_SKIP_PERMISSIONS_PATCH) {
|
|
1614
|
+
try {
|
|
1615
|
+
const { execSync } = require('child_process');
|
|
1616
|
+
const codexInstalledScript = path.join(SCRIPTS_DIR, 'codex-installed.sh');
|
|
1617
|
+
|
|
1618
|
+
// Check if Codex is installed
|
|
1619
|
+
let isCodexInstalled = false;
|
|
1620
|
+
try {
|
|
1621
|
+
if (fs.existsSync(codexInstalledScript)) {
|
|
1622
|
+
execSync(`bash ${codexInstalledScript}`, { stdio: 'pipe' });
|
|
1623
|
+
isCodexInstalled = true;
|
|
1624
|
+
}
|
|
1625
|
+
} catch (err) {
|
|
1626
|
+
// Codex not installed, skip this step
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
if (isCodexInstalled) {
|
|
1630
|
+
const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
|
|
1631
|
+
|
|
1632
|
+
if (fs.existsSync(settingsPath)) {
|
|
1633
|
+
try {
|
|
1634
|
+
const settingsContent = fs.readFileSync(settingsPath, 'utf8');
|
|
1635
|
+
const settings = JSON.parse(settingsContent);
|
|
1636
|
+
|
|
1637
|
+
if (settings.permissions && Array.isArray(settings.permissions.allow)) {
|
|
1638
|
+
const codexRules = [
|
|
1639
|
+
'Bash(which *)',
|
|
1640
|
+
'Bash(codex *)',
|
|
1641
|
+
'Bash(codex exec *)',
|
|
1642
|
+
'Bash(merlin-codex *)',
|
|
1643
|
+
'Bash(~/.claude/scripts/codex-as.sh *)',
|
|
1644
|
+
'Bash(bash ~/.claude/scripts/codex-as.sh *)',
|
|
1645
|
+
'Bash(~/.claude/scripts/duo-codex-call.sh *)',
|
|
1646
|
+
'Bash(bash ~/.claude/scripts/duo-codex-call.sh *)',
|
|
1647
|
+
'Bash(~/.claude/scripts/codex-installed.sh)',
|
|
1648
|
+
'Bash(~/.claude/scripts/duo-*)',
|
|
1649
|
+
'Bash(bash ~/.claude/scripts/duo-*)',
|
|
1650
|
+
'mcp__merlin__*'
|
|
1651
|
+
];
|
|
1652
|
+
|
|
1653
|
+
let addedCount = 0;
|
|
1654
|
+
for (const rule of codexRules) {
|
|
1655
|
+
if (!settings.permissions.allow.includes(rule)) {
|
|
1656
|
+
settings.permissions.allow.push(rule);
|
|
1657
|
+
addedCount++;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
if (addedCount > 0) {
|
|
1662
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
1663
|
+
logSuccess(`Allowlisted codex/duo commands in ~/.claude/settings.json (${addedCount} new rules)`);
|
|
1664
|
+
} else if (process.env.MERLIN_INSTALL_VERBOSE) {
|
|
1665
|
+
log(` codex/duo Bash rules already allowlisted`);
|
|
1666
|
+
}
|
|
1667
|
+
} else {
|
|
1668
|
+
logWarn('permissions.allow not found in ~/.claude/settings.json — skipping codex allowlist');
|
|
1669
|
+
}
|
|
1670
|
+
} catch (parseErr) {
|
|
1671
|
+
logWarn(`Could not parse ~/.claude/settings.json — skipping codex allowlist: ${parseErr.message}`);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
// If settings.json doesn't exist, skip silently (don't create it from scratch)
|
|
1675
|
+
}
|
|
1676
|
+
} catch (err) {
|
|
1677
|
+
// Non-fatal: permissions patch failure does not block the installer
|
|
1678
|
+
if (process.env.MERLIN_INSTALL_VERBOSE) {
|
|
1679
|
+
logWarn(`Codex permissions patch skipped: ${err.message}`);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1612
1684
|
// Step 13.5: Install priority design skills (async, non-blocking)
|
|
1613
1685
|
if (!process.env.MERLIN_SKIP_DESIGN_SKILLS) {
|
|
1614
1686
|
try {
|
|
@@ -1661,6 +1733,10 @@ ${colors.cyan}Universal Task Optimization (NEW in 5.3.0):${colors.reset}
|
|
|
1661
1733
|
• ${colors.bright}/merlin:polish${colors.reset} - Animation polish via animation-expert
|
|
1662
1734
|
• ${colors.bright}/merlin:redesign${colors.reset} - Full redesign via ui-builder
|
|
1663
1735
|
|
|
1736
|
+
${colors.cyan}Duo reliability (NEW in 5.3.3):${colors.reset}
|
|
1737
|
+
• Portable codex timeout wrapper (no more "gtimeout: not found" on macOS)
|
|
1738
|
+
• codex-as.sh now accepts --timeout for long parallel planning runs
|
|
1739
|
+
|
|
1664
1740
|
${colors.cyan}Merlin works with or without Sights:${colors.reset}
|
|
1665
1741
|
• ${colors.green}With Sights${colors.reset}: Instant context, cross-session memory
|
|
1666
1742
|
• ${colors.green}Without${colors.reset}: Full workflows, uses file exploration
|
|
@@ -256,9 +256,12 @@ When duo is OFF, Merlin runs a pre-route hook at the start of every routing deci
|
|
|
256
256
|
|
|
257
257
|
`duo-installed.sh` only checks PATH presence. If Codex is on PATH but fails at runtime:
|
|
258
258
|
|
|
259
|
-
1. Wrap all Codex invocations with a
|
|
260
|
-
-
|
|
261
|
-
-
|
|
259
|
+
1. Wrap all Codex invocations with a timeout using the portable wrapper:
|
|
260
|
+
- `~/.claude/scripts/with-timeout.sh <seconds> codex exec …`
|
|
261
|
+
(Auto-detects gtimeout / timeout / perl alarm — DO NOT compose raw `gtimeout`
|
|
262
|
+
or `timeout` calls; they are not always installed.)
|
|
263
|
+
- For long-running parallel planning runs, prefer `codex-as.sh --timeout 1800 <agent> "<task>"`
|
|
264
|
+
- For 60-second probes, the existing `duo-codex-call.sh` already enforces this via MERLIN_CODEX_TIMEOUT_SEC (default 60s)
|
|
262
265
|
2. On Codex error or timeout: log to `~/.claude/merlin-state/duo-decisions.log` with severity `codex_runtime_failure`. Fall back to Claude for that step:
|
|
263
266
|
- Parallel branch: drop the codex result; arbiter receives one input
|
|
264
267
|
- Sequential branch: Claude takes the author role
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# codex-as.sh — invoke Codex as a Merlin specialist agent
|
|
3
|
-
# Usage: codex-as.sh <agent-name> <task-text> [--model <model-name>]
|
|
3
|
+
# Usage: codex-as.sh <agent-name> <task-text> [--model <model-name>] [--timeout <seconds>]
|
|
4
4
|
|
|
5
5
|
set -euo pipefail
|
|
6
6
|
|
|
@@ -10,6 +10,7 @@ command -v codex >/dev/null 2>&1 || exit 0
|
|
|
10
10
|
AGENT_NAME=""
|
|
11
11
|
TASK_TEXT=""
|
|
12
12
|
MODEL_FLAG=""
|
|
13
|
+
TIMEOUT_SEC="${MERLIN_CODEX_TIMEOUT_SEC:-1800}"
|
|
13
14
|
|
|
14
15
|
# Parse arguments
|
|
15
16
|
while [[ $# -gt 0 ]]; do
|
|
@@ -23,6 +24,15 @@ while [[ $# -gt 0 ]]; do
|
|
|
23
24
|
exit 1
|
|
24
25
|
fi
|
|
25
26
|
;;
|
|
27
|
+
--timeout)
|
|
28
|
+
if [[ -n "${2:-}" && "$2" =~ ^[0-9]+$ ]]; then
|
|
29
|
+
TIMEOUT_SEC="$2"
|
|
30
|
+
shift 2
|
|
31
|
+
else
|
|
32
|
+
echo "Error: --timeout requires a non-negative integer" >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
;;
|
|
26
36
|
*)
|
|
27
37
|
if [[ -z "$AGENT_NAME" ]]; then
|
|
28
38
|
AGENT_NAME="$1"
|
|
@@ -73,5 +83,13 @@ ${TASK_TEXT}"
|
|
|
73
83
|
# (The legacy --write flag was removed from `codex exec`; -s workspace-write is the
|
|
74
84
|
# current equivalent. Use --dangerously-bypass-approvals-and-sandbox only if you
|
|
75
85
|
# explicitly want to skip all prompts — workspace-write is the safer default.)
|
|
76
|
-
#
|
|
77
|
-
|
|
86
|
+
# Wrap with timeout using the portable with-timeout.sh wrapper.
|
|
87
|
+
WITH_TIMEOUT="$(dirname "${BASH_SOURCE[0]}")/with-timeout.sh"
|
|
88
|
+
if [[ -x "$WITH_TIMEOUT" ]]; then
|
|
89
|
+
# shellcheck disable=SC2086
|
|
90
|
+
exec "$WITH_TIMEOUT" "$TIMEOUT_SEC" codex exec -s workspace-write --cd "$PWD" $MODEL_FLAG "$FULL_PROMPT"
|
|
91
|
+
else
|
|
92
|
+
# Fallback if with-timeout.sh missing (shouldn't happen post-install)
|
|
93
|
+
# shellcheck disable=SC2086
|
|
94
|
+
exec codex exec -s workspace-write --cd "$PWD" $MODEL_FLAG "$FULL_PROMPT"
|
|
95
|
+
fi
|
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
# Usage: duo-codex-call.sh <codex-command> [args...]
|
|
4
4
|
# Exit 0: success (stdout/stderr forwarded)
|
|
5
5
|
# Exit 75 (TEMPFAIL): codex failed or timed out — caller should fall back to Claude
|
|
6
|
-
# Always exits — never hangs beyond 60s
|
|
6
|
+
# Always exits — never hangs beyond MERLIN_CODEX_TIMEOUT_SEC (default 60s)
|
|
7
|
+
#
|
|
8
|
+
# Environment variables:
|
|
9
|
+
# MERLIN_CODEX_TIMEOUT_SEC — timeout in seconds (default: 60)
|
|
7
10
|
|
|
8
11
|
set -euo pipefail
|
|
9
12
|
|
|
13
|
+
TIMEOUT_SEC="${MERLIN_CODEX_TIMEOUT_SEC:-60}"
|
|
10
14
|
FAILURES_FILE="${HOME}/.claude/merlin-state/.duo-codex-failures"
|
|
11
15
|
DECISIONS_LOG="${HOME}/.claude/merlin-state/duo-decisions.log"
|
|
12
16
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
@@ -47,21 +51,15 @@ _auto_disable_duo() {
|
|
|
47
51
|
_write_counter 0
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
# ---
|
|
51
|
-
|
|
52
|
-
if command -v gtimeout >/dev/null 2>&1; then
|
|
53
|
-
_TIMEOUT="gtimeout 60"
|
|
54
|
-
elif timeout --version >/dev/null 2>&1; then
|
|
55
|
-
_TIMEOUT="timeout 60"
|
|
56
|
-
fi
|
|
57
|
-
|
|
58
|
-
# --- Execute (capture exit code without triggering set -e) ---
|
|
54
|
+
# --- Execute with timeout (capture exit code without triggering set -e) ---
|
|
55
|
+
WITH_TIMEOUT="${SCRIPT_DIR}/with-timeout.sh"
|
|
59
56
|
EXIT_CODE=0
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
|
|
58
|
+
if [[ -x "$WITH_TIMEOUT" ]]; then
|
|
59
|
+
"$WITH_TIMEOUT" "$TIMEOUT_SEC" "$@" || EXIT_CODE=$?
|
|
62
60
|
else
|
|
63
|
-
#
|
|
64
|
-
perl -e 'alarm
|
|
61
|
+
# Fallback if with-timeout.sh missing (shouldn't happen post-install)
|
|
62
|
+
perl -e 'alarm shift; exec @ARGV or exit 127' "$TIMEOUT_SEC" -- "$@" || EXIT_CODE=$?
|
|
65
63
|
fi
|
|
66
64
|
|
|
67
65
|
if [[ $EXIT_CODE -eq 0 ]]; then
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# with-timeout.sh — portable timeout wrapper (gtimeout → timeout → perl alarm)
|
|
3
|
+
# Usage: with-timeout.sh <seconds> <command> [args...]
|
|
4
|
+
# Exit 124 on timeout (GNU convention). Otherwise the wrapped command's exit code.
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
if [[ $# -lt 2 ]]; then
|
|
9
|
+
echo "Usage: with-timeout.sh <seconds> <command> [args...]" >&2
|
|
10
|
+
exit 2
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
SECS="$1"
|
|
14
|
+
shift
|
|
15
|
+
|
|
16
|
+
if ! [[ "$SECS" =~ ^[0-9]+$ ]]; then
|
|
17
|
+
echo "with-timeout.sh: timeout must be a non-negative integer (got: $SECS)" >&2
|
|
18
|
+
exit 2
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if command -v gtimeout >/dev/null 2>&1; then
|
|
22
|
+
exec gtimeout --preserve-status --signal=TERM "${SECS}s" "$@"
|
|
23
|
+
elif command -v timeout >/dev/null 2>&1; then
|
|
24
|
+
exec timeout --preserve-status --signal=TERM "${SECS}s" "$@"
|
|
25
|
+
else
|
|
26
|
+
# Perl fork+alarm fallback (always available on macOS+Linux).
|
|
27
|
+
# Must fork — exec replaces the perl process and discards the SIGALRM handler,
|
|
28
|
+
# so the alarm would never reach our exit-124 code path.
|
|
29
|
+
exec perl -e '
|
|
30
|
+
my $secs = shift;
|
|
31
|
+
my $pid = fork();
|
|
32
|
+
die "with-timeout.sh: fork failed: $!\n" unless defined $pid;
|
|
33
|
+
if ($pid == 0) {
|
|
34
|
+
exec { $ARGV[0] } @ARGV
|
|
35
|
+
or do { print STDERR "with-timeout.sh: exec failed: $!\n"; exit 127 };
|
|
36
|
+
}
|
|
37
|
+
$SIG{ALRM} = sub {
|
|
38
|
+
kill "TERM", $pid;
|
|
39
|
+
select(undef, undef, undef, 0.5);
|
|
40
|
+
kill "KILL", $pid if kill(0, $pid);
|
|
41
|
+
waitpid $pid, 0;
|
|
42
|
+
exit 124;
|
|
43
|
+
};
|
|
44
|
+
alarm $secs;
|
|
45
|
+
waitpid $pid, 0;
|
|
46
|
+
my $rc = $?;
|
|
47
|
+
if ($rc & 127) { exit 128 + ($rc & 127); }
|
|
48
|
+
exit($rc >> 8);
|
|
49
|
+
' "$SECS" "$@"
|
|
50
|
+
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-merlin-brain",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.3",
|
|
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",
|