@cyperx/clawforge 1.2.0 → 1.4.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.
- package/VERSION +1 -1
- package/bin/clawforge +14 -0
- package/bin/clawforge-dashboard +0 -0
- package/bin/clawforge-web +0 -0
- package/bin/completions.sh +68 -0
- package/bin/doctor.sh +63 -0
- package/bin/export.sh +125 -0
- package/bin/on-complete.sh +51 -3
- package/bin/profile.sh +138 -0
- package/bin/replay.sh +134 -0
- package/bin/spawn-agent.sh +37 -1
- package/bin/web.sh +68 -0
- package/package.json +13 -3
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.4.0
|
package/bin/clawforge
CHANGED
|
@@ -81,6 +81,15 @@ Power Features (v1.2):
|
|
|
81
81
|
summary AI-generated summary of what an agent did
|
|
82
82
|
parse-cost Parse real cost/token data from agent output
|
|
83
83
|
|
|
84
|
+
Web Dashboard (v1.4):
|
|
85
|
+
web Launch web dashboard (accessible from phone/browser)
|
|
86
|
+
|
|
87
|
+
Developer Experience (v1.3):
|
|
88
|
+
profile Manage reusable agent profiles (presets)
|
|
89
|
+
replay Re-run a completed task with same parameters
|
|
90
|
+
export Export task history as markdown/JSON report
|
|
91
|
+
completions Install shell tab completions (bash/zsh/fish)
|
|
92
|
+
|
|
84
93
|
Evaluation (v0.6.2):
|
|
85
94
|
eval Run-eval logging and weekly summaries
|
|
86
95
|
|
|
@@ -236,6 +245,11 @@ doctor) exec "${BIN_DIR}/doctor.sh" "$@" ;;
|
|
|
236
245
|
multi-review) exec "${BIN_DIR}/multi-review.sh" "$@" ;;
|
|
237
246
|
summary) exec "${BIN_DIR}/summary.sh" "$@" ;;
|
|
238
247
|
parse-cost) exec "${BIN_DIR}/parse-cost.sh" "$@" ;;
|
|
248
|
+
profile) exec "${BIN_DIR}/profile.sh" "$@" ;;
|
|
249
|
+
replay) exec "${BIN_DIR}/replay.sh" "$@" ;;
|
|
250
|
+
export) exec "${BIN_DIR}/export.sh" "$@" ;;
|
|
251
|
+
completions) exec "${BIN_DIR}/completions.sh" "$@" ;;
|
|
252
|
+
web) exec "${BIN_DIR}/web.sh" "$@" ;;
|
|
239
253
|
logs) exec "${BIN_DIR}/logs.sh" "$@" ;;
|
|
240
254
|
on-complete) exec "${BIN_DIR}/on-complete.sh" "$@" ;;
|
|
241
255
|
|
package/bin/clawforge-dashboard
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# completions.sh — Install shell completions for clawforge
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
COMP_DIR="${SCRIPT_DIR}/../completions"
|
|
7
|
+
|
|
8
|
+
usage() {
|
|
9
|
+
cat <<EOF
|
|
10
|
+
Usage: clawforge completions <shell>
|
|
11
|
+
|
|
12
|
+
Install tab completions for clawforge.
|
|
13
|
+
|
|
14
|
+
Shells:
|
|
15
|
+
bash Install bash completions
|
|
16
|
+
zsh Install zsh completions
|
|
17
|
+
fish Install fish completions
|
|
18
|
+
--help Show this help
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
clawforge completions bash
|
|
22
|
+
clawforge completions zsh
|
|
23
|
+
clawforge completions fish
|
|
24
|
+
|
|
25
|
+
After installing, restart your shell or source the completion file.
|
|
26
|
+
EOF
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
[[ $# -eq 0 ]] && { usage; exit 0; }
|
|
30
|
+
|
|
31
|
+
case "$1" in
|
|
32
|
+
bash)
|
|
33
|
+
DEST="${BASH_COMPLETION_USER_DIR:-${HOME}/.local/share/bash-completion/completions}"
|
|
34
|
+
mkdir -p "$DEST"
|
|
35
|
+
cp "$COMP_DIR/clawforge.bash" "$DEST/clawforge"
|
|
36
|
+
echo "Installed bash completions to $DEST/clawforge"
|
|
37
|
+
echo "Restart your shell or run: source $DEST/clawforge"
|
|
38
|
+
;;
|
|
39
|
+
zsh)
|
|
40
|
+
# Try homebrew zsh completions first, then user dir
|
|
41
|
+
if [[ -d "/opt/homebrew/share/zsh/site-functions" ]]; then
|
|
42
|
+
DEST="/opt/homebrew/share/zsh/site-functions"
|
|
43
|
+
elif [[ -d "${HOME}/.zsh/completions" ]]; then
|
|
44
|
+
DEST="${HOME}/.zsh/completions"
|
|
45
|
+
else
|
|
46
|
+
DEST="${HOME}/.zsh/completions"
|
|
47
|
+
mkdir -p "$DEST"
|
|
48
|
+
echo "Add to .zshrc: fpath=(~/.zsh/completions \$fpath)"
|
|
49
|
+
fi
|
|
50
|
+
cp "$COMP_DIR/_clawforge" "$DEST/_clawforge"
|
|
51
|
+
echo "Installed zsh completions to $DEST/_clawforge"
|
|
52
|
+
echo "Run: rm -f ~/.zcompdump && compinit"
|
|
53
|
+
;;
|
|
54
|
+
fish)
|
|
55
|
+
DEST="${HOME}/.config/fish/completions"
|
|
56
|
+
mkdir -p "$DEST"
|
|
57
|
+
cp "$COMP_DIR/clawforge.fish" "$DEST/clawforge.fish"
|
|
58
|
+
echo "Installed fish completions to $DEST/clawforge.fish"
|
|
59
|
+
;;
|
|
60
|
+
--help|-h)
|
|
61
|
+
usage
|
|
62
|
+
;;
|
|
63
|
+
*)
|
|
64
|
+
echo "Unknown shell: $1"
|
|
65
|
+
echo "Supported: bash, zsh, fish"
|
|
66
|
+
exit 1
|
|
67
|
+
;;
|
|
68
|
+
esac
|
package/bin/doctor.sh
CHANGED
|
@@ -181,6 +181,69 @@ else
|
|
|
181
181
|
check OK "Disk check skipped (df unavailable)"
|
|
182
182
|
fi
|
|
183
183
|
|
|
184
|
+
# 7. Lock file health
|
|
185
|
+
echo ""
|
|
186
|
+
echo "── Lock Files ────────────────────────────"
|
|
187
|
+
LOCK_FILE="${CLAWFORGE_DIR}/registry/.lock"
|
|
188
|
+
if [[ -f "$LOCK_FILE" ]]; then
|
|
189
|
+
LOCK_PID=$(cat "$LOCK_FILE" 2>/dev/null || true)
|
|
190
|
+
if [[ -n "$LOCK_PID" ]] && ! kill -0 "$LOCK_PID" 2>/dev/null; then
|
|
191
|
+
check WARN "Stale lock file (PID $LOCK_PID not running)"
|
|
192
|
+
if $FIX; then
|
|
193
|
+
rm -f "$LOCK_FILE"
|
|
194
|
+
echo " → Fixed: removed stale lock"
|
|
195
|
+
FIXED=$((FIXED+1))
|
|
196
|
+
fi
|
|
197
|
+
else
|
|
198
|
+
check OK "Lock file clean"
|
|
199
|
+
fi
|
|
200
|
+
else
|
|
201
|
+
check OK "No lock file"
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
# 8. Config validation
|
|
205
|
+
echo ""
|
|
206
|
+
echo "── Configuration ─────────────────────────"
|
|
207
|
+
USER_CFG="${HOME}/.clawforge/config.json"
|
|
208
|
+
if [[ -f "$USER_CFG" ]]; then
|
|
209
|
+
if jq empty "$USER_CFG" 2>/dev/null; then
|
|
210
|
+
KEY_COUNT=$(jq 'keys | length' "$USER_CFG")
|
|
211
|
+
check OK "User config valid ($KEY_COUNT keys)"
|
|
212
|
+
else
|
|
213
|
+
check ERROR "User config is malformed JSON"
|
|
214
|
+
if $FIX; then
|
|
215
|
+
cp "$USER_CFG" "${USER_CFG}.bak"
|
|
216
|
+
echo '{}' > "$USER_CFG"
|
|
217
|
+
echo " → Fixed: reset config (backup at ${USER_CFG}.bak)"
|
|
218
|
+
FIXED=$((FIXED+1))
|
|
219
|
+
fi
|
|
220
|
+
fi
|
|
221
|
+
else
|
|
222
|
+
check OK "No user config (using defaults)"
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
# 9. Profiles directory
|
|
226
|
+
PROFILES_DIR="${HOME}/.clawforge/profiles"
|
|
227
|
+
if [[ -d "$PROFILES_DIR" ]]; then
|
|
228
|
+
PROFILE_COUNT=$(find "$PROFILES_DIR" -maxdepth 1 -name "*.json" 2>/dev/null | wc -l | tr -d ' ')
|
|
229
|
+
check OK "$PROFILE_COUNT agent profile(s) configured"
|
|
230
|
+
# Validate each profile
|
|
231
|
+
shopt -s nullglob 2>/dev/null || true
|
|
232
|
+
for pf in "$PROFILES_DIR"/*.json; do
|
|
233
|
+
[[ -f "$pf" ]] || continue
|
|
234
|
+
if ! jq empty "$pf" 2>/dev/null; then
|
|
235
|
+
check WARN "Malformed profile: $(basename "$pf" .json)"
|
|
236
|
+
if $FIX; then
|
|
237
|
+
rm "$pf"
|
|
238
|
+
echo " → Fixed: removed malformed profile"
|
|
239
|
+
FIXED=$((FIXED+1))
|
|
240
|
+
fi
|
|
241
|
+
fi
|
|
242
|
+
done
|
|
243
|
+
else
|
|
244
|
+
check OK "No profiles directory"
|
|
245
|
+
fi
|
|
246
|
+
|
|
184
247
|
# Summary
|
|
185
248
|
echo ""
|
|
186
249
|
echo "────────────────────────────────────────"
|
package/bin/export.sh
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# export.sh — Export task history as markdown or JSON report
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "${SCRIPT_DIR}/../lib/common.sh"
|
|
7
|
+
|
|
8
|
+
usage() {
|
|
9
|
+
cat <<EOF
|
|
10
|
+
Usage: clawforge export [options]
|
|
11
|
+
|
|
12
|
+
Export full task history as a report.
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
--format <fmt> Output format: markdown, json (default: markdown)
|
|
16
|
+
--status <status> Filter by status (done, failed, running, all) (default: all)
|
|
17
|
+
--since <date> Filter tasks since date (YYYY-MM-DD)
|
|
18
|
+
--save <path> Save to file (default: stdout)
|
|
19
|
+
--help Show this help
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
clawforge export # Full markdown report
|
|
23
|
+
clawforge export --format json # JSON dump
|
|
24
|
+
clawforge export --status done --save report.md # Only completed tasks
|
|
25
|
+
clawforge export --since 2026-03-01 # Recent tasks only
|
|
26
|
+
EOF
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
FORMAT="markdown" STATUS_FILTER="all" SINCE="" SAVE_PATH=""
|
|
30
|
+
|
|
31
|
+
while [[ $# -gt 0 ]]; do
|
|
32
|
+
case "$1" in
|
|
33
|
+
--format) FORMAT="$2"; shift 2 ;;
|
|
34
|
+
--status) STATUS_FILTER="$2"; shift 2 ;;
|
|
35
|
+
--since) SINCE="$2"; shift 2 ;;
|
|
36
|
+
--save) SAVE_PATH="$2"; shift 2 ;;
|
|
37
|
+
--help|-h) usage; exit 0 ;;
|
|
38
|
+
--*) log_error "Unknown option: $1"; usage; exit 1 ;;
|
|
39
|
+
*) shift ;;
|
|
40
|
+
esac
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
_ensure_registry
|
|
44
|
+
|
|
45
|
+
# Gather all tasks (active + completed)
|
|
46
|
+
COMPLETED_FILE="${CLAWFORGE_DIR}/registry/completed-tasks.jsonl"
|
|
47
|
+
COSTS_FILE="${CLAWFORGE_DIR}/registry/costs.jsonl"
|
|
48
|
+
|
|
49
|
+
ALL_TASKS="[]"
|
|
50
|
+
|
|
51
|
+
# Active tasks
|
|
52
|
+
ACTIVE=$(jq '.tasks' "$REGISTRY_FILE" 2>/dev/null || echo '[]')
|
|
53
|
+
ALL_TASKS=$(echo "$ACTIVE" | jq '.')
|
|
54
|
+
|
|
55
|
+
# Completed tasks
|
|
56
|
+
if [[ -f "$COMPLETED_FILE" ]]; then
|
|
57
|
+
COMPLETED=$(jq -s '.' "$COMPLETED_FILE" 2>/dev/null || echo '[]')
|
|
58
|
+
ALL_TASKS=$(jq -s '.[0] + .[1] | unique_by(.id // .taskId)' <(echo "$ALL_TASKS") <(echo "$COMPLETED") 2>/dev/null || echo "$ALL_TASKS")
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Apply filters
|
|
62
|
+
if [[ "$STATUS_FILTER" != "all" ]]; then
|
|
63
|
+
ALL_TASKS=$(echo "$ALL_TASKS" | jq --arg s "$STATUS_FILTER" '[.[] | select(.status == $s)]')
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
if [[ -n "$SINCE" ]]; then
|
|
67
|
+
SINCE_TS=$(date -j -f "%Y-%m-%d" "$SINCE" "+%s" 2>/dev/null || date -d "$SINCE" "+%s" 2>/dev/null || echo "0")
|
|
68
|
+
SINCE_MS=$((SINCE_TS * 1000))
|
|
69
|
+
ALL_TASKS=$(echo "$ALL_TASKS" | jq --argjson s "$SINCE_MS" '[.[] | select((.startedAt // .timestamp // 0) >= $s)]')
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
TASK_COUNT=$(echo "$ALL_TASKS" | jq 'length')
|
|
73
|
+
|
|
74
|
+
# Generate output
|
|
75
|
+
generate_markdown() {
|
|
76
|
+
echo "# ClawForge Task Report"
|
|
77
|
+
echo "Generated: $(date)"
|
|
78
|
+
echo "Tasks: $TASK_COUNT"
|
|
79
|
+
[[ "$STATUS_FILTER" != "all" ]] && echo "Filter: status=$STATUS_FILTER"
|
|
80
|
+
[[ -n "$SINCE" ]] && echo "Since: $SINCE"
|
|
81
|
+
echo ""
|
|
82
|
+
|
|
83
|
+
# Summary stats
|
|
84
|
+
DONE_COUNT=$(echo "$ALL_TASKS" | jq '[.[] | select(.status == "done")] | length')
|
|
85
|
+
FAIL_COUNT=$(echo "$ALL_TASKS" | jq '[.[] | select(.status == "failed")] | length')
|
|
86
|
+
RUN_COUNT=$(echo "$ALL_TASKS" | jq '[.[] | select(.status == "running")] | length')
|
|
87
|
+
|
|
88
|
+
echo "## Summary"
|
|
89
|
+
echo "| Status | Count |"
|
|
90
|
+
echo "|--------|-------|"
|
|
91
|
+
echo "| ✅ Done | $DONE_COUNT |"
|
|
92
|
+
echo "| ❌ Failed | $FAIL_COUNT |"
|
|
93
|
+
echo "| 🔄 Running | $RUN_COUNT |"
|
|
94
|
+
echo "| Total | $TASK_COUNT |"
|
|
95
|
+
echo ""
|
|
96
|
+
|
|
97
|
+
# Costs
|
|
98
|
+
if [[ -f "$COSTS_FILE" ]] && [[ -s "$COSTS_FILE" ]]; then
|
|
99
|
+
TOTAL_COST=$(jq -s '[.[].totalCost // 0] | add' "$COSTS_FILE" 2>/dev/null || echo "0")
|
|
100
|
+
TOTAL_TOKENS=$(jq -s '[.[].totalTokens // 0] | add' "$COSTS_FILE" 2>/dev/null || echo "0")
|
|
101
|
+
echo "## Costs"
|
|
102
|
+
echo "- Total cost: \$${TOTAL_COST}"
|
|
103
|
+
echo "- Total tokens: ${TOTAL_TOKENS}"
|
|
104
|
+
echo ""
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Task details
|
|
108
|
+
echo "## Tasks"
|
|
109
|
+
echo ""
|
|
110
|
+
|
|
111
|
+
echo "$ALL_TASKS" | jq -r '.[] | "### #\(.short_id // "—") — \(.description // .desc // "—")\n- **Status:** \(.status // "—")\n- **Mode:** \(.mode // "—")\n- **Agent:** \(.agent // "—")\n- **Branch:** \(.branch // "—")\n"' 2>/dev/null || true
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if [[ "$FORMAT" == "json" ]]; then
|
|
115
|
+
OUTPUT=$(echo "$ALL_TASKS" | jq '.')
|
|
116
|
+
else
|
|
117
|
+
OUTPUT=$(generate_markdown)
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
if [[ -n "$SAVE_PATH" ]]; then
|
|
121
|
+
echo "$OUTPUT" > "$SAVE_PATH"
|
|
122
|
+
echo "Report saved to $SAVE_PATH ($TASK_COUNT tasks)"
|
|
123
|
+
else
|
|
124
|
+
echo "$OUTPUT"
|
|
125
|
+
fi
|
package/bin/on-complete.sh
CHANGED
|
@@ -117,7 +117,55 @@ if [[ -n "$WEBHOOK" ]]; then
|
|
|
117
117
|
fi
|
|
118
118
|
fi
|
|
119
119
|
|
|
120
|
-
# 3.
|
|
120
|
+
# 3. Discord/Slack webhook
|
|
121
|
+
DISCORD_WEBHOOK=$(echo "$TASK_DATA" | jq -r '.discord_webhook // empty')
|
|
122
|
+
if [[ -z "$DISCORD_WEBHOOK" ]]; then
|
|
123
|
+
DISCORD_WEBHOOK=$(config_get discord_webhook "")
|
|
124
|
+
fi
|
|
125
|
+
if [[ -n "$DISCORD_WEBHOOK" ]]; then
|
|
126
|
+
EMOJI="✅"
|
|
127
|
+
COLOR=5763719 # green
|
|
128
|
+
[[ "$STATUS" == "failed" ]] && { EMOJI="❌"; COLOR=15548997; } # red
|
|
129
|
+
[[ "$STATUS" == "timeout" ]] && { EMOJI="⏰"; COLOR=16776960; } # yellow
|
|
130
|
+
[[ "$STATUS" == "cancelled" ]] && { EMOJI="🚫"; COLOR=10070709; } # grey
|
|
131
|
+
|
|
132
|
+
DISCORD_PAYLOAD=$(jq -cn \
|
|
133
|
+
--arg title "${EMOJI} ClawForge #${SHORT_ID} ${STATUS}" \
|
|
134
|
+
--arg desc "$DESC" \
|
|
135
|
+
--arg mode "$MODE" \
|
|
136
|
+
--arg status "$STATUS" \
|
|
137
|
+
--argjson color "$COLOR" \
|
|
138
|
+
'{embeds:[{title:$title,description:$desc,color:$color,fields:[{name:"Mode",value:$mode,inline:true},{name:"Status",value:$status,inline:true}]}]}')
|
|
139
|
+
|
|
140
|
+
if $DRY_RUN; then
|
|
141
|
+
echo "[dry-run] Would send Discord webhook"
|
|
142
|
+
else
|
|
143
|
+
curl -s -X POST -H "Content-Type: application/json" -d "$DISCORD_PAYLOAD" "$DISCORD_WEBHOOK" >/dev/null 2>&1 || log_warn "Discord webhook failed"
|
|
144
|
+
log_info "Sent Discord notification"
|
|
145
|
+
fi
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
SLACK_WEBHOOK=$(echo "$TASK_DATA" | jq -r '.slack_webhook // empty')
|
|
149
|
+
if [[ -z "$SLACK_WEBHOOK" ]]; then
|
|
150
|
+
SLACK_WEBHOOK=$(config_get slack_webhook "")
|
|
151
|
+
fi
|
|
152
|
+
if [[ -n "$SLACK_WEBHOOK" ]]; then
|
|
153
|
+
EMOJI="✅"
|
|
154
|
+
[[ "$STATUS" == "failed" ]] && EMOJI="❌"
|
|
155
|
+
[[ "$STATUS" == "timeout" ]] && EMOJI="⏰"
|
|
156
|
+
SLACK_PAYLOAD=$(jq -cn \
|
|
157
|
+
--arg text "${EMOJI} ClawForge #${SHORT_ID} ${STATUS}: ${DESC} (${MODE})" \
|
|
158
|
+
'{text:$text}')
|
|
159
|
+
|
|
160
|
+
if $DRY_RUN; then
|
|
161
|
+
echo "[dry-run] Would send Slack webhook"
|
|
162
|
+
else
|
|
163
|
+
curl -s -X POST -H "Content-Type: application/json" -d "$SLACK_PAYLOAD" "$SLACK_WEBHOOK" >/dev/null 2>&1 || log_warn "Slack webhook failed"
|
|
164
|
+
log_info "Sent Slack notification"
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
# 4. Auto-clean
|
|
121
169
|
if [[ "$AUTO_CLEAN" == "true" ]]; then
|
|
122
170
|
if $DRY_RUN; then
|
|
123
171
|
echo "[dry-run] Would auto-clean task #${SHORT_ID}"
|
|
@@ -127,12 +175,12 @@ if [[ "$AUTO_CLEAN" == "true" ]]; then
|
|
|
127
175
|
fi
|
|
128
176
|
fi
|
|
129
177
|
|
|
130
|
-
#
|
|
178
|
+
# 5. Mark hooks as fired
|
|
131
179
|
if ! $DRY_RUN; then
|
|
132
180
|
registry_update "$TASK_ID" "hooks_fired" 'true' 2>/dev/null || true
|
|
133
181
|
fi
|
|
134
182
|
|
|
135
|
-
#
|
|
183
|
+
# 6. Log completion
|
|
136
184
|
COMPLETION_LOG="${CLAWFORGE_DIR}/registry/completions.jsonl"
|
|
137
185
|
if ! $DRY_RUN; then
|
|
138
186
|
ENTRY=$(jq -cn \
|
package/bin/profile.sh
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# profile.sh — Manage reusable agent profiles
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "${SCRIPT_DIR}/../lib/common.sh"
|
|
7
|
+
|
|
8
|
+
PROFILES_DIR="${HOME}/.clawforge/profiles"
|
|
9
|
+
|
|
10
|
+
usage() {
|
|
11
|
+
cat <<EOF
|
|
12
|
+
Usage: clawforge profile <subcommand> [args]
|
|
13
|
+
|
|
14
|
+
Manage reusable agent profiles (saved parameter presets).
|
|
15
|
+
|
|
16
|
+
Subcommands:
|
|
17
|
+
list List all profiles
|
|
18
|
+
show <name> Show profile details
|
|
19
|
+
create <name> [options] Create a new profile
|
|
20
|
+
delete <name> Delete a profile
|
|
21
|
+
use <name> Print spawn flags for a profile
|
|
22
|
+
--help Show this help
|
|
23
|
+
|
|
24
|
+
Create options:
|
|
25
|
+
--agent <name> Agent: claude or codex
|
|
26
|
+
--model <model> Model to use
|
|
27
|
+
--effort <level> Effort: high, medium, low
|
|
28
|
+
--timeout <minutes> Timeout in minutes
|
|
29
|
+
--auto-clean Enable auto-clean
|
|
30
|
+
--notify Enable notifications
|
|
31
|
+
--routing <strategy> Routing: auto, cheap, quality
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
clawforge profile create fast --agent claude --model claude-haiku-3.5 --timeout 5
|
|
35
|
+
clawforge profile create quality --agent claude --model claude-opus-4 --effort high --notify
|
|
36
|
+
clawforge profile create cheap --agent codex --model gpt-5.2-codex --routing cheap
|
|
37
|
+
clawforge profile use fast
|
|
38
|
+
clawforge sprint --repo . --task "fix bug" $(clawforge profile use fast)
|
|
39
|
+
EOF
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
[[ $# -eq 0 ]] && { usage; exit 0; }
|
|
43
|
+
|
|
44
|
+
mkdir -p "$PROFILES_DIR"
|
|
45
|
+
|
|
46
|
+
case "$1" in
|
|
47
|
+
list)
|
|
48
|
+
echo "── Agent Profiles ──"
|
|
49
|
+
if [[ -z "$(ls -A "$PROFILES_DIR" 2>/dev/null)" ]]; then
|
|
50
|
+
echo "(none — create with: clawforge profile create <name>)"
|
|
51
|
+
exit 0
|
|
52
|
+
fi
|
|
53
|
+
for f in "$PROFILES_DIR"/*.json; do
|
|
54
|
+
[[ -f "$f" ]] || continue
|
|
55
|
+
name=$(basename "$f" .json)
|
|
56
|
+
agent=$(jq -r '.agent // "—"' "$f")
|
|
57
|
+
model=$(jq -r '.model // "—"' "$f")
|
|
58
|
+
printf " %-15s agent=%-8s model=%s\n" "$name" "$agent" "$model"
|
|
59
|
+
done
|
|
60
|
+
;;
|
|
61
|
+
|
|
62
|
+
show)
|
|
63
|
+
[[ -z "${2:-}" ]] && { log_error "Profile name required"; exit 1; }
|
|
64
|
+
PROFILE_FILE="$PROFILES_DIR/$2.json"
|
|
65
|
+
[[ -f "$PROFILE_FILE" ]] || { log_error "Profile '$2' not found"; exit 1; }
|
|
66
|
+
echo "── Profile: $2 ──"
|
|
67
|
+
jq '.' "$PROFILE_FILE"
|
|
68
|
+
;;
|
|
69
|
+
|
|
70
|
+
create)
|
|
71
|
+
shift
|
|
72
|
+
[[ -z "${1:-}" ]] && { log_error "Profile name required"; exit 1; }
|
|
73
|
+
PROFILE_NAME="$1"; shift
|
|
74
|
+
|
|
75
|
+
AGENT="" MODEL="" EFFORT="" TIMEOUT="" AUTO_CLEAN=false NOTIFY=false ROUTING=""
|
|
76
|
+
while [[ $# -gt 0 ]]; do
|
|
77
|
+
case "$1" in
|
|
78
|
+
--agent) AGENT="$2"; shift 2 ;;
|
|
79
|
+
--model) MODEL="$2"; shift 2 ;;
|
|
80
|
+
--effort) EFFORT="$2"; shift 2 ;;
|
|
81
|
+
--timeout) TIMEOUT="$2"; shift 2 ;;
|
|
82
|
+
--auto-clean) AUTO_CLEAN=true; shift ;;
|
|
83
|
+
--notify) NOTIFY=true; shift ;;
|
|
84
|
+
--routing) ROUTING="$2"; shift 2 ;;
|
|
85
|
+
*) log_error "Unknown option: $1"; exit 1 ;;
|
|
86
|
+
esac
|
|
87
|
+
done
|
|
88
|
+
|
|
89
|
+
PROFILE_FILE="$PROFILES_DIR/${PROFILE_NAME}.json"
|
|
90
|
+
jq -cn \
|
|
91
|
+
--arg agent "$AGENT" \
|
|
92
|
+
--arg model "$MODEL" \
|
|
93
|
+
--arg effort "$EFFORT" \
|
|
94
|
+
--arg timeout "$TIMEOUT" \
|
|
95
|
+
--argjson autoClean "$AUTO_CLEAN" \
|
|
96
|
+
--argjson notify "$NOTIFY" \
|
|
97
|
+
--arg routing "$ROUTING" \
|
|
98
|
+
'{agent:$agent,model:$model,effort:$effort,timeout:$timeout,autoClean:$autoClean,notify:$notify,routing:$routing} | with_entries(select(.value != "" and .value != null))' \
|
|
99
|
+
> "$PROFILE_FILE"
|
|
100
|
+
|
|
101
|
+
echo "Created profile '$PROFILE_NAME' at $PROFILE_FILE"
|
|
102
|
+
jq '.' "$PROFILE_FILE"
|
|
103
|
+
;;
|
|
104
|
+
|
|
105
|
+
delete)
|
|
106
|
+
[[ -z "${2:-}" ]] && { log_error "Profile name required"; exit 1; }
|
|
107
|
+
PROFILE_FILE="$PROFILES_DIR/$2.json"
|
|
108
|
+
[[ -f "$PROFILE_FILE" ]] || { log_error "Profile '$2' not found"; exit 1; }
|
|
109
|
+
rm "$PROFILE_FILE"
|
|
110
|
+
echo "Deleted profile '$2'"
|
|
111
|
+
;;
|
|
112
|
+
|
|
113
|
+
use)
|
|
114
|
+
[[ -z "${2:-}" ]] && { log_error "Profile name required"; exit 1; }
|
|
115
|
+
PROFILE_FILE="$PROFILES_DIR/$2.json"
|
|
116
|
+
[[ -f "$PROFILE_FILE" ]] || { log_error "Profile '$2' not found"; exit 1; }
|
|
117
|
+
|
|
118
|
+
FLAGS=""
|
|
119
|
+
agent=$(jq -r '.agent // empty' "$PROFILE_FILE")
|
|
120
|
+
model=$(jq -r '.model // empty' "$PROFILE_FILE")
|
|
121
|
+
effort=$(jq -r '.effort // empty' "$PROFILE_FILE")
|
|
122
|
+
timeout=$(jq -r '.timeout // empty' "$PROFILE_FILE")
|
|
123
|
+
auto_clean=$(jq -r '.autoClean // false' "$PROFILE_FILE")
|
|
124
|
+
notify=$(jq -r '.notify // false' "$PROFILE_FILE")
|
|
125
|
+
|
|
126
|
+
[[ -n "$agent" ]] && FLAGS+="--agent $agent "
|
|
127
|
+
[[ -n "$model" ]] && FLAGS+="--model $model "
|
|
128
|
+
[[ -n "$effort" ]] && FLAGS+="--effort $effort "
|
|
129
|
+
[[ -n "$timeout" ]] && FLAGS+="--timeout $timeout "
|
|
130
|
+
[[ "$auto_clean" == "true" ]] && FLAGS+="--auto-clean "
|
|
131
|
+
[[ "$notify" == "true" ]] && FLAGS+="--notify "
|
|
132
|
+
|
|
133
|
+
echo "$FLAGS"
|
|
134
|
+
;;
|
|
135
|
+
|
|
136
|
+
--help|-h) usage ;;
|
|
137
|
+
*) log_error "Unknown subcommand: $1"; usage; exit 1 ;;
|
|
138
|
+
esac
|
package/bin/replay.sh
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# replay.sh — Re-run a completed task with the same parameters
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "${SCRIPT_DIR}/../lib/common.sh"
|
|
7
|
+
|
|
8
|
+
usage() {
|
|
9
|
+
cat <<EOF
|
|
10
|
+
Usage: clawforge replay <id> [options]
|
|
11
|
+
|
|
12
|
+
Re-run a completed task with the same parameters on a fresh worktree.
|
|
13
|
+
|
|
14
|
+
Arguments:
|
|
15
|
+
<id> Task ID or short ID
|
|
16
|
+
|
|
17
|
+
Options:
|
|
18
|
+
--model <model> Override model (default: same as original)
|
|
19
|
+
--agent <agent> Override agent (default: same as original)
|
|
20
|
+
--branch <name> Override branch name (default: original-retry-N)
|
|
21
|
+
--dry-run Show what would run
|
|
22
|
+
--help Show this help
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
clawforge replay 1
|
|
26
|
+
clawforge replay 1 --model claude-opus-4
|
|
27
|
+
clawforge replay 1 --dry-run
|
|
28
|
+
EOF
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
TASK_REF="" MODEL_OVERRIDE="" AGENT_OVERRIDE="" BRANCH_OVERRIDE="" DRY_RUN=false
|
|
32
|
+
|
|
33
|
+
while [[ $# -gt 0 ]]; do
|
|
34
|
+
case "$1" in
|
|
35
|
+
--model) MODEL_OVERRIDE="$2"; shift 2 ;;
|
|
36
|
+
--agent) AGENT_OVERRIDE="$2"; shift 2 ;;
|
|
37
|
+
--branch) BRANCH_OVERRIDE="$2"; shift 2 ;;
|
|
38
|
+
--dry-run) DRY_RUN=true; shift ;;
|
|
39
|
+
--help|-h) usage; exit 0 ;;
|
|
40
|
+
--*) log_error "Unknown option: $1"; usage; exit 1 ;;
|
|
41
|
+
*) TASK_REF="$1"; shift ;;
|
|
42
|
+
esac
|
|
43
|
+
done
|
|
44
|
+
|
|
45
|
+
[[ -z "$TASK_REF" ]] && { log_error "Task ID required"; usage; exit 1; }
|
|
46
|
+
|
|
47
|
+
_ensure_registry
|
|
48
|
+
|
|
49
|
+
# Resolve task (check completed tasks too)
|
|
50
|
+
TASK_DATA=""
|
|
51
|
+
if [[ "$TASK_REF" =~ ^[0-9]+$ ]]; then
|
|
52
|
+
TASK_DATA=$(jq -r --argjson sid "$TASK_REF" '.tasks[] | select(.short_id == $sid)' "$REGISTRY_FILE" 2>/dev/null || true)
|
|
53
|
+
fi
|
|
54
|
+
if [[ -z "$TASK_DATA" ]]; then
|
|
55
|
+
TASK_DATA=$(registry_get "$TASK_REF" 2>/dev/null || true)
|
|
56
|
+
fi
|
|
57
|
+
# Also check completed-tasks.jsonl
|
|
58
|
+
if [[ -z "$TASK_DATA" ]]; then
|
|
59
|
+
COMPLETED_FILE="${CLAWFORGE_DIR}/registry/completed-tasks.jsonl"
|
|
60
|
+
if [[ -f "$COMPLETED_FILE" ]]; then
|
|
61
|
+
if [[ "$TASK_REF" =~ ^[0-9]+$ ]]; then
|
|
62
|
+
TASK_DATA=$(jq -r --argjson sid "$TASK_REF" 'select(.short_id == $sid)' "$COMPLETED_FILE" 2>/dev/null | tail -1 || true)
|
|
63
|
+
else
|
|
64
|
+
TASK_DATA=$(jq -r --arg id "$TASK_REF" 'select(.id == $id)' "$COMPLETED_FILE" 2>/dev/null | tail -1 || true)
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
if [[ -z "$TASK_DATA" ]]; then
|
|
70
|
+
log_error "Task '$TASK_REF' not found in active or completed tasks"
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# Extract original parameters
|
|
75
|
+
ORIG_REPO=$(echo "$TASK_DATA" | jq -r '.repo // empty')
|
|
76
|
+
ORIG_BRANCH=$(echo "$TASK_DATA" | jq -r '.branch // empty')
|
|
77
|
+
ORIG_TASK=$(echo "$TASK_DATA" | jq -r '.description // empty')
|
|
78
|
+
ORIG_AGENT=$(echo "$TASK_DATA" | jq -r '.agent // "claude"')
|
|
79
|
+
ORIG_MODEL=$(echo "$TASK_DATA" | jq -r '.model // empty')
|
|
80
|
+
ORIG_MODE=$(echo "$TASK_DATA" | jq -r '.mode // "sprint"')
|
|
81
|
+
ORIG_EFFORT=$(echo "$TASK_DATA" | jq -r '.effort // "high"')
|
|
82
|
+
SHORT_ID=$(echo "$TASK_DATA" | jq -r '.short_id // 0')
|
|
83
|
+
|
|
84
|
+
# Apply overrides
|
|
85
|
+
AGENT="${AGENT_OVERRIDE:-$ORIG_AGENT}"
|
|
86
|
+
MODEL="${MODEL_OVERRIDE:-$ORIG_MODEL}"
|
|
87
|
+
|
|
88
|
+
# Generate retry branch name
|
|
89
|
+
if [[ -n "$BRANCH_OVERRIDE" ]]; then
|
|
90
|
+
NEW_BRANCH="$BRANCH_OVERRIDE"
|
|
91
|
+
else
|
|
92
|
+
# Find retry number
|
|
93
|
+
RETRY=1
|
|
94
|
+
while true; do
|
|
95
|
+
NEW_BRANCH="${ORIG_BRANCH}-retry-${RETRY}"
|
|
96
|
+
# Check if branch exists
|
|
97
|
+
if [[ -n "$ORIG_REPO" ]] && git -C "$ORIG_REPO" rev-parse --verify "$NEW_BRANCH" >/dev/null 2>&1; then
|
|
98
|
+
RETRY=$((RETRY + 1))
|
|
99
|
+
else
|
|
100
|
+
break
|
|
101
|
+
fi
|
|
102
|
+
done
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
[[ -z "$ORIG_REPO" ]] && { log_error "Original task has no repo path — can't replay"; exit 1; }
|
|
106
|
+
[[ -z "$ORIG_TASK" ]] && { log_error "Original task has no description — can't replay"; exit 1; }
|
|
107
|
+
|
|
108
|
+
# Show replay plan
|
|
109
|
+
echo "🔄 Replaying task #${SHORT_ID}"
|
|
110
|
+
echo " Description: $ORIG_TASK"
|
|
111
|
+
echo " Mode: $ORIG_MODE"
|
|
112
|
+
echo " Agent: $AGENT"
|
|
113
|
+
echo " Model: $MODEL"
|
|
114
|
+
echo " Branch: $NEW_BRANCH"
|
|
115
|
+
echo " Repo: $ORIG_REPO"
|
|
116
|
+
echo ""
|
|
117
|
+
|
|
118
|
+
if $DRY_RUN; then
|
|
119
|
+
echo "[dry-run] Would run:"
|
|
120
|
+
echo " clawforge ${ORIG_MODE} --repo $ORIG_REPO --task \"$ORIG_TASK\" --agent $AGENT --model $MODEL"
|
|
121
|
+
exit 0
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# Dispatch based on mode
|
|
125
|
+
SPAWN_CMD=(
|
|
126
|
+
"${SCRIPT_DIR}/spawn-agent.sh"
|
|
127
|
+
--repo "$ORIG_REPO"
|
|
128
|
+
--branch "$NEW_BRANCH"
|
|
129
|
+
--task "$ORIG_TASK"
|
|
130
|
+
--agent "$AGENT"
|
|
131
|
+
)
|
|
132
|
+
[[ -n "$MODEL" ]] && SPAWN_CMD+=(--model "$MODEL")
|
|
133
|
+
|
|
134
|
+
exec "${SPAWN_CMD[@]}"
|
package/bin/spawn-agent.sh
CHANGED
|
@@ -17,13 +17,14 @@ Options:
|
|
|
17
17
|
--agent <name> Agent to use: claude or codex (default: auto-detect)
|
|
18
18
|
--model <model> Model override
|
|
19
19
|
--effort <level> Effort level: high, medium, low (default: high)
|
|
20
|
+
--after <id> Wait for task <id> to complete before spawning
|
|
20
21
|
--dry-run Do everything except launch the agent
|
|
21
22
|
--help Show this help
|
|
22
23
|
EOF
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
# ── Parse args ─────────────────────────────────────────────────────────
|
|
26
|
-
REPO="" BRANCH="" TASK="" AGENT="" MODEL="" EFFORT="" DRY_RUN=false
|
|
27
|
+
REPO="" BRANCH="" TASK="" AGENT="" MODEL="" EFFORT="" DRY_RUN=false AFTER=""
|
|
27
28
|
|
|
28
29
|
while [[ $# -gt 0 ]]; do
|
|
29
30
|
case "$1" in
|
|
@@ -33,6 +34,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
33
34
|
--agent) AGENT="$2"; shift 2 ;;
|
|
34
35
|
--model) MODEL="$2"; shift 2 ;;
|
|
35
36
|
--effort) EFFORT="$2"; shift 2 ;;
|
|
37
|
+
--after) AFTER="$2"; shift 2 ;;
|
|
36
38
|
--dry-run) DRY_RUN=true; shift ;;
|
|
37
39
|
--help|-h) usage; exit 0 ;;
|
|
38
40
|
*) log_error "Unknown option: $1"; usage; exit 1 ;;
|
|
@@ -45,6 +47,40 @@ done
|
|
|
45
47
|
[[ -z "$TASK" ]] && { log_error "--task is required"; usage; exit 1; }
|
|
46
48
|
[[ -d "$REPO/.git" ]] || [[ -f "$REPO/.git" ]] || { log_error "Not a git repo: $REPO"; exit 1; }
|
|
47
49
|
|
|
50
|
+
# ── Wait for dependency ──────────────────────────────────────────────
|
|
51
|
+
if [[ -n "$AFTER" ]]; then
|
|
52
|
+
log_info "Waiting for task $AFTER to complete before spawning..."
|
|
53
|
+
WAIT_TIMEOUT=${CLAWFORGE_DEP_TIMEOUT:-3600} # 1 hour default
|
|
54
|
+
ELAPSED=0
|
|
55
|
+
INTERVAL=5
|
|
56
|
+
while [[ $ELAPSED -lt $WAIT_TIMEOUT ]]; do
|
|
57
|
+
DEP_STATUS=""
|
|
58
|
+
if [[ "$AFTER" =~ ^[0-9]+$ ]]; then
|
|
59
|
+
DEP_STATUS=$(jq -r --argjson sid "$AFTER" '.tasks[] | select(.short_id == $sid) | .status' "$REGISTRY_FILE" 2>/dev/null || true)
|
|
60
|
+
else
|
|
61
|
+
DEP_STATUS=$(jq -r --arg id "$AFTER" '.tasks[] | select(.id == $id) | .status' "$REGISTRY_FILE" 2>/dev/null || true)
|
|
62
|
+
fi
|
|
63
|
+
case "$DEP_STATUS" in
|
|
64
|
+
done)
|
|
65
|
+
log_info "Dependency $AFTER completed. Spawning..."
|
|
66
|
+
break
|
|
67
|
+
;;
|
|
68
|
+
failed|timeout|cancelled)
|
|
69
|
+
log_error "Dependency $AFTER ended with status: $DEP_STATUS. Aborting spawn."
|
|
70
|
+
exit 1
|
|
71
|
+
;;
|
|
72
|
+
*)
|
|
73
|
+
sleep $INTERVAL
|
|
74
|
+
ELAPSED=$((ELAPSED + INTERVAL))
|
|
75
|
+
;;
|
|
76
|
+
esac
|
|
77
|
+
done
|
|
78
|
+
if [[ $ELAPSED -ge $WAIT_TIMEOUT ]]; then
|
|
79
|
+
log_error "Dependency wait timed out after ${WAIT_TIMEOUT}s"
|
|
80
|
+
exit 1
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
|
|
48
84
|
# ── Resolve settings ──────────────────────────────────────────────────
|
|
49
85
|
RESOLVED_AGENT=$(detect_agent "${AGENT:-}")
|
|
50
86
|
EFFORT="${EFFORT:-$(config_get default_effort high)}"
|
package/bin/web.sh
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# web.sh — Launch the ClawForge web dashboard
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
CLAWFORGE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
7
|
+
WEB_DIR="${CLAWFORGE_DIR}/web"
|
|
8
|
+
BINARY="${SCRIPT_DIR}/clawforge-web"
|
|
9
|
+
|
|
10
|
+
export CLAWFORGE_DIR
|
|
11
|
+
|
|
12
|
+
usage() {
|
|
13
|
+
cat <<EOF
|
|
14
|
+
Usage: clawforge web [options]
|
|
15
|
+
|
|
16
|
+
Launch the ClawForge web dashboard.
|
|
17
|
+
Accessible from your phone, tablet, or any browser on the network.
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
--port <port> Port to listen on (default: 9876)
|
|
21
|
+
--open Open in default browser
|
|
22
|
+
--build Force rebuild the web binary
|
|
23
|
+
--help Show this help
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
clawforge web # Start on http://localhost:9876
|
|
27
|
+
clawforge web --port 8080 # Custom port
|
|
28
|
+
clawforge web --open # Start + open browser
|
|
29
|
+
|
|
30
|
+
Keyboard shortcuts (in browser):
|
|
31
|
+
1/2/3/4 Filter: all/running/done/failed
|
|
32
|
+
Escape Close preview panel
|
|
33
|
+
Click task Open detail + agent output preview
|
|
34
|
+
EOF
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
PORT=9876
|
|
38
|
+
OPEN=false
|
|
39
|
+
BUILD=false
|
|
40
|
+
|
|
41
|
+
while [[ $# -gt 0 ]]; do
|
|
42
|
+
case "$1" in
|
|
43
|
+
--port|-p) PORT="$2"; shift 2 ;;
|
|
44
|
+
--open|-o) OPEN=true; shift ;;
|
|
45
|
+
--build) BUILD=true; shift ;;
|
|
46
|
+
--help|-h) usage; exit 0 ;;
|
|
47
|
+
*) shift ;;
|
|
48
|
+
esac
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
# Build if needed
|
|
52
|
+
if [[ ! -f "$BINARY" ]] || $BUILD; then
|
|
53
|
+
echo "Building web dashboard..."
|
|
54
|
+
if ! command -v go &>/dev/null; then
|
|
55
|
+
echo "Error: go is required. Install with: brew install go"
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
(cd "$WEB_DIR" && go build -o "$BINARY" .)
|
|
59
|
+
echo "Built: $BINARY"
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Open browser
|
|
63
|
+
if $OPEN; then
|
|
64
|
+
(sleep 1 && open "http://localhost:${PORT}" 2>/dev/null || xdg-open "http://localhost:${PORT}" 2>/dev/null) &
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Run
|
|
68
|
+
exec "$BINARY" --port="$PORT"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyperx/clawforge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Multi-mode coding workflow CLI for orchestrating AI coding agents",
|
|
5
5
|
"bin": {
|
|
6
6
|
"@cyperx/clawforge": "./bin/clawforge"
|
|
@@ -12,13 +12,23 @@
|
|
|
12
12
|
"type": "git",
|
|
13
13
|
"url": "https://github.com/cyperx84/clawforge.git"
|
|
14
14
|
},
|
|
15
|
-
"keywords": [
|
|
15
|
+
"keywords": [
|
|
16
|
+
"cli",
|
|
17
|
+
"coding",
|
|
18
|
+
"agents",
|
|
19
|
+
"orchestration",
|
|
20
|
+
"tmux",
|
|
21
|
+
"worktree"
|
|
22
|
+
],
|
|
16
23
|
"author": "cyperx84",
|
|
17
24
|
"license": "MIT",
|
|
18
25
|
"engines": {
|
|
19
26
|
"node": ">=18"
|
|
20
27
|
},
|
|
21
|
-
"os": [
|
|
28
|
+
"os": [
|
|
29
|
+
"darwin",
|
|
30
|
+
"linux"
|
|
31
|
+
],
|
|
22
32
|
"files": [
|
|
23
33
|
"bin/",
|
|
24
34
|
"lib/",
|