@sugar-crash-studios/vibe-forge 0.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/.claude/commands/clear-attention.md +63 -0
- package/.claude/commands/compact-context.md +52 -0
- package/.claude/commands/configure-vcs.md +102 -0
- package/.claude/commands/forge.md +171 -0
- package/.claude/commands/need-help.md +77 -0
- package/.claude/commands/update-status.md +64 -0
- package/.claude/commands/worker-loop.md +106 -0
- package/.claude/hooks/worker-loop.js +198 -0
- package/.claude/scripts/setup-worker-loop.sh +45 -0
- package/.claude/settings.local.json +46 -0
- package/LICENSE +21 -0
- package/README.md +238 -0
- package/agents/aegis/personality.md +294 -0
- package/agents/anvil/personality.md +276 -0
- package/agents/architect/personality.md +258 -0
- package/agents/crucible/personality.md +360 -0
- package/agents/ember/personality.md +291 -0
- package/agents/forge-master/capabilities.md +144 -0
- package/agents/forge-master/context-template.md +128 -0
- package/agents/forge-master/personality.md +138 -0
- package/agents/furnace/personality.md +340 -0
- package/agents/herald/personality.md +247 -0
- package/agents/loki/personality.md +108 -0
- package/agents/oracle/personality.md +283 -0
- package/agents/pixel/personality.md +113 -0
- package/agents/planning-hub/personality.md +320 -0
- package/agents/scribe/personality.md +251 -0
- package/agents/temper/personality.md +218 -0
- package/bin/cli.js +375 -0
- package/bin/dashboard/api/agents.js +333 -0
- package/bin/dashboard/api/dispatch.js +483 -0
- package/bin/dashboard/api/tasks.js +416 -0
- package/bin/dashboard/frontend/index.html +13 -0
- package/bin/dashboard/frontend/package.json +16 -0
- package/bin/dashboard/frontend/src/App.svelte +222 -0
- package/bin/dashboard/frontend/src/app.css +1777 -0
- package/bin/dashboard/frontend/src/lib/components/AgentCard.svelte +60 -0
- package/bin/dashboard/frontend/src/lib/components/AgentsPanel.svelte +57 -0
- package/bin/dashboard/frontend/src/lib/components/DispatchModal.svelte +180 -0
- package/bin/dashboard/frontend/src/lib/components/Footer.svelte +33 -0
- package/bin/dashboard/frontend/src/lib/components/Header.svelte +84 -0
- package/bin/dashboard/frontend/src/lib/components/IssueCard.svelte +33 -0
- package/bin/dashboard/frontend/src/lib/components/IssuesPanel.svelte +73 -0
- package/bin/dashboard/frontend/src/lib/components/KeyboardShortcutsModal.svelte +108 -0
- package/bin/dashboard/frontend/src/lib/components/MobileTabs.svelte +52 -0
- package/bin/dashboard/frontend/src/lib/components/NotificationCard.svelte +60 -0
- package/bin/dashboard/frontend/src/lib/components/NotificationsPanel.svelte +44 -0
- package/bin/dashboard/frontend/src/lib/components/TaskCard.svelte +63 -0
- package/bin/dashboard/frontend/src/lib/components/TasksPanel.svelte +82 -0
- package/bin/dashboard/frontend/src/lib/components/Toast.svelte +45 -0
- package/bin/dashboard/frontend/src/lib/stores/agents.js +34 -0
- package/bin/dashboard/frontend/src/lib/stores/issues.js +54 -0
- package/bin/dashboard/frontend/src/lib/stores/notifications.js +48 -0
- package/bin/dashboard/frontend/src/lib/stores/tasks.js +63 -0
- package/bin/dashboard/frontend/src/lib/stores/theme.js +33 -0
- package/bin/dashboard/frontend/src/lib/stores/toast.js +35 -0
- package/bin/dashboard/frontend/src/lib/stores/ui.js +25 -0
- package/bin/dashboard/frontend/src/lib/stores/voice.js +275 -0
- package/bin/dashboard/frontend/src/lib/stores/websocket.js +295 -0
- package/bin/dashboard/frontend/src/lib/utils/api.js +101 -0
- package/bin/dashboard/frontend/src/lib/utils/formatters.js +54 -0
- package/bin/dashboard/frontend/src/main.js +9 -0
- package/bin/dashboard/frontend/svelte.config.js +5 -0
- package/bin/dashboard/frontend/vite.config.js +20 -0
- package/bin/dashboard/public/assets/index-DnfVj9Ce.css +1 -0
- package/bin/dashboard/public/assets/index-Ze5h0kXQ.js +2 -0
- package/bin/dashboard/public/index.html +14 -0
- package/bin/dashboard/server.js +566 -0
- package/bin/forge-daemon.sh +463 -0
- package/bin/forge-setup.sh +645 -0
- package/bin/forge-spawn.sh +164 -0
- package/bin/forge.cmd +83 -0
- package/bin/forge.sh +533 -0
- package/bin/lib/agents.sh +177 -0
- package/bin/lib/colors.sh +44 -0
- package/bin/lib/config.sh +347 -0
- package/bin/lib/constants.sh +241 -0
- package/bin/lib/daemon/display.sh +128 -0
- package/bin/lib/daemon/notifications.sh +263 -0
- package/bin/lib/daemon/routing.sh +77 -0
- package/bin/lib/daemon/state.sh +115 -0
- package/bin/lib/daemon/sync.sh +95 -0
- package/bin/lib/database.sh +310 -0
- package/bin/lib/heimdall-setup.js +113 -0
- package/bin/lib/heimdall.js +265 -0
- package/bin/lib/json.sh +264 -0
- package/bin/lib/terminal.js +451 -0
- package/bin/lib/util.sh +126 -0
- package/bin/lib/vcs.js +349 -0
- package/config/agent-manifest.yaml +203 -0
- package/config/agents.json +168 -0
- package/config/task-template.md +159 -0
- package/config/task-types.yaml +106 -0
- package/context/agent-status/aegis.json +7 -0
- package/context/agent-status/anvil.json +7 -0
- package/context/agent-status/architect.json +7 -0
- package/context/agent-status/crucible.json +7 -0
- package/context/agent-status/ember.json +7 -0
- package/context/agent-status/furnace.json +7 -0
- package/context/agent-status/loki.json +7 -0
- package/context/agent-status/oracle.json +7 -0
- package/context/agent-status/pixel.json +7 -0
- package/context/agent-status/planning-hub.json +7 -0
- package/context/agent-status/scribe.json +7 -0
- package/context/agent-status/temper.json +7 -0
- package/context/feature-brainstorm.md +426 -0
- package/context/forge-state.yaml +19 -0
- package/context/modern-conventions.md +129 -0
- package/context/project-context-template.md +122 -0
- package/context/project-context.md +122 -0
- package/docs/TODO.md +150 -0
- package/docs/agents.md +409 -0
- package/docs/architecture/decisions/ADR-001-daemon-modularization.md +122 -0
- package/docs/architecture/vibe-lab-integration.md +684 -0
- package/docs/architecture.md +194 -0
- package/docs/bmad-gap-analysis-2026-03-31.md +444 -0
- package/docs/cleanup-workflow.md +329 -0
- package/docs/commands.md +451 -0
- package/docs/dashboard-mockup.html +989 -0
- package/docs/getting-started.md +261 -0
- package/docs/integration/forge-ownership-policy.md +112 -0
- package/docs/npm-publishing.md +132 -0
- package/docs/roadmap-2026.md +519 -0
- package/docs/security.md +144 -0
- package/docs/wireframes/dashboard-mvp.md +1164 -0
- package/docs/workflows/README.md +32 -0
- package/docs/workflows/azure-devops.md +108 -0
- package/docs/workflows/bitbucket.md +104 -0
- package/docs/workflows/git-only.md +130 -0
- package/docs/workflows/gitea.md +168 -0
- package/docs/workflows/github.md +103 -0
- package/docs/workflows/gitlab.md +105 -0
- package/docs/workflows.md +454 -0
- package/package.json +73 -0
- package/tasks/completed/ARCH-001-duplicate-agent-config.md +121 -0
- package/tasks/completed/ARCH-002-mixed-bash-node-implementation.md +88 -0
- package/tasks/completed/ARCH-003-worker-loop-hook-duplication.md +77 -0
- package/tasks/completed/ARCH-009-test-organization.md +78 -0
- package/tasks/completed/ARCH-011-jq-vs-nodejs-json.md +94 -0
- package/tasks/completed/ARCH-012-tmp-files-in-root.md +71 -0
- package/tasks/completed/ARCH-013-exit-code-constants.md +65 -0
- package/tasks/completed/ARCH-014-sed-incompatibility.md +96 -0
- package/tasks/completed/ARCH-015-docs-todo-tracking.md +83 -0
- package/tasks/completed/BUG-dash-001-tasks-filter-error.md +31 -0
- package/tasks/completed/BUG-dash-002-agents-unknown.md +41 -0
- package/tasks/completed/CLEAN-001.md +38 -0
- package/tasks/completed/CLEAN-002.md +43 -0
- package/tasks/completed/CLEAN-003.md +47 -0
- package/tasks/completed/CLEAN-004.md +56 -0
- package/tasks/completed/CLEAN-005.md +75 -0
- package/tasks/completed/CLEAN-006.md +47 -0
- package/tasks/completed/CLEAN-007.md +34 -0
- package/tasks/completed/CLEAN-008.md +49 -0
- package/tasks/completed/CLEAN-012.md +58 -0
- package/tasks/completed/CLEAN-013.md +45 -0
- package/tasks/completed/FEATURE-001a-dashboard-wireframes.md +162 -0
- package/tasks/completed/IMPL-007a-daemon-notifications-module.md +82 -0
- package/tasks/completed/IMPL-007b-daemon-sync-module.md +71 -0
- package/tasks/completed/IMPL-007c-daemon-state-module.md +80 -0
- package/tasks/completed/IMPL-007d-daemon-routing-module.md +77 -0
- package/tasks/completed/IMPL-007e-daemon-display-module.md +77 -0
- package/tasks/completed/IMPL-007f-daemon-integration.md +124 -0
- package/tasks/completed/PLAT-1-heimdall.md +420 -0
- package/tasks/completed/SEC-001-sql-injection-fix.md +58 -0
- package/tasks/completed/SEC-002-notification-injection-fix.md +45 -0
- package/tasks/completed/SEC-003-eval-injection-fix.md +54 -0
- package/tasks/completed/SEC-004-pid-race-condition-fix.md +49 -0
- package/tasks/completed/SEC-005-worker-loop-path-fix.md +51 -0
- package/tasks/completed/SEC-006-eval-agent-names.md +55 -0
- package/tasks/completed/SEC-007-spawn-escaping.md +67 -0
- package/tasks/completed/TASK-DASH-001-server-infrastructure.md +185 -0
- package/tasks/completed/TASK-anvil-001-dashboard-frontend.md +133 -0
- package/tasks/completed/review-bmad-aegis.md +89 -0
- package/tasks/completed/review-bmad-anvil.md +80 -0
- package/tasks/completed/review-bmad-crucible.md +81 -0
- package/tasks/completed/review-bmad-ember.md +90 -0
- package/tasks/completed/review-bmad-furnace.md +79 -0
- package/tasks/completed/review-bmad-pixel.md +82 -0
- package/tasks/completed/review-bmad-scribe.md +92 -0
- package/tasks/completed/review-bmad-sentinel.md +83 -0
- package/tasks/pending/ARCH-004-git-bash-detection-duplication.md +72 -0
- package/tasks/pending/ARCH-005-missing-src-directory.md +95 -0
- package/tasks/pending/ARCH-006-task-template-location.md +64 -0
- package/tasks/pending/ARCH-008-forge-master-vs-hub.md +81 -0
- package/tasks/pending/ARCH-010-missing-index-files.md +84 -0
- package/tasks/pending/CLEAN-009.md +31 -0
- package/tasks/pending/CLEAN-010.md +30 -0
- package/tasks/pending/CLEAN-011.md +30 -0
- package/tasks/pending/CLEAN-014.md +32 -0
- package/tasks/pending/DESIGN-dash-001-layout-review.md +45 -0
- package/tasks/pending/FEATURE-001-dashboard-mvp.md +268 -0
- package/tasks/review/ARCH-007-daemon-monolith.md +162 -0
- package/tasks/review/bmad-review-aegis.md +349 -0
- package/tasks/review/bmad-review-anvil.md +259 -0
- package/tasks/review/bmad-review-crucible.md +277 -0
- package/tasks/review/bmad-review-ember.md +307 -0
- package/tasks/review/bmad-review-furnace.md +285 -0
- package/tasks/review/bmad-review-pixel.md +329 -0
- package/tasks/review/bmad-review-scribe.md +361 -0
- package/tasks/review/bmad-review-sentinel.md +242 -0
- package/tasks/review/task-001.md +78 -0
package/bin/forge.sh
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Vibe Forge - Main Launcher
|
|
4
|
+
# Starts the Planning Hub or specific worker agents
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# forge - Start Planning Hub (main session)
|
|
8
|
+
# forge init - Initialize Vibe Forge
|
|
9
|
+
# forge start <agent> - Start a specific worker agent
|
|
10
|
+
# forge spawn <agent> - Spawn agent in new terminal window/tab
|
|
11
|
+
# forge status - Show forge status
|
|
12
|
+
# forge doctor - Full environment diagnostics (alias: test)
|
|
13
|
+
# forge daemon - Start/stop the background daemon
|
|
14
|
+
# forge help - Show help
|
|
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
|
+
#
|
|
21
|
+
|
|
22
|
+
set -e
|
|
23
|
+
|
|
24
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
|
+
FORGE_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
26
|
+
CONFIG_FILE="$FORGE_ROOT/.forge/config.json"
|
|
27
|
+
|
|
28
|
+
# =============================================================================
|
|
29
|
+
# Load Shared Libraries
|
|
30
|
+
# =============================================================================
|
|
31
|
+
|
|
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/json.sh
|
|
39
|
+
source "$SCRIPT_DIR/lib/json.sh"
|
|
40
|
+
# shellcheck source=lib/agents.sh
|
|
41
|
+
source "$SCRIPT_DIR/lib/agents.sh"
|
|
42
|
+
|
|
43
|
+
# Load agent configuration from JSON if available
|
|
44
|
+
# This overwrites the fallback values in constants.sh
|
|
45
|
+
if [[ -f "$FORGE_ROOT/$AGENTS_CONFIG" ]]; then
|
|
46
|
+
if ! load_agents_from_json "$FORGE_ROOT/$AGENTS_CONFIG" 2>/dev/null; then
|
|
47
|
+
log_warn "Failed to load agents.json, using fallback defaults"
|
|
48
|
+
fi
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# =============================================================================
|
|
52
|
+
# Commands
|
|
53
|
+
# =============================================================================
|
|
54
|
+
|
|
55
|
+
cmd_init() {
|
|
56
|
+
"$SCRIPT_DIR/forge-setup.sh" "$@"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
cmd_start() {
|
|
60
|
+
local agent="${1:-hub}"
|
|
61
|
+
|
|
62
|
+
# Load and validate config
|
|
63
|
+
require_forge_config "$FORGE_ROOT"
|
|
64
|
+
|
|
65
|
+
# Validate and resolve agent name (SECURITY: whitelist validation)
|
|
66
|
+
local resolved
|
|
67
|
+
resolved=$(resolve_agent "$agent") || {
|
|
68
|
+
log_error "Unknown agent: $agent"
|
|
69
|
+
echo ""
|
|
70
|
+
show_available_agents
|
|
71
|
+
exit $EXIT_INVALID_ARGUMENT
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Get personality path (SECURITY: path traversal protection)
|
|
75
|
+
local personality_path
|
|
76
|
+
personality_path=$(get_agent_personality_path "$FORGE_ROOT" "$resolved") || {
|
|
77
|
+
log_error "Personality file not found for agent: $resolved"
|
|
78
|
+
exit $EXIT_CONFIG_ERROR
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Get display name
|
|
82
|
+
local agent_name
|
|
83
|
+
agent_name=$(get_agent_display_name "$resolved")
|
|
84
|
+
|
|
85
|
+
log_header "🔥 Starting $agent_name..."
|
|
86
|
+
echo ""
|
|
87
|
+
|
|
88
|
+
# Build the system prompt from personality file
|
|
89
|
+
local system_prompt
|
|
90
|
+
system_prompt=$(cat "$personality_path")
|
|
91
|
+
|
|
92
|
+
# Add project context if it exists
|
|
93
|
+
local project_context="$FORGE_ROOT/$CONTEXT_DIR/project-context.md"
|
|
94
|
+
if [[ -f "$project_context" ]]; then
|
|
95
|
+
system_prompt="$system_prompt
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
# Project Context
|
|
100
|
+
|
|
101
|
+
$(cat "$project_context")"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# Add startup instructions based on agent type
|
|
105
|
+
if [[ "$resolved" == "hub" ]]; then
|
|
106
|
+
# Planning Hub startup - Party Mode Team
|
|
107
|
+
system_prompt="$system_prompt
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
# Startup Instructions
|
|
112
|
+
|
|
113
|
+
On startup, you MUST immediately display the team assembly welcome as shown in the Startup Behavior section of your personality. Show the forge council members assembling with their icons and roles. Then check for any current work status.
|
|
114
|
+
"
|
|
115
|
+
else
|
|
116
|
+
# Worker agent startup
|
|
117
|
+
system_prompt="$system_prompt
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
# Startup Instructions
|
|
122
|
+
|
|
123
|
+
On startup:
|
|
124
|
+
1. Announce yourself briefly (icon, name, role)
|
|
125
|
+
2. Check tasks/pending/ and tasks/needs-changes/ for tasks assigned to you
|
|
126
|
+
3. If you find assigned tasks, IMMEDIATELY begin working on them (no confirmation needed)
|
|
127
|
+
4. If no tasks found, announce you're idle and ready for work
|
|
128
|
+
|
|
129
|
+
You are autonomous - when assigned work exists, start it without asking permission.
|
|
130
|
+
"
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
# Launch Claude Code with the personality
|
|
134
|
+
# --dangerously-skip-permissions avoids repeated prompts when starting agents
|
|
135
|
+
# This is documented in docs/security.md
|
|
136
|
+
if [[ "$resolved" == "hub" ]]; then
|
|
137
|
+
claude --dangerously-skip-permissions --system-prompt "$system_prompt" "begin"
|
|
138
|
+
else
|
|
139
|
+
claude --dangerously-skip-permissions --system-prompt "$system_prompt" "startup"
|
|
140
|
+
fi
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
cmd_status() {
|
|
144
|
+
require_forge_config "$FORGE_ROOT"
|
|
145
|
+
|
|
146
|
+
local state_file="$FORGE_ROOT/$CONTEXT_DIR/forge-state.yaml"
|
|
147
|
+
|
|
148
|
+
echo ""
|
|
149
|
+
log_header "🔥 Forge Status"
|
|
150
|
+
|
|
151
|
+
if [[ -f "$state_file" ]]; then
|
|
152
|
+
cat "$state_file"
|
|
153
|
+
else
|
|
154
|
+
echo "No active forge state."
|
|
155
|
+
echo ""
|
|
156
|
+
echo "Start the forge with: forge"
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
160
|
+
echo ""
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
cmd_test() {
|
|
164
|
+
# Alias for doctor
|
|
165
|
+
cmd_doctor
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
cmd_doctor() {
|
|
169
|
+
echo ""
|
|
170
|
+
log_header "🩺 Forge Doctor"
|
|
171
|
+
echo ""
|
|
172
|
+
|
|
173
|
+
local issues=0
|
|
174
|
+
|
|
175
|
+
# --- Dependencies ---
|
|
176
|
+
echo "Dependencies"
|
|
177
|
+
echo "────────────────────────────────────────────────"
|
|
178
|
+
|
|
179
|
+
if claude --version &> /dev/null; then
|
|
180
|
+
local claude_ver
|
|
181
|
+
claude_ver=$(claude --version 2>&1 | head -1)
|
|
182
|
+
log_success "Claude Code: $claude_ver"
|
|
183
|
+
else
|
|
184
|
+
log_error "Claude Code: not found"
|
|
185
|
+
echo " Install from: https://claude.ai/download"
|
|
186
|
+
issues=$((issues + 1))
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
if command -v node &> /dev/null; then
|
|
190
|
+
local node_ver
|
|
191
|
+
node_ver=$(node --version 2>&1)
|
|
192
|
+
log_success "Node.js: $node_ver"
|
|
193
|
+
# Warn if below Node 18
|
|
194
|
+
local node_major
|
|
195
|
+
node_major=$(node --version | sed 's/v\([0-9]*\).*/\1/')
|
|
196
|
+
if [[ "$node_major" -lt 18 ]]; then
|
|
197
|
+
log_warn "Node.js $node_ver is below recommended v18+"
|
|
198
|
+
fi
|
|
199
|
+
else
|
|
200
|
+
log_error "Node.js: not found"
|
|
201
|
+
issues=$((issues + 1))
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
if command -v git &> /dev/null; then
|
|
205
|
+
local git_ver
|
|
206
|
+
git_ver=$(git --version 2>&1)
|
|
207
|
+
log_success "Git: $git_ver"
|
|
208
|
+
else
|
|
209
|
+
log_error "Git: not found"
|
|
210
|
+
issues=$((issues + 1))
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
if command -v sqlite3 &> /dev/null; then
|
|
214
|
+
local sq_ver
|
|
215
|
+
sq_ver=$(sqlite3 --version 2>&1 | head -1)
|
|
216
|
+
log_success "SQLite3: $sq_ver"
|
|
217
|
+
else
|
|
218
|
+
log_warn "SQLite3: not found (daemon metrics disabled)"
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
# --- Forge Config ---
|
|
222
|
+
echo ""
|
|
223
|
+
echo "Configuration"
|
|
224
|
+
echo "────────────────────────────────────────────────"
|
|
225
|
+
|
|
226
|
+
local config_file="$FORGE_ROOT/.forge/config.json"
|
|
227
|
+
local local_config_file="$FORGE_ROOT/.forge/config.local.json"
|
|
228
|
+
|
|
229
|
+
if [[ -f "$config_file" ]]; then
|
|
230
|
+
log_success "config.json: found"
|
|
231
|
+
local platform terminal
|
|
232
|
+
platform=$(json_get_string "$config_file" "platform" 2>/dev/null || echo "unknown")
|
|
233
|
+
terminal=$(json_get_string "$config_file" "terminal_type" 2>/dev/null || echo "unknown")
|
|
234
|
+
echo " Platform: $platform | Terminal: $terminal"
|
|
235
|
+
else
|
|
236
|
+
log_error "config.json: not found — run 'forge init'"
|
|
237
|
+
issues=$((issues + 1))
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
if [[ -f "$local_config_file" ]]; then
|
|
241
|
+
log_success "config.local.json: found (local overrides active)"
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
# --- agents.json sync check ---
|
|
245
|
+
local agents_file="$FORGE_ROOT/config/agents.json"
|
|
246
|
+
if [[ -f "$agents_file" ]]; then
|
|
247
|
+
log_success "agents.json: found"
|
|
248
|
+
local agent_count
|
|
249
|
+
agent_count=$(node -e "const a=require('$agents_file'); console.log(Object.keys(a.agents||{}).length)" 2>/dev/null || echo "?")
|
|
250
|
+
echo " Agents defined: $agent_count"
|
|
251
|
+
else
|
|
252
|
+
log_error "agents.json: not found"
|
|
253
|
+
issues=$((issues + 1))
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
# --- Alias collision check ---
|
|
257
|
+
if [[ -f "$agents_file" ]]; then
|
|
258
|
+
local collisions
|
|
259
|
+
collisions=$(node -e "
|
|
260
|
+
const a = require('$agents_file').agents || {};
|
|
261
|
+
const seen = {};
|
|
262
|
+
const dupes = [];
|
|
263
|
+
for (const [name, info] of Object.entries(a)) {
|
|
264
|
+
const aliases = [name, ...(info.aliases || [])];
|
|
265
|
+
for (const alias of aliases) {
|
|
266
|
+
if (seen[alias] && seen[alias] !== name) {
|
|
267
|
+
dupes.push(alias + ' (' + seen[alias] + ' vs ' + name + ')');
|
|
268
|
+
}
|
|
269
|
+
seen[alias] = name;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (dupes.length) console.log('COLLISION: ' + dupes.join(', '));
|
|
273
|
+
" 2>/dev/null || true)
|
|
274
|
+
if [[ -n "$collisions" ]]; then
|
|
275
|
+
log_error "Alias collision detected: $collisions"
|
|
276
|
+
issues=$((issues + 1))
|
|
277
|
+
else
|
|
278
|
+
log_success "No alias collisions"
|
|
279
|
+
fi
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
# --- Task directories ---
|
|
283
|
+
echo ""
|
|
284
|
+
echo "Task System"
|
|
285
|
+
echo "────────────────────────────────────────────────"
|
|
286
|
+
|
|
287
|
+
local tasks_dir="$FORGE_ROOT/tasks"
|
|
288
|
+
local required_dirs=("pending" "in-progress" "completed" "review" "approved" "merged" "needs-changes" "attention" "bugs")
|
|
289
|
+
for dir in "${required_dirs[@]}"; do
|
|
290
|
+
if [[ -d "$tasks_dir/$dir" ]]; then
|
|
291
|
+
local count
|
|
292
|
+
count=$(ls "$tasks_dir/$dir/"*.md 2>/dev/null | wc -l | tr -d ' ')
|
|
293
|
+
log_success "$dir/: $count task(s)"
|
|
294
|
+
else
|
|
295
|
+
log_warn "$dir/: missing (will be created by daemon)"
|
|
296
|
+
fi
|
|
297
|
+
done
|
|
298
|
+
|
|
299
|
+
# --- Daemon ---
|
|
300
|
+
echo ""
|
|
301
|
+
echo "Daemon"
|
|
302
|
+
echo "────────────────────────────────────────────────"
|
|
303
|
+
|
|
304
|
+
local pid_file="$FORGE_ROOT/.forge/daemon.pid"
|
|
305
|
+
if [[ -f "$pid_file" ]]; then
|
|
306
|
+
local pid
|
|
307
|
+
pid=$(cat "$pid_file" 2>/dev/null)
|
|
308
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
309
|
+
log_success "Daemon running (PID $pid)"
|
|
310
|
+
else
|
|
311
|
+
log_warn "Daemon PID file exists but process is dead — run 'forge daemon start'"
|
|
312
|
+
fi
|
|
313
|
+
else
|
|
314
|
+
log_warn "Daemon not running — run 'forge daemon start' for automated task routing"
|
|
315
|
+
fi
|
|
316
|
+
|
|
317
|
+
# --- Claude Code test ---
|
|
318
|
+
echo ""
|
|
319
|
+
echo "Claude Code Integration"
|
|
320
|
+
echo "────────────────────────────────────────────────"
|
|
321
|
+
|
|
322
|
+
local test_output
|
|
323
|
+
test_output=$(claude --system-prompt "You are a test. Reply only: FORGE_TEST_OK" --print "test" 2>&1 | head -1)
|
|
324
|
+
if [[ "$test_output" == *"FORGE_TEST_OK"* || "$test_output" == *"test"* ]]; then
|
|
325
|
+
log_success "Personality injection: working"
|
|
326
|
+
else
|
|
327
|
+
log_warn "Personality injection: response unexpected"
|
|
328
|
+
echo " Output: $test_output"
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
# --- Summary ---
|
|
332
|
+
echo ""
|
|
333
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
334
|
+
if [[ $issues -eq 0 ]]; then
|
|
335
|
+
log_success "🔥 Forge looks healthy"
|
|
336
|
+
else
|
|
337
|
+
log_error "Found $issues issue(s) — resolve the errors above before starting"
|
|
338
|
+
fi
|
|
339
|
+
echo ""
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
cmd_spawn() {
|
|
343
|
+
local agent="${1:-}"
|
|
344
|
+
|
|
345
|
+
if [[ -z "$agent" ]]; then
|
|
346
|
+
log_error "No agent specified."
|
|
347
|
+
echo "Usage: forge spawn <agent>"
|
|
348
|
+
echo ""
|
|
349
|
+
show_available_agents
|
|
350
|
+
exit $EXIT_INVALID_ARGUMENT
|
|
351
|
+
fi
|
|
352
|
+
|
|
353
|
+
# Validate agent before passing to spawn script (SECURITY: whitelist check)
|
|
354
|
+
if ! is_valid_agent "$agent"; then
|
|
355
|
+
log_error "Unknown agent: $agent"
|
|
356
|
+
echo ""
|
|
357
|
+
show_available_agents
|
|
358
|
+
exit $EXIT_INVALID_ARGUMENT
|
|
359
|
+
fi
|
|
360
|
+
|
|
361
|
+
"$SCRIPT_DIR/forge-spawn.sh" "$agent"
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
cmd_daemon() {
|
|
365
|
+
local action="${1:-status}"
|
|
366
|
+
|
|
367
|
+
case "$action" in
|
|
368
|
+
"start")
|
|
369
|
+
echo "Starting forge daemon..."
|
|
370
|
+
"$SCRIPT_DIR/forge-daemon.sh" start
|
|
371
|
+
;;
|
|
372
|
+
"stop")
|
|
373
|
+
echo "Stopping forge daemon..."
|
|
374
|
+
"$SCRIPT_DIR/forge-daemon.sh" stop
|
|
375
|
+
;;
|
|
376
|
+
"status")
|
|
377
|
+
"$SCRIPT_DIR/forge-daemon.sh" status
|
|
378
|
+
;;
|
|
379
|
+
"notifications"|"notify")
|
|
380
|
+
shift
|
|
381
|
+
"$SCRIPT_DIR/forge-daemon.sh" notifications "$@"
|
|
382
|
+
;;
|
|
383
|
+
"clear")
|
|
384
|
+
"$SCRIPT_DIR/forge-daemon.sh" clear
|
|
385
|
+
;;
|
|
386
|
+
*)
|
|
387
|
+
echo "Usage: forge daemon [start|stop|status|notifications|clear]"
|
|
388
|
+
;;
|
|
389
|
+
esac
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
cmd_config() {
|
|
393
|
+
local setting="${1:-}"
|
|
394
|
+
local value="${2:-}"
|
|
395
|
+
local config_file="$FORGE_ROOT/.forge/config.json"
|
|
396
|
+
|
|
397
|
+
if [[ ! -f "$config_file" ]]; then
|
|
398
|
+
log_error "Forge not initialized. Run 'forge init' first."
|
|
399
|
+
exit $EXIT_CONFIG_ERROR
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
case "$setting" in
|
|
403
|
+
"worker-loop"|"loop")
|
|
404
|
+
case "$value" in
|
|
405
|
+
"on"|"true"|"1"|"enable"|"enabled")
|
|
406
|
+
# Enable worker loop
|
|
407
|
+
json_write_bool "$config_file" "worker_loop_enabled" true
|
|
408
|
+
log_success "Worker Loop enabled"
|
|
409
|
+
echo "Workers will now keep running to check for new tasks."
|
|
410
|
+
;;
|
|
411
|
+
"off"|"false"|"0"|"disable"|"disabled")
|
|
412
|
+
# Disable worker loop
|
|
413
|
+
json_write_bool "$config_file" "worker_loop_enabled" false
|
|
414
|
+
log_success "Worker Loop disabled"
|
|
415
|
+
echo "Workers will exit after completing their tasks."
|
|
416
|
+
;;
|
|
417
|
+
""|"status")
|
|
418
|
+
# Show current status
|
|
419
|
+
local current
|
|
420
|
+
current=$(json_read "$config_file" "worker_loop_enabled" "false")
|
|
421
|
+
if [[ "$current" == "true" ]]; then
|
|
422
|
+
echo "Worker Loop: enabled"
|
|
423
|
+
else
|
|
424
|
+
echo "Worker Loop: disabled"
|
|
425
|
+
fi
|
|
426
|
+
;;
|
|
427
|
+
*)
|
|
428
|
+
echo "Usage: forge config worker-loop [on|off|status]"
|
|
429
|
+
;;
|
|
430
|
+
esac
|
|
431
|
+
;;
|
|
432
|
+
"")
|
|
433
|
+
# Show all config
|
|
434
|
+
echo ""
|
|
435
|
+
log_header "Forge Configuration"
|
|
436
|
+
echo ""
|
|
437
|
+
json_pretty "$config_file"
|
|
438
|
+
echo ""
|
|
439
|
+
;;
|
|
440
|
+
*)
|
|
441
|
+
log_error "Unknown setting: $setting"
|
|
442
|
+
echo ""
|
|
443
|
+
echo "Available settings:"
|
|
444
|
+
echo " worker-loop Toggle persistent worker mode (on/off)"
|
|
445
|
+
echo ""
|
|
446
|
+
echo "Usage: forge config <setting> [value]"
|
|
447
|
+
;;
|
|
448
|
+
esac
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
cmd_help() {
|
|
452
|
+
echo ""
|
|
453
|
+
log_header "🔥 Vibe Forge"
|
|
454
|
+
echo ""
|
|
455
|
+
echo "Usage: forge [command] [options]"
|
|
456
|
+
echo ""
|
|
457
|
+
echo "Commands:"
|
|
458
|
+
echo " (none) Start the Planning Hub (main session)"
|
|
459
|
+
echo " init Initialize Vibe Forge for this project"
|
|
460
|
+
echo " start <agent> Start a specific worker agent (in current terminal)"
|
|
461
|
+
echo " spawn <agent> Spawn agent in new terminal window/tab"
|
|
462
|
+
echo " status Show current forge status"
|
|
463
|
+
echo " doctor Full environment diagnostics (alias: test)"
|
|
464
|
+
echo " daemon <action> Manage background daemon (start|stop|status|notifications|clear)"
|
|
465
|
+
echo " config <setting> View or change configuration settings"
|
|
466
|
+
echo " help Show this help message"
|
|
467
|
+
echo ""
|
|
468
|
+
show_available_agents
|
|
469
|
+
echo ""
|
|
470
|
+
echo "Configuration:"
|
|
471
|
+
echo " forge config Show all settings"
|
|
472
|
+
echo " forge config worker-loop on Enable persistent worker mode"
|
|
473
|
+
echo " forge config worker-loop off Disable persistent worker mode"
|
|
474
|
+
echo ""
|
|
475
|
+
echo "Examples:"
|
|
476
|
+
echo " forge Start Planning Hub"
|
|
477
|
+
echo " forge init Initialize for new project"
|
|
478
|
+
echo " forge start anvil Start Anvil (frontend) agent"
|
|
479
|
+
echo " forge spawn fe Spawn frontend agent in new terminal"
|
|
480
|
+
echo " forge status Check current status"
|
|
481
|
+
echo ""
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
# =============================================================================
|
|
485
|
+
# Main
|
|
486
|
+
# =============================================================================
|
|
487
|
+
|
|
488
|
+
main() {
|
|
489
|
+
local command="${1:-}"
|
|
490
|
+
|
|
491
|
+
case "$command" in
|
|
492
|
+
"init")
|
|
493
|
+
shift
|
|
494
|
+
cmd_init "$@"
|
|
495
|
+
;;
|
|
496
|
+
"start")
|
|
497
|
+
shift
|
|
498
|
+
cmd_start "$@"
|
|
499
|
+
;;
|
|
500
|
+
"spawn")
|
|
501
|
+
shift
|
|
502
|
+
cmd_spawn "$@"
|
|
503
|
+
;;
|
|
504
|
+
"status")
|
|
505
|
+
cmd_status
|
|
506
|
+
;;
|
|
507
|
+
"test"|"doctor")
|
|
508
|
+
cmd_doctor
|
|
509
|
+
;;
|
|
510
|
+
"daemon")
|
|
511
|
+
shift
|
|
512
|
+
cmd_daemon "$@"
|
|
513
|
+
;;
|
|
514
|
+
"config")
|
|
515
|
+
shift
|
|
516
|
+
cmd_config "$@"
|
|
517
|
+
;;
|
|
518
|
+
"help"|"--help"|"-h")
|
|
519
|
+
cmd_help
|
|
520
|
+
;;
|
|
521
|
+
"")
|
|
522
|
+
# Default: start Planning Hub
|
|
523
|
+
cmd_start "hub"
|
|
524
|
+
;;
|
|
525
|
+
*)
|
|
526
|
+
log_error "Unknown command: $command"
|
|
527
|
+
echo "Run 'forge help' for usage."
|
|
528
|
+
exit $EXIT_INVALID_ARGUMENT
|
|
529
|
+
;;
|
|
530
|
+
esac
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
main "$@"
|
|
@@ -0,0 +1,177 @@
|
|
|
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
|
+
}
|
|
158
|
+
|
|
159
|
+
# get_agent_icon AGENT
|
|
160
|
+
# Returns the icon/emoji for an agent
|
|
161
|
+
get_agent_icon() {
|
|
162
|
+
local agent="$1"
|
|
163
|
+
local canonical
|
|
164
|
+
canonical=$(resolve_agent "$agent") || return 1
|
|
165
|
+
|
|
166
|
+
echo "${AGENT_ICONS[$canonical]:-}"
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# get_agent_tab_color AGENT
|
|
170
|
+
# Returns the Windows Terminal tab color for an agent (hex format)
|
|
171
|
+
get_agent_tab_color() {
|
|
172
|
+
local agent="$1"
|
|
173
|
+
local canonical
|
|
174
|
+
canonical=$(resolve_agent "$agent") || return 1
|
|
175
|
+
|
|
176
|
+
echo "${AGENT_TAB_COLORS[$canonical]:-}"
|
|
177
|
+
}
|