vibe-forge 0.1.0 → 0.2.1
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/.claude/commands/forge.md +147 -0
- package/.claude/settings.local.json +7 -0
- package/LICENSE +21 -21
- package/README.md +230 -211
- package/agents/forge-master/capabilities.md +144 -144
- package/agents/forge-master/context-template.md +128 -128
- package/bin/cli.js +3 -1
- package/bin/forge-daemon.sh +195 -71
- package/bin/forge-setup.sh +6 -6
- package/bin/forge-spawn.sh +46 -46
- package/bin/forge.sh +76 -127
- package/bin/lib/agents.sh +157 -0
- package/bin/lib/colors.sh +44 -0
- package/bin/lib/config.sh +259 -0
- package/bin/lib/constants.sh +143 -0
- package/config/agents.json +76 -0
- package/config/task-template.md +87 -87
- package/docs/TODO.md +65 -0
- package/docs/security.md +144 -0
- package/package.json +11 -2
package/bin/forge.sh
CHANGED
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
# forge daemon - Start/stop the background daemon
|
|
14
14
|
# forge help - Show help
|
|
15
15
|
#
|
|
16
|
+
# Security Note:
|
|
17
|
+
# This script uses --dangerously-skip-permissions for Claude Code to enable
|
|
18
|
+
# seamless agent startup. This is intentional for the forge workflow.
|
|
19
|
+
# See docs/security.md for details.
|
|
20
|
+
#
|
|
16
21
|
|
|
17
22
|
set -e
|
|
18
23
|
|
|
@@ -20,99 +25,24 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
20
25
|
FORGE_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
21
26
|
CONFIG_FILE="$FORGE_ROOT/.forge/config.json"
|
|
22
27
|
|
|
23
|
-
# Colors
|
|
24
|
-
RED='\033[0;31m'
|
|
25
|
-
GREEN='\033[0;32m'
|
|
26
|
-
YELLOW='\033[1;33m'
|
|
27
|
-
BLUE='\033[0;34m'
|
|
28
|
-
NC='\033[0m'
|
|
29
|
-
|
|
30
28
|
# =============================================================================
|
|
31
|
-
#
|
|
29
|
+
# Load Shared Libraries
|
|
32
30
|
# =============================================================================
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# Convert forward slashes to backslashes for Windows
|
|
49
|
-
GIT_BASH_PATH_WIN="${GIT_BASH_PATH//\//\\}"
|
|
50
|
-
export CLAUDE_CODE_GIT_BASH_PATH="$GIT_BASH_PATH_WIN"
|
|
51
|
-
|
|
52
|
-
# Add common Windows paths that Git Bash might miss
|
|
53
|
-
# npm global path (where claude is typically installed)
|
|
54
|
-
NPM_PATH="/c/Users/$USER/AppData/Roaming/npm"
|
|
55
|
-
if [[ -d "$NPM_PATH" ]] && [[ ":$PATH:" != *":$NPM_PATH:"* ]]; then
|
|
56
|
-
export PATH="$NPM_PATH:$PATH"
|
|
57
|
-
fi
|
|
58
|
-
|
|
59
|
-
# Also try with USERPROFILE
|
|
60
|
-
if [[ -n "$USERPROFILE" ]]; then
|
|
61
|
-
NPM_PATH_ALT="$USERPROFILE/AppData/Roaming/npm"
|
|
62
|
-
NPM_PATH_ALT="${NPM_PATH_ALT//\\//}" # Convert backslashes
|
|
63
|
-
if [[ -d "$NPM_PATH_ALT" ]] && [[ ":$PATH:" != *":$NPM_PATH_ALT:"* ]]; then
|
|
64
|
-
export PATH="$NPM_PATH_ALT:$PATH"
|
|
65
|
-
fi
|
|
66
|
-
fi
|
|
67
|
-
fi
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
get_personality_path() {
|
|
71
|
-
local agent="$1"
|
|
72
|
-
local personality_file=""
|
|
73
|
-
|
|
74
|
-
case "$agent" in
|
|
75
|
-
"hub"|"planning"|"master"|"forge-master"|"")
|
|
76
|
-
personality_file="$FORGE_ROOT/agents/planning-hub/personality.md"
|
|
77
|
-
;;
|
|
78
|
-
"anvil")
|
|
79
|
-
personality_file="$FORGE_ROOT/agents/anvil/personality.md"
|
|
80
|
-
;;
|
|
81
|
-
"furnace")
|
|
82
|
-
personality_file="$FORGE_ROOT/agents/furnace/personality.md"
|
|
83
|
-
;;
|
|
84
|
-
"crucible")
|
|
85
|
-
personality_file="$FORGE_ROOT/agents/crucible/personality.md"
|
|
86
|
-
;;
|
|
87
|
-
"sentinel")
|
|
88
|
-
personality_file="$FORGE_ROOT/agents/sentinel/personality.md"
|
|
89
|
-
;;
|
|
90
|
-
"scribe")
|
|
91
|
-
personality_file="$FORGE_ROOT/agents/scribe/personality.md"
|
|
92
|
-
;;
|
|
93
|
-
"herald")
|
|
94
|
-
personality_file="$FORGE_ROOT/agents/herald/personality.md"
|
|
95
|
-
;;
|
|
96
|
-
"ember")
|
|
97
|
-
personality_file="$FORGE_ROOT/agents/ember/personality.md"
|
|
98
|
-
;;
|
|
99
|
-
"aegis")
|
|
100
|
-
personality_file="$FORGE_ROOT/agents/aegis/personality.md"
|
|
101
|
-
;;
|
|
102
|
-
*)
|
|
103
|
-
echo -e "${RED}Unknown agent: $agent${NC}"
|
|
104
|
-
echo "Available agents: anvil, furnace, crucible, sentinel, scribe, herald, ember, aegis"
|
|
105
|
-
exit 1
|
|
106
|
-
;;
|
|
107
|
-
esac
|
|
108
|
-
|
|
109
|
-
if [[ ! -f "$personality_file" ]]; then
|
|
110
|
-
echo -e "${RED}Error: Personality file not found: $personality_file${NC}"
|
|
111
|
-
exit 1
|
|
112
|
-
fi
|
|
113
|
-
|
|
114
|
-
echo "$personality_file"
|
|
115
|
-
}
|
|
32
|
+
# shellcheck source=lib/colors.sh
|
|
33
|
+
source "$SCRIPT_DIR/lib/colors.sh"
|
|
34
|
+
# shellcheck source=lib/constants.sh
|
|
35
|
+
source "$SCRIPT_DIR/lib/constants.sh"
|
|
36
|
+
# shellcheck source=lib/config.sh
|
|
37
|
+
source "$SCRIPT_DIR/lib/config.sh"
|
|
38
|
+
# shellcheck source=lib/agents.sh
|
|
39
|
+
source "$SCRIPT_DIR/lib/agents.sh"
|
|
40
|
+
|
|
41
|
+
# Load agent configuration from JSON if available
|
|
42
|
+
# This overwrites the fallback values in constants.sh
|
|
43
|
+
if [[ -f "$FORGE_ROOT/$AGENTS_CONFIG" ]]; then
|
|
44
|
+
load_agents_from_json "$FORGE_ROOT/$AGENTS_CONFIG" 2>/dev/null || true
|
|
45
|
+
fi
|
|
116
46
|
|
|
117
47
|
# =============================================================================
|
|
118
48
|
# Commands
|
|
@@ -124,19 +54,31 @@ cmd_init() {
|
|
|
124
54
|
|
|
125
55
|
cmd_start() {
|
|
126
56
|
local agent="${1:-hub}"
|
|
127
|
-
load_config
|
|
128
57
|
|
|
58
|
+
# Load and validate config
|
|
59
|
+
require_forge_config "$FORGE_ROOT"
|
|
60
|
+
|
|
61
|
+
# Validate and resolve agent name (SECURITY: whitelist validation)
|
|
62
|
+
local resolved
|
|
63
|
+
resolved=$(resolve_agent "$agent") || {
|
|
64
|
+
log_error "Unknown agent: $agent"
|
|
65
|
+
echo ""
|
|
66
|
+
show_available_agents
|
|
67
|
+
exit 1
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Get personality path (SECURITY: path traversal protection)
|
|
129
71
|
local personality_path
|
|
130
|
-
personality_path=$(
|
|
72
|
+
personality_path=$(get_agent_personality_path "$FORGE_ROOT" "$resolved") || {
|
|
73
|
+
log_error "Personality file not found for agent: $resolved"
|
|
74
|
+
exit 1
|
|
75
|
+
}
|
|
131
76
|
|
|
77
|
+
# Get display name
|
|
132
78
|
local agent_name
|
|
133
|
-
|
|
134
|
-
agent_name="Planning Hub"
|
|
135
|
-
else
|
|
136
|
-
agent_name="$agent"
|
|
137
|
-
fi
|
|
79
|
+
agent_name=$(get_agent_display_name "$resolved")
|
|
138
80
|
|
|
139
|
-
|
|
81
|
+
log_header "🔥 Starting $agent_name..."
|
|
140
82
|
echo ""
|
|
141
83
|
|
|
142
84
|
# Build the system prompt from personality file
|
|
@@ -144,7 +86,7 @@ cmd_start() {
|
|
|
144
86
|
system_prompt=$(cat "$personality_path")
|
|
145
87
|
|
|
146
88
|
# Add project context if it exists
|
|
147
|
-
local project_context="$FORGE_ROOT/
|
|
89
|
+
local project_context="$FORGE_ROOT/$CONTEXT_DIR/project-context.md"
|
|
148
90
|
if [[ -f "$project_context" ]]; then
|
|
149
91
|
system_prompt="$system_prompt
|
|
150
92
|
|
|
@@ -156,7 +98,7 @@ $(cat "$project_context")"
|
|
|
156
98
|
fi
|
|
157
99
|
|
|
158
100
|
# Add startup instructions based on agent type
|
|
159
|
-
if [[ "$
|
|
101
|
+
if [[ "$resolved" == "hub" ]]; then
|
|
160
102
|
# Planning Hub startup - Party Mode Team
|
|
161
103
|
system_prompt="$system_prompt
|
|
162
104
|
|
|
@@ -180,8 +122,8 @@ On startup: Announce yourself (name, icon, role), check tasks/pending/ and tasks
|
|
|
180
122
|
|
|
181
123
|
# Launch Claude Code with the personality
|
|
182
124
|
# --dangerously-skip-permissions avoids repeated prompts when starting agents
|
|
183
|
-
#
|
|
184
|
-
if [[ "$
|
|
125
|
+
# This is documented in docs/security.md
|
|
126
|
+
if [[ "$resolved" == "hub" ]]; then
|
|
185
127
|
claude --dangerously-skip-permissions --system-prompt "$system_prompt" "begin"
|
|
186
128
|
else
|
|
187
129
|
claude --dangerously-skip-permissions --system-prompt "$system_prompt" "startup"
|
|
@@ -189,13 +131,12 @@ On startup: Announce yourself (name, icon, role), check tasks/pending/ and tasks
|
|
|
189
131
|
}
|
|
190
132
|
|
|
191
133
|
cmd_status() {
|
|
192
|
-
|
|
134
|
+
require_forge_config "$FORGE_ROOT"
|
|
193
135
|
|
|
194
|
-
local state_file="$FORGE_ROOT/
|
|
136
|
+
local state_file="$FORGE_ROOT/$CONTEXT_DIR/forge-state.yaml"
|
|
195
137
|
|
|
196
138
|
echo ""
|
|
197
|
-
|
|
198
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
139
|
+
log_header "🔥 Forge Status"
|
|
199
140
|
|
|
200
141
|
if [[ -f "$state_file" ]]; then
|
|
201
142
|
cat "$state_file"
|
|
@@ -210,18 +151,18 @@ cmd_status() {
|
|
|
210
151
|
}
|
|
211
152
|
|
|
212
153
|
cmd_test() {
|
|
213
|
-
|
|
154
|
+
require_forge_config "$FORGE_ROOT"
|
|
214
155
|
|
|
215
156
|
echo ""
|
|
216
|
-
|
|
157
|
+
log_header "🧪 Testing Vibe Forge setup..."
|
|
217
158
|
echo ""
|
|
218
159
|
|
|
219
160
|
# Test Claude Code
|
|
220
161
|
echo "Testing Claude Code..."
|
|
221
162
|
if claude --version &> /dev/null; then
|
|
222
|
-
|
|
163
|
+
log_success "Claude Code working"
|
|
223
164
|
else
|
|
224
|
-
|
|
165
|
+
log_error "Claude Code not working"
|
|
225
166
|
exit 1
|
|
226
167
|
fi
|
|
227
168
|
|
|
@@ -232,24 +173,32 @@ cmd_test() {
|
|
|
232
173
|
test_output=$(claude --system-prompt "You are a test. Respond with only: FORGE_TEST_OK" --print "test" 2>&1 | head -1)
|
|
233
174
|
|
|
234
175
|
if [[ "$test_output" == *"FORGE_TEST_OK"* || "$test_output" == *"test"* ]]; then
|
|
235
|
-
|
|
176
|
+
log_success "Personality loading working"
|
|
236
177
|
else
|
|
237
|
-
|
|
178
|
+
log_warn "Personality loading may have issues"
|
|
238
179
|
echo " Output: $test_output"
|
|
239
180
|
fi
|
|
240
181
|
|
|
241
182
|
echo ""
|
|
242
|
-
|
|
183
|
+
log_success "🔥 Setup validated!"
|
|
243
184
|
}
|
|
244
185
|
|
|
245
186
|
cmd_spawn() {
|
|
246
187
|
local agent="${1:-}"
|
|
247
188
|
|
|
248
189
|
if [[ -z "$agent" ]]; then
|
|
249
|
-
|
|
190
|
+
log_error "No agent specified."
|
|
250
191
|
echo "Usage: forge spawn <agent>"
|
|
251
192
|
echo ""
|
|
252
|
-
|
|
193
|
+
show_available_agents
|
|
194
|
+
exit 1
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# Validate agent before passing to spawn script (SECURITY: whitelist check)
|
|
198
|
+
if ! is_valid_agent "$agent"; then
|
|
199
|
+
log_error "Unknown agent: $agent"
|
|
200
|
+
echo ""
|
|
201
|
+
show_available_agents
|
|
253
202
|
exit 1
|
|
254
203
|
fi
|
|
255
204
|
|
|
@@ -286,8 +235,7 @@ cmd_daemon() {
|
|
|
286
235
|
|
|
287
236
|
cmd_help() {
|
|
288
237
|
echo ""
|
|
289
|
-
|
|
290
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
238
|
+
log_header "🔥 Vibe Forge"
|
|
291
239
|
echo ""
|
|
292
240
|
echo "Usage: forge [command] [options]"
|
|
293
241
|
echo ""
|
|
@@ -301,20 +249,21 @@ cmd_help() {
|
|
|
301
249
|
echo " daemon <action> Manage background daemon (start|stop|status|notifications|clear)"
|
|
302
250
|
echo " help Show this help message"
|
|
303
251
|
echo ""
|
|
304
|
-
echo "Agents:"
|
|
305
|
-
echo " anvil
|
|
306
|
-
echo " furnace
|
|
307
|
-
echo " crucible
|
|
308
|
-
echo " sentinel
|
|
309
|
-
echo " scribe
|
|
310
|
-
echo " herald
|
|
311
|
-
echo " ember
|
|
312
|
-
echo " aegis
|
|
252
|
+
echo "Agents (with aliases):"
|
|
253
|
+
echo " anvil (frontend, ui, fe) - Frontend Developer"
|
|
254
|
+
echo " furnace (backend, api, be) - Backend Developer"
|
|
255
|
+
echo " crucible (test, testing, qa) - Tester / QA"
|
|
256
|
+
echo " sentinel (review, reviewer, cr) - Code Reviewer"
|
|
257
|
+
echo " scribe (docs, documentation) - Documentation"
|
|
258
|
+
echo " herald (release, deploy) - Release Manager"
|
|
259
|
+
echo " ember (devops, ops, infra) - DevOps"
|
|
260
|
+
echo " aegis (security, sec, appsec) - Security"
|
|
313
261
|
echo ""
|
|
314
262
|
echo "Examples:"
|
|
315
263
|
echo " forge Start Planning Hub"
|
|
316
264
|
echo " forge init Initialize for new project"
|
|
317
265
|
echo " forge start anvil Start Anvil (frontend) agent"
|
|
266
|
+
echo " forge spawn fe Spawn frontend agent in new terminal"
|
|
318
267
|
echo " forge status Check current status"
|
|
319
268
|
echo ""
|
|
320
269
|
}
|
|
@@ -357,7 +306,7 @@ main() {
|
|
|
357
306
|
cmd_start "hub"
|
|
358
307
|
;;
|
|
359
308
|
*)
|
|
360
|
-
|
|
309
|
+
log_error "Unknown command: $command"
|
|
361
310
|
echo "Run 'forge help' for usage."
|
|
362
311
|
exit 1
|
|
363
312
|
;;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Vibe Forge - Agent Resolution and Validation
|
|
4
|
+
# Source this file in other scripts: source "$SCRIPT_DIR/lib/agents.sh"
|
|
5
|
+
#
|
|
6
|
+
# SECURITY: This module provides safe agent name resolution with whitelist validation.
|
|
7
|
+
# Never pass user input directly to shell commands - always resolve through these functions.
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
# Ensure constants are loaded
|
|
11
|
+
if [[ -z "${VALID_AGENTS+x}" ]]; then
|
|
12
|
+
echo "Error: constants.sh must be sourced before agents.sh" >&2
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# resolve_agent ALIAS
|
|
17
|
+
# Converts an agent alias to its canonical name.
|
|
18
|
+
# Returns: canonical name on stdout, empty string if not found
|
|
19
|
+
# Exit code: 0 if found, 1 if not found
|
|
20
|
+
#
|
|
21
|
+
# SECURITY: This function validates against a whitelist.
|
|
22
|
+
# The returned value is safe to use in file paths and commands.
|
|
23
|
+
resolve_agent() {
|
|
24
|
+
local input="$1"
|
|
25
|
+
|
|
26
|
+
# Normalize to lowercase for matching
|
|
27
|
+
local normalized
|
|
28
|
+
normalized=$(echo "$input" | tr '[:upper:]' '[:lower:]')
|
|
29
|
+
|
|
30
|
+
# Look up in alias map
|
|
31
|
+
local canonical="${AGENT_ALIASES[$normalized]:-}"
|
|
32
|
+
|
|
33
|
+
if [[ -n "$canonical" ]]; then
|
|
34
|
+
echo "$canonical"
|
|
35
|
+
return 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
return 1
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# is_valid_agent AGENT
|
|
42
|
+
# Checks if an agent name is valid (either canonical or alias)
|
|
43
|
+
# Returns: 0 if valid, 1 if invalid
|
|
44
|
+
is_valid_agent() {
|
|
45
|
+
local agent="$1"
|
|
46
|
+
resolve_agent "$agent" >/dev/null 2>&1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# get_agent_personality_path FORGE_ROOT AGENT
|
|
50
|
+
# Returns the path to an agent's personality file.
|
|
51
|
+
# Uses AGENT_PERSONALITY_FILES from constants.sh (or loaded from agents.json)
|
|
52
|
+
# SECURITY: Validates agent name before constructing path.
|
|
53
|
+
# Returns: full path on stdout
|
|
54
|
+
# Exit code: 0 on success, 1 if agent invalid or file missing
|
|
55
|
+
get_agent_personality_path() {
|
|
56
|
+
local forge_root="$1"
|
|
57
|
+
local agent="$2"
|
|
58
|
+
|
|
59
|
+
# Resolve and validate agent
|
|
60
|
+
local canonical
|
|
61
|
+
canonical=$(resolve_agent "$agent") || {
|
|
62
|
+
return 1
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Get personality file path from configuration
|
|
66
|
+
local relative_path="${AGENT_PERSONALITY_FILES[$canonical]:-}"
|
|
67
|
+
if [[ -z "$relative_path" ]]; then
|
|
68
|
+
# Fallback: construct path if not in config
|
|
69
|
+
relative_path="agents/$canonical/personality.md"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
local personality_path="$forge_root/$relative_path"
|
|
73
|
+
|
|
74
|
+
# Validate file exists
|
|
75
|
+
if [[ ! -f "$personality_path" ]]; then
|
|
76
|
+
return 1
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# SECURITY: Verify the resolved path is within agents directory
|
|
80
|
+
local real_path
|
|
81
|
+
real_path=$(cd "$(dirname "$personality_path")" 2>/dev/null && pwd)/$(basename "$personality_path")
|
|
82
|
+
local agents_dir
|
|
83
|
+
agents_dir=$(cd "$forge_root/agents" 2>/dev/null && pwd)
|
|
84
|
+
|
|
85
|
+
if [[ "$real_path" != "$agents_dir"/* ]]; then
|
|
86
|
+
echo "Security error: Path traversal detected" >&2
|
|
87
|
+
return 1
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
echo "$personality_path"
|
|
91
|
+
return 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# show_available_agents
|
|
95
|
+
# Prints a formatted list of available agents with aliases
|
|
96
|
+
# Uses data from AGENT_DISPLAY_NAMES, AGENT_ROLES, and AGENT_ALIASES
|
|
97
|
+
show_available_agents() {
|
|
98
|
+
echo "Available agents:"
|
|
99
|
+
|
|
100
|
+
# Iterate over valid agents (excluding hub for user display)
|
|
101
|
+
for agent in "${VALID_AGENTS[@]}"; do
|
|
102
|
+
if [[ "$agent" == "hub" ]]; then
|
|
103
|
+
continue
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# Get display info
|
|
107
|
+
local role="${AGENT_ROLES[$agent]:-}"
|
|
108
|
+
|
|
109
|
+
# Collect aliases for this agent
|
|
110
|
+
local aliases=()
|
|
111
|
+
for alias in "${!AGENT_ALIASES[@]}"; do
|
|
112
|
+
if [[ "${AGENT_ALIASES[$alias]}" == "$agent" && "$alias" != "$agent" ]]; then
|
|
113
|
+
aliases+=("$alias")
|
|
114
|
+
fi
|
|
115
|
+
done
|
|
116
|
+
|
|
117
|
+
# Format aliases (take first 3)
|
|
118
|
+
local alias_str=""
|
|
119
|
+
if [[ ${#aliases[@]} -gt 0 ]]; then
|
|
120
|
+
# Sort and take first 3
|
|
121
|
+
IFS=$'\n' sorted=($(printf '%s\n' "${aliases[@]}" | sort)); unset IFS
|
|
122
|
+
local display_aliases=("${sorted[@]:0:3}")
|
|
123
|
+
alias_str="($(IFS=", "; echo "${display_aliases[*]}"))"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# Print formatted line
|
|
127
|
+
printf " %-9s %-25s - %s\n" "$agent" "$alias_str" "$role"
|
|
128
|
+
done
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# get_agent_display_name AGENT
|
|
132
|
+
# Returns the display name for an agent (e.g., "Anvil" for "anvil")
|
|
133
|
+
# Uses AGENT_DISPLAY_NAMES from constants.sh (or loaded from agents.json)
|
|
134
|
+
get_agent_display_name() {
|
|
135
|
+
local agent="$1"
|
|
136
|
+
local canonical
|
|
137
|
+
canonical=$(resolve_agent "$agent") || return 1
|
|
138
|
+
|
|
139
|
+
# Look up in AGENT_DISPLAY_NAMES array
|
|
140
|
+
local display_name="${AGENT_DISPLAY_NAMES[$canonical]:-}"
|
|
141
|
+
if [[ -n "$display_name" ]]; then
|
|
142
|
+
echo "$display_name"
|
|
143
|
+
else
|
|
144
|
+
# Fallback to canonical name with first letter capitalized
|
|
145
|
+
echo "${canonical^}"
|
|
146
|
+
fi
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# get_agent_role AGENT
|
|
150
|
+
# Returns the role description for an agent
|
|
151
|
+
get_agent_role() {
|
|
152
|
+
local agent="$1"
|
|
153
|
+
local canonical
|
|
154
|
+
canonical=$(resolve_agent "$agent") || return 1
|
|
155
|
+
|
|
156
|
+
echo "${AGENT_ROLES[$canonical]:-}"
|
|
157
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Vibe Forge - Shared Color Definitions and Logging
|
|
4
|
+
# Source this file in other scripts: source "$SCRIPT_DIR/lib/colors.sh"
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
# Colors (only if terminal supports them)
|
|
8
|
+
if [[ -t 1 ]]; then
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
GREEN='\033[0;32m'
|
|
11
|
+
YELLOW='\033[1;33m'
|
|
12
|
+
BLUE='\033[0;34m'
|
|
13
|
+
CYAN='\033[0;36m'
|
|
14
|
+
NC='\033[0m'
|
|
15
|
+
else
|
|
16
|
+
RED=''
|
|
17
|
+
GREEN=''
|
|
18
|
+
YELLOW=''
|
|
19
|
+
BLUE=''
|
|
20
|
+
CYAN=''
|
|
21
|
+
NC=''
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Logging functions
|
|
25
|
+
log_error() {
|
|
26
|
+
echo -e "${RED}Error: $1${NC}" >&2
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
log_success() {
|
|
30
|
+
echo -e "${GREEN}✓ $1${NC}"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
log_info() {
|
|
34
|
+
echo -e "${BLUE}ℹ $1${NC}"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
log_warn() {
|
|
38
|
+
echo -e "${YELLOW}⚠ $1${NC}"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
log_header() {
|
|
42
|
+
echo -e "${YELLOW}$1${NC}"
|
|
43
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
44
|
+
}
|