diff --git a/nanobot/agent/memory.py b/nanobot/agent/memory.py index 58c079b..453407e 100644 --- a/nanobot/agent/memory.py +++ b/nanobot/agent/memory.py @@ -11,7 +11,6 @@ class MemoryStore: Memory system for the agent. Supports daily notes (memory/YYYY-MM-DD.md) and long-term memory (MEMORY.md). - Compatible with clawbot memory format. """ def __init__(self, workspace: Path): diff --git a/nanobot/agent/skills.py b/nanobot/agent/skills.py index 7b04924..ead9f5b 100644 --- a/nanobot/agent/skills.py +++ b/nanobot/agent/skills.py @@ -53,7 +53,7 @@ class SkillsLoader: # Filter by requirements if filter_unavailable: - return [s for s in skills if self._check_requirements(self._get_ocmeta(s["name"]))] + return [s for s in skills if self._check_requirements(self._get_skill_meta(s["name"]))] return skills def load_skill(self, name: str) -> str | None: @@ -120,8 +120,8 @@ class SkillsLoader: name = escape_xml(s["name"]) path = s["path"] desc = escape_xml(self._get_skill_description(s["name"])) - ocmeta = self._get_ocmeta(s["name"]) - available = self._check_requirements(ocmeta) + skill_meta = self._get_skill_meta(s["name"]) + available = self._check_requirements(skill_meta) lines.append(f" ") lines.append(f" {name}") @@ -130,7 +130,7 @@ class SkillsLoader: # Show missing requirements for unavailable skills if not available: - missing = self._get_missing_requirements(ocmeta) + missing = self._get_missing_requirements(skill_meta) if missing: lines.append(f" {escape_xml(missing)}") @@ -139,10 +139,10 @@ class SkillsLoader: return "\n".join(lines) - def _get_missing_requirements(self, ocmeta: dict) -> str: + def _get_missing_requirements(self, skill_meta: dict) -> str: """Get a description of missing requirements.""" missing = [] - requires = ocmeta.get("requires", {}) + requires = skill_meta.get("requires", {}) for b in requires.get("bins", []): if not shutil.which(b): missing.append(f"CLI: {b}") @@ -166,17 +166,17 @@ class SkillsLoader: return content[match.end():].strip() return content - def _parse_openclaw_metadata(self, raw: str) -> dict: - """Parse openclaw metadata JSON from frontmatter.""" + def _parse_nanobot_metadata(self, raw: str) -> dict: + """Parse nanobot metadata JSON from frontmatter.""" try: data = json.loads(raw) - return data.get("openclaw", {}) if isinstance(data, dict) else {} + return data.get("nanobot", {}) if isinstance(data, dict) else {} except (json.JSONDecodeError, TypeError): return {} - def _check_requirements(self, ocmeta: dict) -> bool: + def _check_requirements(self, skill_meta: dict) -> bool: """Check if skill requirements are met (bins, env vars).""" - requires = ocmeta.get("requires", {}) + requires = skill_meta.get("requires", {}) for b in requires.get("bins", []): if not shutil.which(b): return False @@ -185,18 +185,18 @@ class SkillsLoader: return False return True - def _get_ocmeta(self, name: str) -> dict: - """Get openclaw metadata for a skill (cached in frontmatter).""" + def _get_skill_meta(self, name: str) -> dict: + """Get nanobot metadata for a skill (cached in frontmatter).""" meta = self.get_skill_metadata(name) or {} - return self._parse_openclaw_metadata(meta.get("metadata", "")) + return self._parse_nanobot_metadata(meta.get("metadata", "")) def get_always_skills(self) -> list[str]: """Get skills marked as always=true that meet requirements.""" result = [] for s in self.list_skills(filter_unavailable=True): meta = self.get_skill_metadata(s["name"]) or {} - ocmeta = self._parse_openclaw_metadata(meta.get("metadata", "")) - if ocmeta.get("always") or meta.get("always"): + skill_meta = self._parse_nanobot_metadata(meta.get("metadata", "")) + if skill_meta.get("always") or meta.get("always"): result.append(s["name"]) return result diff --git a/nanobot/config/loader.py b/nanobot/config/loader.py index e828c20..f8de881 100644 --- a/nanobot/config/loader.py +++ b/nanobot/config/loader.py @@ -53,7 +53,7 @@ def save_config(config: Config, config_path: Path | None = None) -> None: path = config_path or get_config_path() path.parent.mkdir(parents=True, exist_ok=True) - # Convert to clawbot-compatible format (camelCase) + # Convert to camelCase format data = config.model_dump() data = convert_to_camel(data) @@ -71,7 +71,7 @@ def convert_keys(data: Any) -> Any: def convert_to_camel(data: Any) -> Any: - """Convert snake_case keys to camelCase for clawbot compatibility.""" + """Convert snake_case keys to camelCase.""" if isinstance(data, dict): return {snake_to_camel(k): convert_to_camel(v) for k, v in data.items()} if isinstance(data, list): diff --git a/nanobot/config/schema.py b/nanobot/config/schema.py index 865bb55..06c36e6 100644 --- a/nanobot/config/schema.py +++ b/nanobot/config/schema.py @@ -75,11 +75,7 @@ class ToolsConfig(BaseModel): class Config(BaseSettings): - """ - Root configuration for nanobot. - - Compatible with clawbot configuration format for easy migration. - """ + """Root configuration for nanobot.""" agents: AgentsConfig = Field(default_factory=AgentsConfig) channels: ChannelsConfig = Field(default_factory=ChannelsConfig) providers: ProvidersConfig = Field(default_factory=ProvidersConfig) diff --git a/nanobot/skills/README.md b/nanobot/skills/README.md new file mode 100644 index 0000000..f0dcea7 --- /dev/null +++ b/nanobot/skills/README.md @@ -0,0 +1,24 @@ +# nanobot Skills + +This directory contains built-in skills that extend nanobot's capabilities. + +## Skill Format + +Each skill is a directory containing a `SKILL.md` file with: +- YAML frontmatter (name, description, metadata) +- Markdown instructions for the agent + +## Attribution + +These skills are adapted from [OpenClaw](https://github.com/openclaw/openclaw)'s skill system. +The skill format and metadata structure follow OpenClaw's conventions to maintain compatibility. + +## Available Skills + +| Skill | Description | +|-------|-------------| +| `github` | Interact with GitHub using the `gh` CLI | +| `weather` | Get weather info using wttr.in and Open-Meteo | +| `summarize` | Summarize URLs, files, and YouTube videos | +| `tmux` | Remote-control tmux sessions | +| `skill-creator` | Create new skills | \ No newline at end of file diff --git a/nanobot/skills/github/SKILL.md b/nanobot/skills/github/SKILL.md index 8ba76ef..57d8127 100644 --- a/nanobot/skills/github/SKILL.md +++ b/nanobot/skills/github/SKILL.md @@ -1,7 +1,7 @@ --- name: github description: "Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries." -metadata: {"openclaw":{"emoji":"🐙","requires":{"bins":["gh"]},"install":[{"id":"brew","kind":"brew","formula":"gh","bins":["gh"],"label":"Install GitHub CLI (brew)"},{"id":"apt","kind":"apt","package":"gh","bins":["gh"],"label":"Install GitHub CLI (apt)"}]}} +metadata: {"nanobot":{"emoji":"🐙","requires":{"bins":["gh"]},"install":[{"id":"brew","kind":"brew","formula":"gh","bins":["gh"],"label":"Install GitHub CLI (brew)"},{"id":"apt","kind":"apt","package":"gh","bins":["gh"],"label":"Install GitHub CLI (apt)"}]}} --- # GitHub Skill diff --git a/nanobot/skills/summarize/SKILL.md b/nanobot/skills/summarize/SKILL.md index edbb60f..766ab5d 100644 --- a/nanobot/skills/summarize/SKILL.md +++ b/nanobot/skills/summarize/SKILL.md @@ -2,7 +2,7 @@ name: summarize description: Summarize or extract text/transcripts from URLs, podcasts, and local files (great fallback for “transcribe this YouTube/video”). homepage: https://summarize.sh -metadata: {"openclaw":{"emoji":"🧾","requires":{"bins":["summarize"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/summarize","bins":["summarize"],"label":"Install summarize (brew)"}]}} +metadata: {"nanobot":{"emoji":"🧾","requires":{"bins":["summarize"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/summarize","bins":["summarize"],"label":"Install summarize (brew)"}]}} --- # Summarize diff --git a/nanobot/skills/tmux/SKILL.md b/nanobot/skills/tmux/SKILL.md index b881d01..f2a3144 100644 --- a/nanobot/skills/tmux/SKILL.md +++ b/nanobot/skills/tmux/SKILL.md @@ -1,20 +1,20 @@ --- name: tmux description: Remote-control tmux sessions for interactive CLIs by sending keystrokes and scraping pane output. -metadata: {"openclaw":{"emoji":"🧵","os":["darwin","linux"],"requires":{"bins":["tmux"]}}} +metadata: {"nanobot":{"emoji":"🧵","os":["darwin","linux"],"requires":{"bins":["tmux"]}}} --- -# tmux Skill (OpenClaw) +# tmux Skill Use tmux only when you need an interactive TTY. Prefer exec background mode for long-running, non-interactive tasks. ## Quickstart (isolated socket, exec tool) ```bash -SOCKET_DIR="${OPENCLAW_TMUX_SOCKET_DIR:-${CLAWDBOT_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/openclaw-tmux-sockets}}" +SOCKET_DIR="${NANOBOT_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/nanobot-tmux-sockets}" mkdir -p "$SOCKET_DIR" -SOCKET="$SOCKET_DIR/openclaw.sock" -SESSION=openclaw-python +SOCKET="$SOCKET_DIR/nanobot.sock" +SESSION=nanobot-python tmux -S "$SOCKET" new -d -s "$SESSION" -n shell tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 -- 'PYTHON_BASIC_REPL=1 python3 -q' Enter @@ -31,8 +31,8 @@ To monitor: ## Socket convention -- Use `OPENCLAW_TMUX_SOCKET_DIR` (legacy `CLAWDBOT_TMUX_SOCKET_DIR` also supported). -- Default socket path: `"$OPENCLAW_TMUX_SOCKET_DIR/openclaw.sock"`. +- Use `NANOBOT_TMUX_SOCKET_DIR` environment variable. +- Default socket path: `"$NANOBOT_TMUX_SOCKET_DIR/nanobot.sock"`. ## Targeting panes and naming @@ -43,7 +43,7 @@ To monitor: ## Finding sessions - List sessions on your socket: `{baseDir}/scripts/find-sessions.sh -S "$SOCKET"`. -- Scan all sockets: `{baseDir}/scripts/find-sessions.sh --all` (uses `OPENCLAW_TMUX_SOCKET_DIR`). +- Scan all sockets: `{baseDir}/scripts/find-sessions.sh --all` (uses `NANOBOT_TMUX_SOCKET_DIR`). ## Sending input safely diff --git a/nanobot/skills/tmux/scripts/find-sessions.sh b/nanobot/skills/tmux/scripts/find-sessions.sh index 8387c16..00552c6 100755 --- a/nanobot/skills/tmux/scripts/find-sessions.sh +++ b/nanobot/skills/tmux/scripts/find-sessions.sh @@ -10,7 +10,7 @@ List tmux sessions on a socket (default tmux socket if none provided). Options: -L, --socket tmux socket name (passed to tmux -L) -S, --socket-path tmux socket path (passed to tmux -S) - -A, --all scan all sockets under OPENCLAW_TMUX_SOCKET_DIR + -A, --all scan all sockets under NANOBOT_TMUX_SOCKET_DIR -q, --query case-insensitive substring to filter session names -h, --help show this help USAGE @@ -20,7 +20,7 @@ socket_name="" socket_path="" query="" scan_all=false -socket_dir="${OPENCLAW_TMUX_SOCKET_DIR:-${CLAWDBOT_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/openclaw-tmux-sockets}}" +socket_dir="${NANOBOT_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/nanobot-tmux-sockets}" while [[ $# -gt 0 ]]; do case "$1" in diff --git a/nanobot/skills/weather/SKILL.md b/nanobot/skills/weather/SKILL.md index 03bc56e..8073de1 100644 --- a/nanobot/skills/weather/SKILL.md +++ b/nanobot/skills/weather/SKILL.md @@ -2,7 +2,7 @@ name: weather description: Get current weather and forecasts (no API key required). homepage: https://wttr.in/:help -metadata: {"openclaw":{"emoji":"🌤️","requires":{"bins":["curl"]}}} +metadata: {"nanobot":{"emoji":"🌤️","requires":{"bins":["curl"]}}} --- # Weather