@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 CHANGED
@@ -1 +1 @@
1
- 1.2.0
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
 
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
@@ -117,7 +117,55 @@ if [[ -n "$WEBHOOK" ]]; then
117
117
  fi
118
118
  fi
119
119
 
120
- # 3. Auto-clean
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
- # 4. Mark hooks as fired
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
- # 5. Log completion
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[@]}"
@@ -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.2.0",
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": ["cli", "coding", "agents", "orchestration", "tmux", "worktree"],
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": ["darwin", "linux"],
28
+ "os": [
29
+ "darwin",
30
+ "linux"
31
+ ],
22
32
  "files": [
23
33
  "bin/",
24
34
  "lib/",