Spaces:
Running
Running
feat(agent): add Claude Code-style agent, skills, slash-commands, hooks, todos, sandboxed workspace, and full-stack scaffolding
81aa0b5 verified | """Slash commands system — Claude Code-style. | |
| Commands are markdown files with YAML frontmatter that define | |
| prompt templates triggered by `/command` syntax. | |
| Built-in commands live in code/commands/builtins/. | |
| User commands live in workspace's .sonicoder/commands/. | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import os | |
| import re | |
| from typing import Any | |
| from code.skills import _parse_frontmatter | |
| logger = logging.getLogger(__name__) | |
| _BUILTIN_COMMANDS_DIR = os.path.join(os.path.dirname(__file__), "builtins") | |
| _USER_COMMANDS_DIRNAME = ".sonicoder/commands" | |
| def _command_dirs() -> list[str]: | |
| dirs = [_BUILTIN_COMMANDS_DIR] | |
| try: | |
| from code.tools.fs import get_workspace_root | |
| user_dir = os.path.join(get_workspace_root(), _USER_COMMANDS_DIRNAME) | |
| if os.path.isdir(user_dir): | |
| dirs.append(user_dir) | |
| except Exception: | |
| pass | |
| return dirs | |
| def _load_command(filepath: str) -> dict[str, Any] | None: | |
| try: | |
| with open(filepath, "r", encoding="utf-8") as f: | |
| content = f.read() | |
| except Exception as exc: | |
| logger.warning("Failed to read %s: %s", filepath, exc) | |
| return None | |
| meta, body = _parse_frontmatter(content) | |
| name = meta.get("name") or os.path.splitext(os.path.basename(filepath))[0] | |
| return { | |
| "name": name, | |
| "description": meta.get("description", ""), | |
| "argument_hint": meta.get("argument-hint", ""), | |
| "allowed_tools": [t.strip() for t in meta.get("allowed-tools", "").split(",") if t.strip()], | |
| "body": body.strip(), | |
| "path": filepath, | |
| } | |
| def list_commands() -> list[dict[str, Any]]: | |
| """List all available slash commands.""" | |
| commands: list[dict[str, Any]] = [] | |
| seen: set[str] = set() | |
| for cmds_dir in _command_dirs(): | |
| if not os.path.isdir(cmds_dir): | |
| continue | |
| for entry in sorted(os.listdir(cmds_dir)): | |
| if not entry.endswith(".md"): | |
| continue | |
| filepath = os.path.join(cmds_dir, entry) | |
| cmd = _load_command(filepath) | |
| if cmd and cmd["name"] not in seen: | |
| seen.add(cmd["name"]) | |
| commands.append({ | |
| "name": cmd["name"], | |
| "description": cmd["description"], | |
| "argument_hint": cmd["argument_hint"], | |
| }) | |
| return commands | |
| def get_command(name: str) -> dict[str, Any] | None: | |
| """Get full command content by name.""" | |
| for cmds_dir in _command_dirs(): | |
| if not os.path.isdir(cmds_dir): | |
| continue | |
| # Try name.md and name/something.md | |
| direct = os.path.join(cmds_dir, f"{name}.md") | |
| if os.path.isfile(direct): | |
| return _load_command(direct) | |
| # Try subdirectory: name/command.md | |
| if os.path.isdir(os.path.join(cmds_dir, name)): | |
| for entry in os.listdir(os.path.join(cmds_dir, name)): | |
| if entry.endswith(".md"): | |
| return _load_command(os.path.join(cmds_dir, name, entry)) | |
| return None | |
| def parse_command_input(user_input: str) -> tuple[str | None, str]: | |
| """Parse a user input string for a slash command. | |
| Returns (command_name, arguments) or (None, user_input) if not a command. | |
| """ | |
| stripped = user_input.strip() | |
| if not stripped.startswith("/"): | |
| return None, user_input | |
| # Match /command-name or /namespace:command | |
| match = re.match(r"^/([a-zA-Z][\w:-]*)\s*(.*)$", stripped, re.DOTALL) | |
| if not match: | |
| return None, user_input | |
| return match.group(1), match.group(2).strip() | |
| def expand_command(name: str, arguments: str = "") -> dict[str, Any]: | |
| """Expand a slash command into a full prompt for the model. | |
| Replaces $ARGUMENTS placeholder with the user-provided arguments. | |
| """ | |
| cmd = get_command(name) | |
| if not cmd: | |
| return { | |
| "success": False, | |
| "error": f"Unknown command: /{name}", | |
| "available": [c["name"] for c in list_commands()], | |
| } | |
| body = cmd["body"] | |
| # Replace $ARGUMENTS | |
| expanded = body.replace("$ARGUMENTS", arguments) | |
| # Also support bash-style $(cmd) execution for context blocks (like Claude Code) | |
| # e.g. !`git status` becomes the output of `git status` | |
| from code.tools.bash import run_bash | |
| def _exec_bash(match: re.Match) -> str: | |
| cmd_str = match.group(1) | |
| result = run_bash(cmd_str, timeout=10) | |
| return result.get("stdout", "") + result.get("stderr", "") | |
| expanded = re.sub(r"!`([^`]+)`", _exec_bash, expanded) | |
| return { | |
| "success": True, | |
| "name": cmd["name"], | |
| "description": cmd["description"], | |
| "prompt": expanded, | |
| "allowed_tools": cmd["allowed_tools"], | |
| } | |