Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,12 @@ Everything above is what one user gets. Now scale it up: when you join a shared

## News

- **2026/04/22** — Added a bilingual dashboard with `skillclaw dashboard sync` and `skillclaw dashboard serve` for inspecting local/shared skills, validation progress, version history, and session traces.
- **2026/04/20** — Added [Codex](https://github.com/openai/codex) and [Claude Code](https://docs.anthropic.com/en/docs/claude-code) integration with proxy auto-configuration, native skills-directory defaults, and `doctor` / `restore` commands.
- **2026/04/17** — Added [QwenPaw](https://github.com/agentscope-ai/QwenPaw) integration and updated the docs for broader multi-agent compatibility.
- **2026/04/17** — Added full [Hermes](https://github.com/NousResearch/hermes-agent) integration, per-turn skill tracking, `doctor hermes`, `skillclaw skills *` management commands, and a major docs overhaul.
- **2026/04/14** — WeChat discussion group is live! [Join the group](assets/image.png) to chat with us.
- **2026/04/14** — Seamless integration with [Hermes](https://github.com/NousResearch/hermes-agent) is now available.
- **2026/04/14** — Initial [Hermes](https://github.com/NousResearch/hermes-agent) support landed together with the first README refresh.
- **2026/04/12** — Active discussion with [Deer-Flow](https://github.com/bytedance/deer-flow/discussions/2133) on cross-framework skill sharing.
- **2026/04/11** — SkillClaw ranked **#2 Paper of the Day** on [Hugging Face Daily Papers](https://huggingface.co/papers/2604.08377)!
- **2026/04/10** — SkillClaw is now open source! Code released on [GitHub](https://github.com/AMAP-ML/SkillClaw).
Expand Down Expand Up @@ -311,6 +315,46 @@ skillclaw validation run-once --force

`skillclaw start --daemon` will automatically run the background validator afterward. `run-once --force` is the quickest way to test the path without waiting for the idle timer.

### Optional: inspect skills and sessions with the dashboard

The dashboard is a local visualization layer for the current SkillClaw snapshot. It is useful when you want to inspect:

- local skills and whether they match the shared official version
- candidate validation jobs and their current status
- published shared skills and version history
- local and shared sessions behind skill updates

The dashboard commands are available from the same `skillclaw` install:

```bash
skillclaw dashboard sync
skillclaw dashboard serve
```

If you want to point the dashboard at a local shared root and a specific group:

```bash
skillclaw dashboard sync \
--sharing-local-root /path/to/shared/root \
--sharing-group-id my-group \
--sharing-user-alias alice

skillclaw dashboard serve \
--host 127.0.0.1 \
--port 3791 \
--sharing-local-root /path/to/shared/root \
--sharing-group-id my-group \
--sharing-user-alias alice
```

Then open:

```text
http://127.0.0.1:3791
```

By default, `serve` rebuilds the snapshot on startup. If you already ran `skillclaw dashboard sync`, you can start faster with `--no-sync-on-start`.

## Server Guide

The evolve server is the shared backend for one user or many users. It can run locally for a personal setup, or remotely for a team setup.
Expand Down
6 changes: 5 additions & 1 deletion assets/README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,12 @@ SkillClaw 不是让 Hermes 学更多,而是让它学到的一切,真正变

## 动态

- **2026/04/22** — 新增支持中英文切换的 dashboard,可通过 `skillclaw dashboard sync` 和 `skillclaw dashboard serve` 查看本地 / 共享 skill、候选验证进度、版本历史与会话追溯。
- **2026/04/20** — 新增 [Codex](https://github.com/openai/codex) 与 [Claude Code](https://docs.anthropic.com/en/docs/claude-code) 集成,支持自动接入代理、使用各自原生 skills 目录,并提供 `doctor` / `restore` 命令。
- **2026/04/17** — 新增 [QwenPaw](https://github.com/agentscope-ai/QwenPaw) 集成,并同步更新文档以覆盖更多 Agent 框架。
- **2026/04/17** — 补齐完整的 [Hermes](https://github.com/NousResearch/hermes-agent) 集成能力,加入逐轮 skill 使用追踪、`doctor hermes`、`skillclaw skills *` 管理命令,以及一轮文档重构。
- **2026/04/14** — 微信讨论群已开放![加入群聊](./image.png)和我们交流。
- **2026/04/14** — 已支持与 [Hermes](https://github.com/NousResearch/hermes-agent) 无缝集成
- **2026/04/14** — 初步接入 [Hermes](https://github.com/NousResearch/hermes-agent),并完成第一轮 README 改版
- **2026/04/12** — 正在与 [Deer-Flow](https://github.com/bytedance/deer-flow/discussions/2133) 讨论跨框架技能共享。
- **2026/04/11** — SkillClaw 在 [Hugging Face Daily Papers](https://huggingface.co/papers/2604.08377) 上获得**当日第 2 名**!
- **2026/04/10** — SkillClaw 正式开源!代码已发布在 [GitHub](https://github.com/AMAP-ML/SkillClaw)。
Expand Down
Binary file modified assets/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ skillclaw-evolve-server = "evolve_server.__main__:main"
where = ["."]
include = ["skillclaw*", "evolve_server*"]

[tool.setuptools.package-data]
skillclaw = ["dashboard_assets/*"]

[tool.ruff]
line-length = 120

Expand Down
164 changes: 164 additions & 0 deletions skillclaw/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,170 @@ def validation_run_once(force: bool):
click.echo(f"{key}: {value}")


@skillclaw.group()
def dashboard():
"""Dashboard and skill visualization commands."""


def _apply_dashboard_runtime_overrides(
cfg,
*,
host: str | None = None,
port: int | None = None,
db_path: str | None = None,
no_sync_on_start: bool = False,
sharing_local_root: str | None = None,
sharing_group_id: str | None = None,
sharing_user_alias: str | None = None,
include_shared: bool | None = None,
evolve_server_url: str | None = None,
):
if host:
cfg.dashboard_host = host
if port:
cfg.dashboard_port = port
if db_path:
cfg.dashboard_db_path = db_path
if no_sync_on_start:
cfg.dashboard_sync_on_start = False
if sharing_local_root:
cfg.sharing_enabled = True
cfg.sharing_backend = "local"
cfg.sharing_local_root = sharing_local_root
if sharing_group_id:
cfg.sharing_group_id = sharing_group_id
if sharing_user_alias:
cfg.sharing_user_alias = sharing_user_alias
if include_shared is not None:
cfg.dashboard_include_shared = include_shared
if evolve_server_url is not None:
cfg.dashboard_evolve_server_url = evolve_server_url
return cfg


@dashboard.command(name="sync")
@click.option(
"--db-path",
type=click.Path(dir_okay=False, path_type=str),
default=None,
help="Override dashboard SQLite file path.",
)
@click.option(
"--sharing-local-root",
type=click.Path(file_okay=False, path_type=str),
default=None,
help="Use a local filesystem directory as the shared storage root for dashboard sync.",
)
@click.option("--sharing-group-id", type=str, default=None, help="Override shared storage group id.")
@click.option("--sharing-user-alias", type=str, default=None, help="Override sharing user alias.")
@click.option(
"--include-shared/--no-include-shared",
default=None,
help="Control whether shared storage is included in the dashboard snapshot.",
)
@click.option("--evolve-server-url", type=str, default=None, help="Override evolve server base URL.")
def dashboard_sync(
db_path: str | None,
sharing_local_root: str | None,
sharing_group_id: str | None,
sharing_user_alias: str | None,
include_shared: bool | None,
evolve_server_url: str | None,
):
"""Refresh the dashboard SQLite projection."""
from .dashboard_server import DashboardService

cs = ConfigStore()
cfg = _apply_dashboard_runtime_overrides(
cs.to_skillclaw_config(),
db_path=db_path,
sharing_local_root=sharing_local_root,
sharing_group_id=sharing_group_id,
sharing_user_alias=sharing_user_alias,
include_shared=include_shared,
evolve_server_url=evolve_server_url,
)
service = DashboardService(cfg)
result = service.sync()
summary = result["summary"]
click.echo(
f"Dashboard snapshot synced: "
f"{summary['skills']} skills, "
f"{summary['sessions']} sessions, "
f"{summary['validation_jobs']} validation jobs."
)
click.echo(f"SQLite: {cfg.dashboard_db_path}")
warnings = summary.get("warnings") or []
if warnings:
click.echo("Warnings:")
for item in warnings:
click.echo(f" - {item}")


@dashboard.command(name="serve")
@click.option("--host", type=str, default=None, help="Override dashboard host.")
@click.option("--port", type=int, default=None, help="Override dashboard port.")
@click.option(
"--db-path",
type=click.Path(dir_okay=False, path_type=str),
default=None,
help="Override dashboard SQLite file path.",
)
@click.option(
"--no-sync-on-start",
is_flag=True,
default=False,
help="Start the dashboard without rebuilding the snapshot first.",
)
@click.option(
"--sharing-local-root",
type=click.Path(file_okay=False, path_type=str),
default=None,
help="Use a local filesystem directory as the shared storage root while serving the dashboard.",
)
@click.option("--sharing-group-id", type=str, default=None, help="Override shared storage group id.")
@click.option("--sharing-user-alias", type=str, default=None, help="Override sharing user alias.")
@click.option(
"--include-shared/--no-include-shared",
default=None,
help="Control whether shared storage is included in the dashboard snapshot.",
)
@click.option("--evolve-server-url", type=str, default=None, help="Override evolve server base URL.")
def dashboard_serve(
host: str | None,
port: int | None,
db_path: str | None,
no_sync_on_start: bool,
sharing_local_root: str | None,
sharing_group_id: str | None,
sharing_user_alias: str | None,
include_shared: bool | None,
evolve_server_url: str | None,
):
"""Serve the dashboard UI and API."""
from .dashboard_server import serve_dashboard

cs = ConfigStore()
cfg = _apply_dashboard_runtime_overrides(
cs.to_skillclaw_config(),
host=host,
port=port,
db_path=db_path,
no_sync_on_start=no_sync_on_start,
sharing_local_root=sharing_local_root,
sharing_group_id=sharing_group_id,
sharing_user_alias=sharing_user_alias,
include_shared=include_shared,
evolve_server_url=evolve_server_url,
)

click.echo(
f"Starting SkillClaw dashboard at http://{cfg.dashboard_host}:{cfg.dashboard_port} "
f"(db: {cfg.dashboard_db_path})"
)
serve_dashboard(cfg)


@skillclaw.group()
def skills():
"""Skill management commands."""
Expand Down
11 changes: 11 additions & 0 deletions skillclaw/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,17 @@ class SkillClawConfig:
validation_max_jobs_per_day: int = 5
validation_max_concurrency: int = 1

# ------------------------------------------------------------------ #
# Dashboard #
# ------------------------------------------------------------------ #
dashboard_enabled: bool = False
dashboard_host: str = "127.0.0.1"
dashboard_port: int = 3788
dashboard_db_path: str = "~/.skillclaw/dashboard.db"
dashboard_sync_on_start: bool = True
dashboard_include_shared: bool = True
dashboard_evolve_server_url: str = ""

# ------------------------------------------------------------------ #
# Cloud / Bedrock #
# ------------------------------------------------------------------ #
Expand Down
26 changes: 26 additions & 0 deletions skillclaw/config_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@
"max_jobs_per_day": 5,
"max_concurrency": 1,
},
"dashboard": {
"enabled": False,
"host": "127.0.0.1",
"port": 3788,
"db_path": str(CONFIG_DIR / "dashboard.db"),
"sync_on_start": True,
"include_shared": True,
"evolve_server_url": "",
},
}


Expand Down Expand Up @@ -253,6 +262,7 @@ def to_skillclaw_config(self) -> SkillClawConfig:

sharing = data.get("sharing", {})
validation = data.get("validation", {})
dashboard = data.get("dashboard", {})
sharing_backend = _infer_sharing_backend(sharing)
sharing_endpoint = _first_non_empty(sharing, "endpoint")
sharing_bucket = _first_non_empty(sharing, "bucket")
Expand Down Expand Up @@ -331,6 +341,15 @@ def to_skillclaw_config(self) -> SkillClawConfig:
validation_poll_interval_seconds=int(validation.get("poll_interval_seconds", 60)),
validation_max_jobs_per_day=int(validation.get("max_jobs_per_day", 5)),
validation_max_concurrency=max(1, int(validation.get("max_concurrency", 1))),
dashboard_enabled=bool(dashboard.get("enabled", False)),
dashboard_host=str(dashboard.get("host", "127.0.0.1") or "127.0.0.1"),
dashboard_port=int(dashboard.get("port", 3788) or 3788),
dashboard_db_path=str(
dashboard.get("db_path", str(CONFIG_DIR / "dashboard.db")) or str(CONFIG_DIR / "dashboard.db")
),
dashboard_sync_on_start=bool(dashboard.get("sync_on_start", True)),
dashboard_include_shared=bool(dashboard.get("include_shared", True)),
dashboard_evolve_server_url=str(dashboard.get("evolve_server_url", "") or ""),
)

def describe(self) -> str:
Expand All @@ -339,6 +358,7 @@ def describe(self) -> str:
llm = data.get("llm", {})
skills = data.get("skills", {})
prm = data.get("prm", {})
dashboard = data.get("dashboard", {})
claw_type = str(data.get("claw_type", "openclaw") or "openclaw")
effective_skills_dir = resolve_skills_dir(
skills.get("dir", str(_DEFAULT_SKILLS_DIR)),
Expand Down Expand Up @@ -397,5 +417,11 @@ def describe(self) -> str:
f"validation.mode: {_normalize_validation_mode(validation.get('mode', 'replay'))}",
f"validation.idle_after: {validation.get('idle_after_seconds', 300)}",
f"validation.poll_interval: {validation.get('poll_interval_seconds', 60)}",
f"dashboard.enabled: {dashboard.get('enabled', False)}",
f"dashboard.host: {dashboard.get('host', '127.0.0.1')}",
f"dashboard.port: {dashboard.get('port', 3788)}",
f"dashboard.db_path: {dashboard.get('db_path', str(CONFIG_DIR / 'dashboard.db'))}",
f"dashboard.include_shared: {dashboard.get('include_shared', True)}",
f"dashboard.evolve_server_url: {dashboard.get('evolve_server_url', '') or '(not set)'}",
]
return "\n".join(lines)
Loading
Loading