Skip to content

Commit ec45086

Browse files
committed
feat: add opencode target support for install and integration
1 parent cd78b08 commit ec45086

11 files changed

Lines changed: 2512 additions & 1601 deletions

src/apm_cli/cli.py

Lines changed: 307 additions & 129 deletions
Large diffs are not rendered by default.

src/apm_cli/compilation/agents_compiler.py

Lines changed: 310 additions & 245 deletions
Large diffs are not rendered by default.
Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
"""Target detection for auto-selecting compilation and integration targets.
22
33
This module implements the auto-detection pattern for determining which agent
4-
targets (VSCode/Copilot vs Claude) should be used based on existing project
5-
structure and configuration.
4+
targets (VSCode/Copilot, Claude, OpenCode) should be used based on existing
5+
project structure and configuration.
66
77
Detection priority (highest to lowest):
88
1. Explicit --target flag (always wins)
99
2. apm.yml target setting (top-level field)
1010
3. Auto-detect from existing folders:
11-
- .github/ exists AND .claude/ doesn't → vscode
12-
- .claude/ exists AND .github/ doesn't → claude
13-
- Both exist → all
14-
- Neither exists → minimal (AGENTS.md only, no folder integration)
11+
- .github/ exists only → vscode
12+
- .claude/ exists only → claude
13+
- .opencode/ exists only → opencode
14+
- Multiple integration folders exist → all
15+
- None exist → minimal (AGENTS.md only, no folder integration)
1516
"""
1617

1718
from pathlib import Path
1819
from typing import Literal, Optional, Tuple
1920

2021
# Valid target values
21-
TargetType = Literal["vscode", "claude", "all", "minimal"]
22+
TargetType = Literal["vscode", "claude", "opencode", "all", "minimal"]
2223

2324

2425
def detect_target(
@@ -27,12 +28,12 @@ def detect_target(
2728
config_target: Optional[str] = None,
2829
) -> Tuple[TargetType, str]:
2930
"""Detect the appropriate target for compilation and integration.
30-
31+
3132
Args:
3233
project_root: Root directory of the project
3334
explicit_target: Explicitly provided --target flag value
3435
config_target: Target from apm.yml top-level 'target' field
35-
36+
3637
Returns:
3738
Tuple of (target, reason) where:
3839
- target: The detected target type
@@ -44,39 +45,60 @@ def detect_target(
4445
return "vscode", "explicit --target flag"
4546
elif explicit_target == "claude":
4647
return "claude", "explicit --target flag"
48+
elif explicit_target == "opencode":
49+
return "opencode", "explicit --target flag"
4750
elif explicit_target == "all":
4851
return "all", "explicit --target flag"
49-
52+
5053
# Priority 2: apm.yml target setting
5154
if config_target:
5255
if config_target in ("vscode", "agents"):
5356
return "vscode", "apm.yml target"
5457
elif config_target == "claude":
5558
return "claude", "apm.yml target"
59+
elif config_target == "opencode":
60+
return "opencode", "apm.yml target"
5661
elif config_target == "all":
5762
return "all", "apm.yml target"
58-
63+
5964
# Priority 3: Auto-detect from existing folders
6065
github_exists = (project_root / ".github").exists()
6166
claude_exists = (project_root / ".claude").exists()
62-
63-
if github_exists and not claude_exists:
67+
opencode_exists = (project_root / ".opencode").exists()
68+
69+
enabled_targets = []
70+
if github_exists:
71+
enabled_targets.append("vscode")
72+
if claude_exists:
73+
enabled_targets.append("claude")
74+
if opencode_exists:
75+
enabled_targets.append("opencode")
76+
77+
if enabled_targets == ["vscode"]:
6478
return "vscode", "detected .github/ folder"
65-
elif claude_exists and not github_exists:
79+
elif enabled_targets == ["claude"]:
6680
return "claude", "detected .claude/ folder"
67-
elif github_exists and claude_exists:
68-
return "all", "detected both .github/ and .claude/ folders"
81+
elif enabled_targets == ["opencode"]:
82+
return "opencode", "detected .opencode/ folder"
83+
elif len(enabled_targets) > 1:
84+
labels = {
85+
"vscode": ".github/",
86+
"claude": ".claude/",
87+
"opencode": ".opencode/",
88+
}
89+
joined = ", ".join(labels[target] for target in enabled_targets)
90+
return "all", f"detected multiple integration folders ({joined})"
6991
else:
7092
# Neither folder exists - minimal output
71-
return "minimal", "no .github/ or .claude/ folder found"
93+
return "minimal", "no .github/, .claude/, or .opencode/ folder found"
7294

7395

7496
def should_integrate_vscode(target: TargetType) -> bool:
7597
"""Check if VSCode integration should be performed.
76-
98+
7799
Args:
78100
target: The detected or configured target
79-
101+
80102
Returns:
81103
bool: True if VSCode integration (prompts, agents) should run
82104
"""
@@ -85,37 +107,49 @@ def should_integrate_vscode(target: TargetType) -> bool:
85107

86108
def should_integrate_claude(target: TargetType) -> bool:
87109
"""Check if Claude integration should be performed.
88-
110+
89111
Args:
90112
target: The detected or configured target
91-
113+
92114
Returns:
93115
bool: True if Claude integration (commands, skills) should run
94116
"""
95117
return target in ("claude", "all")
96118

97119

120+
def should_integrate_opencode(target: TargetType) -> bool:
121+
"""Check if OpenCode integration should be performed.
122+
123+
Args:
124+
target: The detected or configured target
125+
126+
Returns:
127+
bool: True if OpenCode integration should run
128+
"""
129+
return target in ("opencode", "all")
130+
131+
98132
def should_compile_agents_md(target: TargetType) -> bool:
99133
"""Check if AGENTS.md should be compiled.
100-
101-
AGENTS.md is generated for vscode, all, and minimal targets.
134+
135+
AGENTS.md is generated for vscode, opencode, all, and minimal targets.
102136
It's the universal format that works everywhere.
103-
137+
104138
Args:
105139
target: The detected or configured target
106-
140+
107141
Returns:
108142
bool: True if AGENTS.md should be generated
109143
"""
110-
return target in ("vscode", "all", "minimal")
144+
return target in ("vscode", "opencode", "all", "minimal")
111145

112146

113147
def should_compile_claude_md(target: TargetType) -> bool:
114148
"""Check if CLAUDE.md should be compiled.
115-
149+
116150
Args:
117151
target: The detected or configured target
118-
152+
119153
Returns:
120154
bool: True if CLAUDE.md should be generated
121155
"""
@@ -124,17 +158,18 @@ def should_compile_claude_md(target: TargetType) -> bool:
124158

125159
def get_target_description(target: TargetType) -> str:
126160
"""Get a human-readable description of what will be generated for a target.
127-
161+
128162
Args:
129163
target: The target type
130-
164+
131165
Returns:
132166
str: Description of output files
133167
"""
134168
descriptions = {
135169
"vscode": "AGENTS.md + .github/prompts/ + .github/agents/",
136170
"claude": "CLAUDE.md + .claude/commands/ + .claude/agents/ + .claude/skills/",
137-
"all": "AGENTS.md + CLAUDE.md + .github/ + .claude/",
171+
"opencode": "AGENTS.md + .opencode/commands/ + .opencode/skills/",
172+
"all": "AGENTS.md + CLAUDE.md + .github/ + .claude/ + .opencode/",
138173
"minimal": "AGENTS.md only (create .github/ or .claude/ for full integration)",
139174
}
140175
return descriptions.get(target, "unknown target")

0 commit comments

Comments
 (0)