Skip to content

Commit 35ec086

Browse files
committed
Add Factory AI droids support (PR buildermethods#266)
This PR adds Factory AI integration to enable automatic generation of droids and slash commands from existing agent definitions. Key changes: - Added factory_ai_droids configuration option (default: true) - Created factory-ai-droid-template.md for droid generation - Implemented tool mapping from agent-os to Factory AI equivalents - Added functions: map_tools_to_factory(), create_factory_droid(), create_factory_command(), install_factory_droids() - Integrated Factory AI installation into project-install.sh and project-update.sh scripts - Changed use_claude_code_subagents default to false (Factory AI uses its own delegation model) When enabled, automatically generates: - 8 droids from agent definitions in .factory/droids/ - 6 commands from multi-agent configurations in .factory/commands/ Code review: ✅ PASSED - Well-structured and follows existing conventions - Proper error handling and cleanup - Backward compatible - Reuses existing compilation infrastructure
1 parent a543311 commit 35ec086

File tree

5 files changed

+342
-5
lines changed

5 files changed

+342
-5
lines changed

config.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ agent_os_commands: false
3131
#
3232
# Override this default when running project-install.sh by using the flag --use-claude-code-subagents true/false
3333
# ================================================
34-
use_claude_code_subagents: true
34+
use_claude_code_subagents: false
3535

3636

3737
# ================================================
@@ -45,6 +45,15 @@ use_claude_code_subagents: true
4545
standards_as_claude_code_skills: false
4646

4747

48+
# ================================================
49+
# Do you use Factory AI?
50+
# Set to true to install droids in your project's .factory/droids/ folder
51+
#
52+
# Override this default when running project-install.sh by using the flag --factory-ai-droids true/false
53+
# ================================================
54+
factory_ai_droids: true
55+
56+
4857
# ================================================
4958
# PROFILE
5059
#
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
name: {{agent_name}}
3+
description: {{agent_description}}
4+
model: inherit
5+
tools: {{agent_tools}}
6+
---
7+
8+
{{agent_content}}

scripts/common-functions.sh

Lines changed: 271 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,7 @@ load_base_config() {
11861186
BASE_USE_CLAUDE_CODE_SUBAGENTS=$(get_yaml_value "$BASE_DIR/config.yml" "use_claude_code_subagents" "true")
11871187
BASE_AGENT_OS_COMMANDS=$(get_yaml_value "$BASE_DIR/config.yml" "agent_os_commands" "false")
11881188
BASE_STANDARDS_AS_CLAUDE_CODE_SKILLS=$(get_yaml_value "$BASE_DIR/config.yml" "standards_as_claude_code_skills" "true")
1189+
BASE_FACTORY_AI_DROIDS=$(get_yaml_value "$BASE_DIR/config.yml" "factory_ai_droids" "false")
11891190

11901191
# Check for old config flags to set variables for validation
11911192
MULTI_AGENT_MODE=$(get_yaml_value "$BASE_DIR/config.yml" "multi_agent_mode" "")
@@ -1201,6 +1202,7 @@ load_project_config() {
12011202
PROJECT_USE_CLAUDE_CODE_SUBAGENTS=$(get_project_config "$PROJECT_DIR" "use_claude_code_subagents")
12021203
PROJECT_AGENT_OS_COMMANDS=$(get_project_config "$PROJECT_DIR" "agent_os_commands")
12031204
PROJECT_STANDARDS_AS_CLAUDE_CODE_SKILLS=$(get_project_config "$PROJECT_DIR" "standards_as_claude_code_skills")
1205+
PROJECT_FACTORY_AI_DROIDS=$(get_project_config "$PROJECT_DIR" "factory_ai_droids")
12041206

12051207
# Check for old config flags to set variables for validation
12061208
MULTI_AGENT_MODE=$(get_project_config "$PROJECT_DIR" "multi_agent_mode")
@@ -1256,6 +1258,7 @@ write_project_config() {
12561258
local use_claude_code_subagents=$4
12571259
local agent_os_commands=$5
12581260
local standards_as_claude_code_skills=$6
1261+
local factory_ai_droids=$7
12591262
local dest="$PROJECT_DIR/agent-os/config.yml"
12601263

12611264
local config_content="version: $version
@@ -1270,7 +1273,8 @@ profile: $profile
12701273
claude_code_commands: $claude_code_commands
12711274
use_claude_code_subagents: $use_claude_code_subagents
12721275
agent_os_commands: $agent_os_commands
1273-
standards_as_claude_code_skills: $standards_as_claude_code_skills"
1276+
standards_as_claude_code_skills: $standards_as_claude_code_skills
1277+
factory_ai_droids: $factory_ai_droids"
12741278

12751279
local result=$(write_file "$config_content" "$dest")
12761280
if [[ "$DRY_RUN" == "true" ]]; then
@@ -1466,3 +1470,269 @@ install_improve_skills_command() {
14661470
fi
14671471
fi
14681472
}
1473+
1474+
# -----------------------------------------------------------------------------
1475+
# Factory AI Droid Functions
1476+
# -----------------------------------------------------------------------------
1477+
1478+
# Map agent-os tool names to Factory AI tool names
1479+
map_tools_to_factory() {
1480+
local tools=$1
1481+
# Remove the brackets and split by comma
1482+
local tools_array=$(echo "$tools" | sed 's/\[//g' | sed 's/\]//g' | sed 's/, */ /g')
1483+
local factory_tools=()
1484+
1485+
for tool in $tools_array; do
1486+
case "$tool" in
1487+
"Write")
1488+
# Map Write to standard Factory edit tools + exploration tools
1489+
factory_tools+=("Read" "LS" "Grep" "Glob" "Create" "Edit" "MultiEdit")
1490+
;;
1491+
"Read")
1492+
# Map Read to Factory read-only tools
1493+
factory_tools+=("Read" "LS" "Grep" "Glob")
1494+
;;
1495+
"Bash")
1496+
# Map Bash to Factory Execute
1497+
factory_tools+=("Execute")
1498+
;;
1499+
"WebFetch")
1500+
# Map WebFetch to Factory web tools (but exclude for most droids)
1501+
# Don't add by default - let droids that need it specify explicitly
1502+
;;
1503+
"Playwright")
1504+
# Don't include Playwright tools - not standard Factory tools
1505+
# Browsers should be used via WebSearch/FetchUrl instead
1506+
;;
1507+
*)
1508+
# Pass through any other tool names as-is
1509+
factory_tools+=("$tool")
1510+
;;
1511+
esac
1512+
done
1513+
1514+
# Remove duplicates and format as JSON array
1515+
local unique_tools=($(echo "${factory_tools[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
1516+
local json_tools="["
1517+
local first=true
1518+
for tool in "${unique_tools[@]}"; do
1519+
if [[ "$first" == "true" ]]; then
1520+
json_tools+="\"$tool\""
1521+
first=false
1522+
else
1523+
json_tools+=", \"$tool\""
1524+
fi
1525+
done
1526+
json_tools+="]"
1527+
echo "$json_tools"
1528+
}
1529+
1530+
# Create a Factory AI droid from an agent file
1531+
# Args: $1=agent file path (relative to profile, e.g., "agents/implementer.md")
1532+
# $2=dest base directory (project directory)
1533+
# $3=base directory (~/agent-os)
1534+
# $4=profile name
1535+
create_factory_droid() {
1536+
local agent_file=$1
1537+
local dest_base=$2
1538+
local base_dir=$3
1539+
local profile=$4
1540+
1541+
# Get the full path to the agent file
1542+
local source_file=$(get_profile_file "$profile" "$agent_file" "$base_dir")
1543+
if [[ ! -f "$source_file" ]]; then
1544+
print_error "Agent file not found: $source_file"
1545+
return 1
1546+
fi
1547+
1548+
# Extract agent name from filename
1549+
local agent_name=$(basename "$agent_file" .md)
1550+
1551+
# Read the agent file and extract frontmatter
1552+
local agent_content=$(cat "$source_file")
1553+
local description=""
1554+
local tools=""
1555+
local model="inherit"
1556+
1557+
# Extract frontmatter using awk - extract everything between first and second ---
1558+
description=$(awk 'BEGIN{count=0} /^---$/{count++; next} count==1 && /^description:/{sub(/^description:[[:space:]]*/, ""); print; exit}' "$source_file")
1559+
tools=$(awk 'BEGIN{count=0} /^---$/{count++; next} count==1 && /^tools:/{sub(/^tools:[[:space:]]*/, ""); print; exit}' "$source_file")
1560+
1561+
# Extract content after frontmatter (everything after second ---)
1562+
local droid_content=$(echo "$agent_content" | awk '/^---$/ {count++; next} count >= 2 {print}')
1563+
1564+
# Compile the droid content (process workflows, standards, conditionals)
1565+
# First write to a temp file
1566+
local temp_file=$(mktemp)
1567+
echo "$droid_content" > "$temp_file"
1568+
1569+
# Now compile it
1570+
local compiled_content=$(compile_agent "$temp_file" "$temp_file.compiled" "$base_dir" "$profile" "" "")
1571+
compiled_content=$(cat "$temp_file.compiled" 2>/dev/null || echo "$droid_content")
1572+
rm -f "$temp_file" "$temp_file.compiled"
1573+
1574+
# Map tools to Factory AI tool names
1575+
local factory_tools=$(map_tools_to_factory "$tools")
1576+
1577+
# Get the droid template
1578+
local template_file=$(get_profile_file "$profile" "factory-ai-droid-template.md" "$base_dir")
1579+
if [[ ! -f "$template_file" ]]; then
1580+
print_error "Factory AI droid template not found: $template_file"
1581+
return 1
1582+
fi
1583+
1584+
# Read template and replace placeholders
1585+
local droid_file_content=$(cat "$template_file")
1586+
droid_file_content=$(echo "$droid_file_content" | sed "s|{{agent_name}}|$agent_name|g")
1587+
droid_file_content=$(echo "$droid_file_content" | sed "s|{{agent_description}}|$description|g")
1588+
droid_file_content=$(echo "$droid_file_content" | sed "s|{{agent_tools}}|$factory_tools|g")
1589+
1590+
# Replace content placeholder (handle multiline)
1591+
local temp_template=$(mktemp)
1592+
local temp_content=$(mktemp)
1593+
echo "$droid_file_content" > "$temp_template"
1594+
echo "$compiled_content" > "$temp_content"
1595+
1596+
droid_file_content=$(perl -e '
1597+
use strict;
1598+
use warnings;
1599+
1600+
my $content_file = $ARGV[0];
1601+
my $template_file = $ARGV[1];
1602+
1603+
# Read compiled content
1604+
open(my $fh, "<", $content_file) or die $!;
1605+
my $content = do { local $/; <$fh> };
1606+
close($fh);
1607+
chomp $content;
1608+
1609+
# Read template
1610+
open($fh, "<", $template_file) or die $!;
1611+
my $template = do { local $/; <$fh> };
1612+
close($fh);
1613+
1614+
# Replace placeholder
1615+
$template =~ s/\{\{agent_content\}\}/$content/g;
1616+
1617+
print $template;
1618+
' "$temp_content" "$temp_template")
1619+
1620+
rm -f "$temp_template" "$temp_content"
1621+
1622+
# Create droid directory
1623+
local droid_dir="$dest_base/.factory/droids"
1624+
ensure_dir "$droid_dir"
1625+
1626+
# Write droid file
1627+
local droid_file="$droid_dir/$agent_name.md"
1628+
if [[ "$DRY_RUN" == "true" ]]; then
1629+
echo "$droid_file"
1630+
else
1631+
echo "$droid_file_content" > "$droid_file"
1632+
print_verbose "Created Factory AI droid: $droid_file"
1633+
fi
1634+
}
1635+
1636+
# Create a Factory AI command that delegates to a droid
1637+
# Args: $1=command file path (relative to profile)
1638+
# $2=dest base directory (project directory)
1639+
# $3=base directory (~/agent-os)
1640+
# $4=profile name
1641+
create_factory_command() {
1642+
local command_file=$1
1643+
local dest_base=$2
1644+
local base_dir=$3
1645+
local profile=$4
1646+
1647+
# Get the full path to the command file
1648+
local source_file=$(get_profile_file "$profile" "$command_file" "$base_dir")
1649+
if [[ ! -f "$source_file" ]]; then
1650+
print_verbose "Command file not found: $source_file"
1651+
return 0
1652+
fi
1653+
1654+
# Extract command name from path
1655+
# e.g., commands/plan-product/multi-agent/plan-product.md -> plan-product
1656+
local command_name=$(echo "$command_file" | cut -d'/' -f2)
1657+
1658+
# Create command directory
1659+
local command_dir="$dest_base/.factory/commands"
1660+
ensure_dir "$command_dir"
1661+
1662+
# Compile the command content (process workflows, standards, conditionals)
1663+
local temp_file=$(mktemp)
1664+
compile_command "$source_file" "$temp_file" "$base_dir" "$profile" ""
1665+
local command_content=$(cat "$temp_file")
1666+
rm -f "$temp_file"
1667+
1668+
# Generate description from command name
1669+
local description=$(echo "$command_name" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1')
1670+
1671+
# Add YAML frontmatter for Factory AI slash command
1672+
local dest_file="$command_dir/$command_name.md"
1673+
if [[ "$DRY_RUN" == "true" ]]; then
1674+
echo "$dest_file"
1675+
else
1676+
cat > "$dest_file" << EOF
1677+
---
1678+
name: $command_name
1679+
description: $description
1680+
---
1681+
1682+
$command_content
1683+
EOF
1684+
print_verbose "Created Factory AI command: $dest_file"
1685+
fi
1686+
}
1687+
1688+
# Install Factory AI droids and commands
1689+
install_factory_droids() {
1690+
# Only install droids if Factory AI is enabled
1691+
if [[ "$EFFECTIVE_FACTORY_AI_DROIDS" != "true" ]]; then
1692+
return 0
1693+
fi
1694+
1695+
if [[ "$DRY_RUN" != "true" ]]; then
1696+
print_status "Installing Factory AI droids and commands..."
1697+
fi
1698+
1699+
local droids_count=0
1700+
local commands_count=0
1701+
1702+
# Install droids from agent files
1703+
while read file; do
1704+
if [[ "$file" == agents/* ]] && [[ "$file" == *.md ]]; then
1705+
# Create droid from this agent file
1706+
create_factory_droid "$file" "$PROJECT_DIR" "$BASE_DIR" "$EFFECTIVE_PROFILE"
1707+
1708+
# Track the droid file for dry run
1709+
local agent_name=$(basename "$file" .md)
1710+
local droid_file="$PROJECT_DIR/.factory/droids/$agent_name.md"
1711+
if [[ "$DRY_RUN" == "true" ]]; then
1712+
INSTALLED_FILES+=("$droid_file")
1713+
fi
1714+
((droids_count++)) || true
1715+
fi
1716+
done < <(get_profile_files "$EFFECTIVE_PROFILE" "$BASE_DIR" "agents")
1717+
1718+
# Install commands (slash commands that delegate to droids)
1719+
while read file; do
1720+
# Use multi-agent commands for Factory AI (they use Task tool delegation)
1721+
if [[ "$file" == commands/*/multi-agent/* ]] || [[ "$file" == commands/orchestrate-tasks/orchestrate-tasks.md ]]; then
1722+
create_factory_command "$file" "$PROJECT_DIR" "$BASE_DIR" "$EFFECTIVE_PROFILE"
1723+
1724+
if [[ "$DRY_RUN" == "true" ]]; then
1725+
local command_name=$(echo "$file" | cut -d'/' -f2)
1726+
INSTALLED_FILES+=("$PROJECT_DIR/.factory/commands/$command_name.md")
1727+
fi
1728+
((commands_count++)) || true
1729+
fi
1730+
done < <(get_profile_files "$EFFECTIVE_PROFILE" "$BASE_DIR" "commands")
1731+
1732+
if [[ "$DRY_RUN" != "true" ]]; then
1733+
if [[ $droids_count -gt 0 ]] || [[ $commands_count -gt 0 ]]; then
1734+
echo "✓ Installed $droids_count Factory AI droids and $commands_count commands"
1735+
echo -e "${YELLOW} 👉 Enable Custom Droids in Factory AI settings, then use slash commands like /plan-product${NC}"
1736+
fi
1737+
fi
1738+
}

0 commit comments

Comments
 (0)