- AgentHub: 13 files updated with non-engineering examples (content drafts, research, strategy) — engineering stays primary, cross-domain secondary - AgentHub: 7 slash commands, 5 Python scripts, 3 references, 1 agent, dry_run.py validation (57 checks) - Marketplace: agenthub entry added with cross-domain keywords, engineering POWERFUL updated (25→30), product (12→13), counts synced across all configs - SEO: generate-docs.py now produces keyword-rich <title> tags and meta descriptions using SKILL.md frontmatter — "Claude Code Skills" in site_name propagates to all 276 HTML pages - SEO: per-domain title suffixes (Agent Skill for Codex & OpenClaw, etc.), slug-as-title cleanup, domain label stripping from titles - Broken links: 141→0 warnings — new rewrite_skill_internal_links() converts references/, scripts/, assets/ links to GitHub source URLs; skills/index.md phantom slugs fixed (6 marketing, 7 RA/QM) - Counts synced: 204 skills, 266 tools, 382 refs, 16 agents, 17 commands, 21 plugins — consistent across CLAUDE.md, README.md, docs/index.md, marketplace.json, getting-started.md, mkdocs.yml - Platform sync: Codex 163 skills, Gemini 246 items, OpenClaw compatible Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
254 lines
8.3 KiB
Python
254 lines
8.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Initialize an AgentHub collaboration session.
|
|
|
|
Creates the .agenthub/ directory structure, generates a session ID,
|
|
and writes config.yaml and state.json for the session.
|
|
|
|
Usage:
|
|
python hub_init.py --task "Optimize API response time" --agents 3 \\
|
|
--eval "pytest bench.py --json" --metric p50_ms --direction lower
|
|
|
|
python hub_init.py --task "Refactor auth module" --agents 2
|
|
|
|
python hub_init.py --demo
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
|
|
|
|
def generate_session_id():
|
|
"""Generate a timestamp-based session ID."""
|
|
return datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
|
|
|
|
def create_directory_structure(base_path):
|
|
"""Create the .agenthub/ directory tree."""
|
|
dirs = [
|
|
os.path.join(base_path, "sessions"),
|
|
os.path.join(base_path, "board", "dispatch"),
|
|
os.path.join(base_path, "board", "progress"),
|
|
os.path.join(base_path, "board", "results"),
|
|
]
|
|
for d in dirs:
|
|
os.makedirs(d, exist_ok=True)
|
|
|
|
|
|
def write_gitignore(base_path):
|
|
"""Write .agenthub/.gitignore to exclude worktree artifacts."""
|
|
gitignore_path = os.path.join(base_path, ".gitignore")
|
|
if not os.path.exists(gitignore_path):
|
|
with open(gitignore_path, "w") as f:
|
|
f.write("# AgentHub gitignore\n")
|
|
f.write("# Keep board and sessions, ignore worktree artifacts\n")
|
|
f.write("*.tmp\n")
|
|
f.write("*.lock\n")
|
|
|
|
|
|
def write_board_index(base_path):
|
|
"""Initialize the board index file."""
|
|
index_path = os.path.join(base_path, "board", "_index.json")
|
|
if not os.path.exists(index_path):
|
|
index = {
|
|
"channels": ["dispatch", "progress", "results"],
|
|
"counters": {"dispatch": 0, "progress": 0, "results": 0},
|
|
}
|
|
with open(index_path, "w") as f:
|
|
json.dump(index, f, indent=2)
|
|
f.write("\n")
|
|
|
|
|
|
def create_session(base_path, session_id, task, agents, eval_cmd, metric,
|
|
direction, base_branch):
|
|
"""Create a new session with config and state files."""
|
|
session_dir = os.path.join(base_path, "sessions", session_id)
|
|
os.makedirs(session_dir, exist_ok=True)
|
|
|
|
# Write config.yaml (manual YAML to avoid dependency)
|
|
config_path = os.path.join(session_dir, "config.yaml")
|
|
config_lines = [
|
|
f"session_id: {session_id}",
|
|
f"task: \"{task}\"",
|
|
f"agent_count: {agents}",
|
|
f"base_branch: {base_branch}",
|
|
f"created: {datetime.now(timezone.utc).isoformat()}",
|
|
]
|
|
if eval_cmd:
|
|
config_lines.append(f"eval_cmd: \"{eval_cmd}\"")
|
|
if metric:
|
|
config_lines.append(f"metric: {metric}")
|
|
if direction:
|
|
config_lines.append(f"direction: {direction}")
|
|
|
|
with open(config_path, "w") as f:
|
|
f.write("\n".join(config_lines))
|
|
f.write("\n")
|
|
|
|
# Write state.json
|
|
state_path = os.path.join(session_dir, "state.json")
|
|
state = {
|
|
"session_id": session_id,
|
|
"state": "init",
|
|
"created": datetime.now(timezone.utc).isoformat(),
|
|
"updated": datetime.now(timezone.utc).isoformat(),
|
|
"agents": {},
|
|
}
|
|
with open(state_path, "w") as f:
|
|
json.dump(state, f, indent=2)
|
|
f.write("\n")
|
|
|
|
return session_dir
|
|
|
|
|
|
def validate_git_repo():
|
|
"""Check if current directory is a git repository."""
|
|
if not os.path.isdir(".git"):
|
|
# Check parent dirs
|
|
path = os.path.abspath(".")
|
|
while path != "/":
|
|
if os.path.isdir(os.path.join(path, ".git")):
|
|
return True
|
|
path = os.path.dirname(path)
|
|
return False
|
|
return True
|
|
|
|
|
|
def get_current_branch():
|
|
"""Get the current git branch name."""
|
|
head_file = os.path.join(".git", "HEAD")
|
|
if os.path.exists(head_file):
|
|
with open(head_file) as f:
|
|
ref = f.read().strip()
|
|
if ref.startswith("ref: refs/heads/"):
|
|
return ref[len("ref: refs/heads/"):]
|
|
return "main"
|
|
|
|
|
|
def run_demo():
|
|
"""Show a demo of what hub_init creates."""
|
|
print("=" * 60)
|
|
print("AgentHub Init — Demo Mode")
|
|
print("=" * 60)
|
|
print()
|
|
print("Session ID: 20260317-143022")
|
|
print("Task: Optimize API response time below 100ms")
|
|
print("Agents: 3")
|
|
print("Eval: pytest bench.py --json")
|
|
print("Metric: p50_ms (lower is better)")
|
|
print("Base branch: dev")
|
|
print()
|
|
print("Directory structure created:")
|
|
print(" .agenthub/")
|
|
print(" ├── .gitignore")
|
|
print(" ├── sessions/")
|
|
print(" │ └── 20260317-143022/")
|
|
print(" │ ├── config.yaml")
|
|
print(" │ └── state.json")
|
|
print(" └── board/")
|
|
print(" ├── _index.json")
|
|
print(" ├── dispatch/")
|
|
print(" ├── progress/")
|
|
print(" └── results/")
|
|
print()
|
|
print("config.yaml:")
|
|
print(' session_id: 20260317-143022')
|
|
print(' task: "Optimize API response time below 100ms"')
|
|
print(" agent_count: 3")
|
|
print(" base_branch: dev")
|
|
print(' eval_cmd: "pytest bench.py --json"')
|
|
print(" metric: p50_ms")
|
|
print(" direction: lower")
|
|
print()
|
|
print("state.json:")
|
|
print(' { "state": "init", "agents": {} }')
|
|
print()
|
|
print("Next step: Run /hub:spawn to launch agents")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Initialize an AgentHub collaboration session"
|
|
)
|
|
parser.add_argument("--task", type=str, help="Task description for agents")
|
|
parser.add_argument("--agents", type=int, default=3,
|
|
help="Number of parallel agents (default: 3)")
|
|
parser.add_argument("--eval", type=str, dest="eval_cmd",
|
|
help="Evaluation command to run in each worktree")
|
|
parser.add_argument("--metric", type=str,
|
|
help="Metric name to extract from eval output")
|
|
parser.add_argument("--direction", choices=["lower", "higher"],
|
|
help="Whether lower or higher metric is better")
|
|
parser.add_argument("--base-branch", type=str,
|
|
help="Base branch (default: current branch)")
|
|
parser.add_argument("--format", choices=["text", "json"], default="text",
|
|
help="Output format (default: text)")
|
|
parser.add_argument("--demo", action="store_true",
|
|
help="Show demo output without creating files")
|
|
args = parser.parse_args()
|
|
|
|
if args.demo:
|
|
run_demo()
|
|
return
|
|
|
|
if not args.task:
|
|
print("Error: --task is required", file=sys.stderr)
|
|
print("Usage: hub_init.py --task 'description' [--agents N] "
|
|
"[--eval 'cmd'] [--metric name] [--direction lower|higher]",
|
|
file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
if not validate_git_repo():
|
|
print("Error: Not a git repository. AgentHub requires git.",
|
|
file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
base_branch = args.base_branch or get_current_branch()
|
|
base_path = ".agenthub"
|
|
session_id = generate_session_id()
|
|
|
|
# Create structure
|
|
create_directory_structure(base_path)
|
|
write_gitignore(base_path)
|
|
write_board_index(base_path)
|
|
|
|
# Create session
|
|
session_dir = create_session(
|
|
base_path, session_id, args.task, args.agents,
|
|
args.eval_cmd, args.metric, args.direction, base_branch
|
|
)
|
|
|
|
if args.format == "json":
|
|
output = {
|
|
"session_id": session_id,
|
|
"session_dir": session_dir,
|
|
"task": args.task,
|
|
"agent_count": args.agents,
|
|
"eval_cmd": args.eval_cmd,
|
|
"metric": args.metric,
|
|
"direction": args.direction,
|
|
"base_branch": base_branch,
|
|
"state": "init",
|
|
}
|
|
print(json.dumps(output, indent=2))
|
|
else:
|
|
print(f"AgentHub session initialized")
|
|
print(f" Session ID: {session_id}")
|
|
print(f" Task: {args.task}")
|
|
print(f" Agents: {args.agents}")
|
|
if args.eval_cmd:
|
|
print(f" Eval: {args.eval_cmd}")
|
|
if args.metric:
|
|
direction_str = "lower is better" if args.direction == "lower" else "higher is better"
|
|
print(f" Metric: {args.metric} ({direction_str})")
|
|
print(f" Base branch: {base_branch}")
|
|
print(f" State: init")
|
|
print()
|
|
print(f"Next step: Run /hub:spawn to launch {args.agents} agents")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|