feat(install): add OpenCode target + InternalAllTheThings knowledge base
- install_opencode: deploys 29 personas as agents + 1011 skills to
~/.config/opencode/{agents,skills}/. Uses OpenCode's markdown+YAML
agent format (mode/color/permission) and SKILL.md format.
- Topic filter with sensible defaults (drops marketing/biz ~514 skills).
CLI: --opencode-topics security-offensive,coding-backend,...
- Clone of swisskyrepo/InternalAllTheThings (168 MD, 1.7MB) added to
_shared/ as a reference trove for AD attack paths, ADCS ESC1-15,
Kerberos delegation, NTLM relay/coerce, lateral movement, persistence.
- NEO redteam + VORTEX cloud-ad personas reference the new KB with
MITRE ATT&CK TTP mapping pointers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
340
build.py
340
build.py
@@ -1226,6 +1226,320 @@ def install_antigravity(output_dir: Path):
|
||||
return count
|
||||
|
||||
|
||||
OPENCODE_TOPICS = {
|
||||
"security-offensive",
|
||||
"security-defensive",
|
||||
"security-cloud",
|
||||
"security-specialized",
|
||||
"security-iam",
|
||||
"security-network",
|
||||
"security-general",
|
||||
"ai-llm-dev",
|
||||
"coding-backend",
|
||||
"coding-frontend",
|
||||
"coding-tools",
|
||||
"cloud-infra",
|
||||
"database",
|
||||
"browser-scrape",
|
||||
"ops-sysadmin",
|
||||
"osint-intel",
|
||||
"marketing-content",
|
||||
"business-pm",
|
||||
"uncategorized",
|
||||
}
|
||||
|
||||
# Default set: dev + security + AI + ops. Drops marketing/biz fluff.
|
||||
OPENCODE_DEFAULT_TOPICS = {
|
||||
"security-offensive", "security-defensive", "security-cloud",
|
||||
"security-specialized", "security-iam", "security-network",
|
||||
"security-general",
|
||||
"ai-llm-dev", "coding-backend", "coding-frontend", "coding-tools",
|
||||
"cloud-infra", "database", "browser-scrape", "ops-sysadmin",
|
||||
"osint-intel",
|
||||
}
|
||||
|
||||
|
||||
def _classify_skill_topic(name: str, fm: dict) -> str:
|
||||
"""Map a skill to one of OPENCODE_TOPICS based on frontmatter + name."""
|
||||
CYBER_MAP = {
|
||||
"red-teaming": "security-offensive",
|
||||
"penetration-testing": "security-offensive",
|
||||
"web-application-security": "security-offensive",
|
||||
"api-security": "security-offensive",
|
||||
"mobile-security": "security-offensive",
|
||||
"cryptography": "security-offensive",
|
||||
"threat-hunting": "security-defensive",
|
||||
"threat-intelligence": "security-defensive",
|
||||
"threat-detection": "security-defensive",
|
||||
"digital-forensics": "security-defensive",
|
||||
"incident-response": "security-defensive",
|
||||
"soc-operations": "security-defensive",
|
||||
"security-operations": "security-defensive",
|
||||
"malware-analysis": "security-defensive",
|
||||
"ransomware-defense": "security-defensive",
|
||||
"phishing-defense": "security-defensive",
|
||||
"endpoint-security": "security-defensive",
|
||||
"deception-technology": "security-defensive",
|
||||
"network-security": "security-network",
|
||||
"cloud-security": "security-cloud",
|
||||
"container-security": "security-cloud",
|
||||
"identity-access-management": "security-iam",
|
||||
"zero-trust-architecture": "security-iam",
|
||||
"ot-ics-security": "security-specialized",
|
||||
"vulnerability-management": "security-specialized",
|
||||
"devsecops": "security-specialized",
|
||||
"compliance-governance": "security-specialized",
|
||||
"application-security": "security-specialized",
|
||||
"supply-chain-security": "security-specialized",
|
||||
}
|
||||
sd = fm.get("subdomain")
|
||||
if sd in CYBER_MAP:
|
||||
return CYBER_MAP[sd]
|
||||
if fm.get("domain") == "cybersecurity":
|
||||
return "security-general"
|
||||
|
||||
NAME_PATTERNS = [
|
||||
("coding-frontend", r"^(react|nextjs|next-|angular|vue-|svelte|tailwind|shadcn|vercel|expo|remotion|frontend|ui-ux|accessibility|canvas-|stitch|framer)"),
|
||||
("coding-backend", r"^(python|java-|csharp|dotnet|aspnet|kotlin|swift|rust-|golang|go-|ruby-|php-|nodejs|node-|bash-|cli-|bazel|async-|architecting-|aspire-)"),
|
||||
("coding-tools", r"^(commit|changelog|debug-|refactor|test-driven|tdd|bdd|git-|github-|gitlab-|bats|copilot|codeql|code-review|linting|formatting|add-|adr-|agent-browser|mcp-)"),
|
||||
("ai-llm-dev", r"^(ai-|agentic|claude-|mcp|openai|anthropic|llm|rag-|embedding|fine-tun|prompt|anythingllm|olla|huggingface|elevenlabs|crawl-for-ai|agent-tools|agent-ui|agent-governance|para-memory|knowledge-hub)"),
|
||||
("cloud-infra", r"^(aws|azure|gcp|kubernetes|docker|terraform|cloudflare|vercel|netlify|supabase|firebase|k8s|iac|devops|cicd|ansible|helm|bigquery|airflow|az-)"),
|
||||
("database", r"^(sql-|postgres|mysql|mongodb|redis)"),
|
||||
("browser-scrape", r"^(browser|playwright|puppeteer|firecrawl|stealth|scrape|crawl|use-my-browser)"),
|
||||
("osint-intel", r"^(osint|recon|intel-|foia|seithar|deep-scraper|stealth-browser|social-trust|news-crawler|proudguard|gov-cyber|tavily|session-logs|youtube-transcript)"),
|
||||
("marketing-content", r"^(copywriting|content-|seo-|blog-|article-|marketing-|ad-(creative|campaign)|brand-|banner|churn|billing|gtm-|competitive|backlink|boost|twitter|ai-social|ai-marketing|ai-content|ai-podcast|ai-music|ai-avatar|ai-automation|ai-image|ai-video|impeccable)"),
|
||||
("ops-sysadmin", r"^(healthcheck|sysadmin|dns-networking|network-|nmap-|pcap-|tmux|freshrss|obsidian-|librarian|pdf-|image-ocr|mistral-ocr|analyze|weather|node-connect|clawflow|skill-creator|devops-engineer)"),
|
||||
("business-pm", r"^(ceo-|cfo-|product-manager|marketing-strategist|marketing-psychology|qa-testing|design-md|persona-customer|product-|gtm-|arize|dataverse|power-|microsoft-)"),
|
||||
("security-offensive", r"^(exploiting|pentest-|performing-(web|api|initial|privilege|credential|graphql|soap|lateral|clickjacking|subdomain|open-source|wireless|physical|iot|external|directory|oauth|csrf|web-application|web-cache|http|thick|content-security|active-directory|kerberoasting|second-order|blind-ssrf|jwt-none|initial-access)|testing-(for|api|oauth2|jwt|websocket|websocket-api|cors)|sql-injection|pwnclaw-security)"),
|
||||
("security-defensive", r"^(security-(review|audit|scanner|headers|skill-scanner)|senior-secops|threat-|ctf-|sys-guard|clawsec|agent-intelligence|war-intel|sentinel)"),
|
||||
]
|
||||
for topic, pattern in NAME_PATTERNS:
|
||||
if re.match(pattern, name.lower()):
|
||||
return topic
|
||||
return "uncategorized"
|
||||
|
||||
|
||||
def _parse_skill_frontmatter_simple(skill_md: Path) -> dict:
|
||||
"""Minimal YAML frontmatter parser — just key: value pairs."""
|
||||
try:
|
||||
text = skill_md.read_text(encoding="utf-8", errors="ignore")
|
||||
except Exception:
|
||||
return {}
|
||||
if not text.startswith("---"):
|
||||
return {}
|
||||
end = text.find("\n---", 4)
|
||||
if end < 0:
|
||||
return {}
|
||||
fm = {}
|
||||
for line in text[4:end].splitlines():
|
||||
m = re.match(r"^([a-z_]+):\s*(.+?)\s*$", line)
|
||||
if m:
|
||||
fm[m.group(1)] = m.group(2).strip().strip('"\'')
|
||||
return fm
|
||||
|
||||
|
||||
def install_opencode(
|
||||
output_dir: Path,
|
||||
shared_dir: Path | None = None,
|
||||
topics: set[str] | None = None,
|
||||
):
|
||||
"""Install personas to OpenCode as agents + skills.
|
||||
|
||||
OpenCode agent format (per https://opencode.ai/docs/agents/):
|
||||
- Location: ~/.config/opencode/agents/<codename>.md
|
||||
- YAML frontmatter: description, mode (primary|subagent), model,
|
||||
temperature, color, permission (edit/bash/webfetch/task).
|
||||
|
||||
OpenCode skill format (per https://opencode.ai/docs/skills/):
|
||||
- Location: ~/.config/opencode/skills/<name>/SKILL.md
|
||||
- YAML frontmatter: name, description (required).
|
||||
- OpenCode ALSO reads ~/.claude/skills/ natively.
|
||||
|
||||
Args:
|
||||
topics: set of topics to install (see OPENCODE_TOPICS). Defaults to
|
||||
OPENCODE_DEFAULT_TOPICS which drops marketing/biz skills.
|
||||
"""
|
||||
if topics is None:
|
||||
topics = OPENCODE_DEFAULT_TOPICS
|
||||
agents_dir = Path.home() / ".config" / "opencode" / "agents"
|
||||
skills_dir = Path.home() / ".config" / "opencode" / "skills"
|
||||
agents_dir.mkdir(parents=True, exist_ok=True)
|
||||
skills_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Offensive/engineering personas get full permissions (primary mode).
|
||||
# Analytical personas are subagents with readonly bias.
|
||||
OFFENSIVE_DOMAINS = {
|
||||
"cybersecurity",
|
||||
"engineering",
|
||||
"devops",
|
||||
"software-development",
|
||||
"ai-ml",
|
||||
}
|
||||
|
||||
DOMAIN_COLOR = {
|
||||
"cybersecurity": "error", # red-like
|
||||
"intelligence": "info", # cyan-like
|
||||
"military": "warning", # orange
|
||||
"law": "warning",
|
||||
"economics": "success",
|
||||
"politics": "accent",
|
||||
"history": "primary",
|
||||
"linguistics": "secondary",
|
||||
"media": "secondary",
|
||||
"engineering": "success",
|
||||
"academia": "primary",
|
||||
"humanities": "accent",
|
||||
"science": "info",
|
||||
"strategy": "accent",
|
||||
}
|
||||
|
||||
agent_count = 0
|
||||
|
||||
for persona_dir in sorted(output_dir.iterdir()):
|
||||
if not persona_dir.is_dir() or persona_dir.name.startswith("_"):
|
||||
continue
|
||||
general_json = persona_dir / "general.json"
|
||||
if not general_json.exists():
|
||||
continue
|
||||
|
||||
data = json.loads(general_json.read_text(encoding="utf-8"))
|
||||
codename = data.get("codename", persona_dir.name)
|
||||
name = data.get("name", codename.title())
|
||||
role = data.get("role", "Specialist")
|
||||
domain = data.get("domain", "")
|
||||
tone = data.get("tone", "")
|
||||
address_to = data.get("address_to", "")
|
||||
quote = data.get("quote", "")
|
||||
skills = data.get("skills", [])
|
||||
|
||||
soul = data.get("sections", {}).get("soul", "")
|
||||
methodology = data.get("sections", {}).get("methodology", "")
|
||||
behavior = data.get("sections", {}).get("behavior_rules", "")
|
||||
|
||||
body = f"You are **{name}** ({address_to}) — {role}.\n\n"
|
||||
body += f"Domain: {domain} | Tone: {tone}\n\n"
|
||||
if quote:
|
||||
body += f'> "{quote}"\n\n'
|
||||
if soul:
|
||||
body += "## Soul\n" + soul.strip() + "\n\n"
|
||||
if methodology:
|
||||
body += "## Methodology\n" + methodology.strip() + "\n\n"
|
||||
if behavior:
|
||||
body += "## Behavior\n" + behavior.strip() + "\n\n"
|
||||
if skills:
|
||||
body += "## Mapped Skills\n" + ", ".join(skills) + "\n"
|
||||
|
||||
is_offensive = domain in OFFENSIVE_DOMAINS
|
||||
mode = "primary" if is_offensive else "subagent"
|
||||
color = DOMAIN_COLOR.get(domain, "primary")
|
||||
|
||||
# Permission block differs for offensive vs analytical personas.
|
||||
if is_offensive:
|
||||
permission_block = (
|
||||
"permission:\n"
|
||||
" edit: allow\n"
|
||||
" bash:\n"
|
||||
' "*": allow\n'
|
||||
" webfetch: allow\n"
|
||||
)
|
||||
else:
|
||||
permission_block = (
|
||||
"permission:\n"
|
||||
" edit: ask\n"
|
||||
" bash:\n"
|
||||
' "*": ask\n'
|
||||
" webfetch: allow\n"
|
||||
)
|
||||
|
||||
desc = f"{name} ({address_to}) — {role}. Domain: {domain}.".replace(
|
||||
"\n", " "
|
||||
)
|
||||
|
||||
frontmatter = (
|
||||
"---\n"
|
||||
f"description: {desc}\n"
|
||||
f"mode: {mode}\n"
|
||||
"temperature: 0.3\n"
|
||||
f"color: {color}\n"
|
||||
f"{permission_block}"
|
||||
"---\n\n"
|
||||
)
|
||||
agent_file = agents_dir / f"{codename}.md"
|
||||
agent_file.write_text(frontmatter + body, encoding="utf-8")
|
||||
agent_count += 1
|
||||
|
||||
# Install shared skills with topic filter. OpenCode reads SKILL.md with
|
||||
# name+description frontmatter (same as Claude).
|
||||
skill_count = 0
|
||||
per_topic: dict[str, int] = {}
|
||||
skipped_topic = 0
|
||||
|
||||
# Purge existing skills dir so stale filtered-out skills are removed.
|
||||
if skills_dir.exists():
|
||||
import shutil as _shutil
|
||||
|
||||
for existing in skills_dir.iterdir():
|
||||
if existing.is_dir():
|
||||
_shutil.rmtree(existing)
|
||||
|
||||
if shared_dir:
|
||||
for skills_subdir in ["skills", "paperclip-skills", "community-skills"]:
|
||||
src_root = shared_dir / skills_subdir
|
||||
if not src_root.exists():
|
||||
continue
|
||||
for skill_dir in src_root.iterdir():
|
||||
if not skill_dir.is_dir():
|
||||
continue
|
||||
src_skill = skill_dir / "SKILL.md"
|
||||
if not src_skill.exists():
|
||||
continue
|
||||
# Honor opencode name regex: ^[a-z0-9]+(-[a-z0-9]+)*$.
|
||||
sanitized = skill_dir.name.lower()
|
||||
if not re.match(r"^[a-z0-9]+(-[a-z0-9]+)*$", sanitized):
|
||||
continue
|
||||
|
||||
# Topic filter — drop skills not in requested topics.
|
||||
fm = _parse_skill_frontmatter_simple(src_skill)
|
||||
topic = _classify_skill_topic(skill_dir.name, fm)
|
||||
if topic not in topics:
|
||||
skipped_topic += 1
|
||||
continue
|
||||
|
||||
per_topic[topic] = per_topic.get(topic, 0) + 1
|
||||
|
||||
dest_dir = skills_dir / sanitized
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
dest_skill = dest_dir / "SKILL.md"
|
||||
dest_skill.write_text(
|
||||
src_skill.read_text(encoding="utf-8"), encoding="utf-8"
|
||||
)
|
||||
# Copy references/ if present.
|
||||
refs = skill_dir / "references"
|
||||
if refs.exists() and refs.is_dir():
|
||||
dest_refs = dest_dir / "references"
|
||||
dest_refs.mkdir(exist_ok=True)
|
||||
for ref in refs.iterdir():
|
||||
if ref.is_file():
|
||||
(dest_refs / ref.name).write_text(
|
||||
ref.read_text(encoding="utf-8"),
|
||||
encoding="utf-8",
|
||||
)
|
||||
skill_count += 1
|
||||
|
||||
print(
|
||||
f" OpenCode: {agent_count} agents installed to {agents_dir}"
|
||||
)
|
||||
print(
|
||||
f" OpenCode skills: {skill_count} installed "
|
||||
f"({skipped_topic} skipped by topic filter)"
|
||||
)
|
||||
if per_topic:
|
||||
print(" Per topic: " + ", ".join(
|
||||
f"{k}={v}" for k, v in sorted(per_topic.items(), key=lambda x: -x[1])
|
||||
))
|
||||
return agent_count
|
||||
|
||||
|
||||
def install_gemini(output_dir: Path):
|
||||
"""Install personas as Gemini Gems (JSON format for Google AI Studio)."""
|
||||
gems_dir = output_dir / "_gems"
|
||||
@@ -1534,6 +1848,7 @@ def main():
|
||||
"antigravity",
|
||||
"gemini",
|
||||
"openclaw",
|
||||
"opencode",
|
||||
"paperclip",
|
||||
"all",
|
||||
],
|
||||
@@ -1587,6 +1902,17 @@ def main():
|
||||
"offensive=red-team+pentest+exploit verbs; defensive=DFIR+threat-hunting; "
|
||||
"ctiops=threat-intel+APT; minimal=top categories only; all=no filters.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--opencode-topics",
|
||||
default=None,
|
||||
help="Comma-separated topic filter for --install opencode. "
|
||||
"Topics: security-offensive, security-defensive, security-cloud, "
|
||||
"security-specialized, security-iam, security-network, security-general, "
|
||||
"ai-llm-dev, coding-backend, coding-frontend, coding-tools, cloud-infra, "
|
||||
"database, browser-scrape, ops-sysadmin, osint-intel, marketing-content, "
|
||||
"business-pm, uncategorized. "
|
||||
"Default drops marketing/biz. Use 'all' for no filter.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--search",
|
||||
type=str,
|
||||
@@ -1717,6 +2043,7 @@ def main():
|
||||
"antigravity",
|
||||
"gemini",
|
||||
"openclaw",
|
||||
"opencode",
|
||||
"paperclip",
|
||||
]
|
||||
else:
|
||||
@@ -1740,6 +2067,19 @@ def main():
|
||||
install_gemini(output_dir)
|
||||
elif target == "openclaw":
|
||||
install_openclaw(output_dir)
|
||||
elif target == "opencode":
|
||||
if args.opencode_topics:
|
||||
if args.opencode_topics.strip().lower() == "all":
|
||||
oc_topics = OPENCODE_TOPICS
|
||||
else:
|
||||
oc_topics = {
|
||||
t.strip()
|
||||
for t in args.opencode_topics.split(",")
|
||||
if t.strip()
|
||||
}
|
||||
else:
|
||||
oc_topics = None # use default
|
||||
install_opencode(output_dir, shared_dir, topics=oc_topics)
|
||||
elif target == "paperclip":
|
||||
install_paperclip(output_dir, personas_dir, shared_dir)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user