skillreg 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
skillreg/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """skillreg — skill registry control plane.
2
+
3
+ A Python CLI + FastAPI backend that manages a skill registry workspace and
4
+ syncs skills to agent install targets. See ``README.md`` and ``docs/`` for the
5
+ current product docs.
6
+ """
7
+
8
+ __version__ = "0.1.0"
@@ -0,0 +1,94 @@
1
+ ---
2
+ name: skillreg-skill
3
+ description: 当用户要注册、导入、转换或同步本地 skill 时使用。用户可能在任意项目里对 agent 说“注册这个 skill”;优先把当前项目或用户给定路径中 agent/用户写好的 SKILL.md 注册到当前 skillreg workspace,而不是要求用户理解产品仓、workspace 仓等实现细节。
4
+ ---
5
+
6
+ # skillreg-skill
7
+
8
+ 当用户想用 `skillreg` 完成 skill 的注册、导入、转换、同步、查看状态或排障时,优先使用这个 skill。
9
+
10
+ 核心心智模型:
11
+
12
+ - 用户关心的是“把本地已经写好的 skill 放进我的 skill workspace,并同步给 agent 用”。
13
+ - 用户不需要关心当前是在业务项目、skill 源码项目、`skillreg` 产品仓,还是 workspace 仓。
14
+ - agent 要负责识别本地 skill 来源、确认当前 `skillreg` workspace,然后完成注册或给出可执行的下一步。
15
+
16
+ ## 触发场景
17
+
18
+ - 用户说"用 skillreg 管一下这个 skill"
19
+ - 用户说"注册这个 skill"、"把这个 skill 注册一下"、"把这个目录注册成 skill"
20
+ - 用户要把一个本地目录、zip、git 仓库注册进 workspace
21
+ - 用户要把 `skills/<name>` 转成 `repos/<name>-cli/skill/<name>`
22
+ - 用户要把 workspace 中的 skills 同步到 Claude、Codex、`.agents/skills` 等目标目录
23
+ - 用户反馈 `skillreg` 命令找不到、dashboard 起不来、workspace 不生效、sync 结果不对
24
+
25
+ ## 先做什么
26
+
27
+ 1. 识别要注册的 skill 来源:
28
+ - 用户给了路径:优先用该路径。
29
+ - 用户没给路径:从当前工作目录开始找 `SKILL.md`;如果当前目录本身就是 skill 目录,直接使用当前目录。
30
+ - 如果找不到 `SKILL.md`,先告诉用户需要一个包含 `SKILL.md` 的目录,并说明当前检查过哪里。
31
+ 2. 跑 `skillreg config`,确认当前 `workspace_path`、targets 和配置文件位置。
32
+ 3. 如果 `workspace_path` 为空,先创建或引导选择 workspace;不要要求用户判断“产品仓/工作区仓”。
33
+ 4. 如果命令不存在或环境没装好,读 `references/install.md`。
34
+ 5. 如果命令能跑但行为异常(workspace 不对、dashboard/sync 出错),读 `references/troubleshooting.md`。
35
+
36
+ ## 推荐工作流
37
+
38
+ ### 1. 创建或确认 workspace
39
+
40
+ | 场景 | 操作 |
41
+ |------|------|
42
+ | 新建 workspace | `skillreg workspace create ~/my-skills` |
43
+ | 已有 workspace | 确认它至少包含 `skills/` 目录 |
44
+ | 需要图形操作 | `skillreg dashboard open` |
45
+
46
+ 说明:
47
+ - workspace 切换主要在 dashboard 中完成。
48
+ - CLI 更适合做"确认配置、创建 workspace、启动 dashboard"这几个入口动作。
49
+
50
+ ### 2. 把本地 skill 纳入 workspace
51
+
52
+ 根据用户目标选择入口:
53
+
54
+ | 目标 | 入口 |
55
+ |------|------|
56
+ | 把当前目录或给定路径里的 skill 注册进 workspace | dashboard 的 import 流程或 `/api/registry/register` |
57
+ | 保留原目录内容并注册进 workspace | dashboard 的 import 流程 |
58
+ | 已有 registry 风格调用面 | `/api/registry/register` |
59
+ | 把 file skill 变成 repo/CLI 骨架 | `/api/registry/convert` |
60
+
61
+ 要点:
62
+ - `register` → 把 skill 放进 `skills/<name>/`
63
+ - `convert` → 把 `skills/<name>` 迁到 `repos/<name>-cli/skill/<name>/`,并生成基础 CLI repo 骨架
64
+ - “注册”默认不是注册当前代码仓库本身,而是注册其中包含 `SKILL.md` 的 skill 目录。
65
+ - 如果用户在任意业务项目里说“注册 skill”,agent 应主动定位该项目中的 `SKILL.md` 或询问具体 skill 目录。
66
+
67
+ ### 3. 同步到 agent 目标目录
68
+
69
+ 1. 在 dashboard 的 Sync 页面配置 targets。
70
+ 2. 执行同步,检查状态。
71
+
72
+ 状态含义(不要混淆):
73
+
74
+ | 状态 | 含义 |
75
+ |------|------|
76
+ | `synced` | workspace 和 target 一致 |
77
+ | `modified` | target 里有本地改动或差异 |
78
+ | `missing` | 配置里有这个 skill,但目标缺失 |
79
+ | `not-installed` | 该 target 当前还没有安装这个 skill |
80
+
81
+ ## agent 行为约束
82
+
83
+ - **先验证再行动**:先用 `skillreg config`、dashboard、API 或测试确认能力真的可用。
84
+ - **用户心智优先**:不要把"产品仓 / workspace 仓 / submodule 仓"作为用户必须回答的问题;这些只是 agent 内部判断路径和命令时的实现细节。
85
+ - **任意项目可触发**:用户可能在任何项目里说"注册 skill"。先找本地 `SKILL.md`,再把它注册进 `skillreg config` 指向的 workspace。
86
+ - **优先工具**:如果用户只是要完成一项实际操作,优先直接用 `skillreg` 完成,不要手工复制目录。
87
+ - **排障先诊断**:如果用户是在排障,先给出现状和根因,再决定是否需要改代码或改配置。
88
+
89
+ ## 何时读参考文档
90
+
91
+ | 问题类型 | 参考文档 |
92
+ |----------|----------|
93
+ | 安装、开发态启动、PATH/venv 问题 | `references/install.md` |
94
+ | dashboard 起不来、workspace 不对、sync 异常、skill 没注入、命令行为不符合预期 | `references/troubleshooting.md` |
@@ -0,0 +1,90 @@
1
+ # skillreg 安装与启动
2
+
3
+ 这个文档给 agent 一个稳定起点:当用户要“安装 skillreg”“把 skillreg CLI 跑起来”“确认本地环境能不能用”时,先按这里走。
4
+
5
+ ## 1. 先确认本机入口
6
+
7
+ 无论用户当前在哪个项目里,都先用下面两个命令识别 `skillreg` 是否可用、当前 workspace 指向哪里:
8
+
9
+ ```bash
10
+ pwd
11
+ skillreg config
12
+ ```
13
+
14
+ 如果 `skillreg` 命令不存在,再继续看下面的安装方式。
15
+
16
+ 不要要求用户先判断自己是在“产品仓”还是“workspace 仓”。用户通常只是在某个本地项目里想把一个包含 `SKILL.md` 的目录注册进 workspace。
17
+
18
+ ## 2. 面向用户的 CLI 安装
19
+
20
+ 如果目标是给本机装一个可直接调用的 `skillreg`,优先建议:
21
+
22
+ ```bash
23
+ uv tool install skillreg
24
+ ```
25
+
26
+ 安装后先确认:
27
+
28
+ ```bash
29
+ skillreg config
30
+ which skillreg
31
+ ```
32
+
33
+ 如果项目还没真正发布到公开包源,`uv tool install skillreg` 可能失败。这种情况下要明确告诉用户:
34
+
35
+ - 当前更可靠的是源码开发态安装
36
+ - 或者从本地仓库执行 `uv pip install -e .`
37
+
38
+ ## 3. 开发态安装
39
+
40
+ 当 agent 正在 `skillreg` 仓库里改代码时,优先用开发态安装:
41
+
42
+ ```bash
43
+ uv venv
44
+ uv pip install -e ".[dev]"
45
+ ```
46
+
47
+ 安装后可验证:
48
+
49
+ ```bash
50
+ skillreg config
51
+ skillreg dashboard open --no-browser
52
+ ```
53
+
54
+ 如果用户是在当前仓库里直接联调,也可以直接起服务:
55
+
56
+ ```bash
57
+ python -m uvicorn skillreg.server:app --host 127.0.0.1 --port 8787
58
+ ```
59
+
60
+ ## 4. 初始化最小可用闭环
61
+
62
+ 安装完成后,最小验证顺序:
63
+
64
+ ```bash
65
+ skillreg workspace create ~/my-skills
66
+ skillreg dashboard open --no-browser
67
+ ```
68
+
69
+ 然后确认:
70
+
71
+ - `~/.skillreg/config.json` 已生成
72
+ - `workspace_path` 指向新建 workspace
73
+ - workspace 下至少有 `skills/` 和 `repos/`
74
+
75
+ ## 5. 常用命令
76
+
77
+ ```bash
78
+ skillreg config
79
+ skillreg workspace create <path>
80
+ skillreg dashboard open
81
+ skillreg dashboard open --no-browser
82
+ ```
83
+
84
+ 当前 agent 应该把 CLI 看成:
85
+
86
+ - `config`:确认配置和当前 workspace
87
+ - `workspace create`:创建新的 workspace
88
+ - `dashboard open`:进入主要操作入口
89
+
90
+ workspace 切换、导入、sync target 管理,目前主要还是通过 dashboard 完成。
@@ -0,0 +1,143 @@
1
+ # skillreg 排障指南
2
+
3
+ 当用户反馈“装好了但不能用”“workspace 不对”“sync 看起来不可信”“dashboard 起不来”时,按这个顺序排查。
4
+
5
+ ## 1. 先看当前配置
6
+
7
+ 第一步永远先跑:
8
+
9
+ ```bash
10
+ skillreg config
11
+ ```
12
+
13
+ 重点看:
14
+
15
+ - 配置文件是不是 `~/.skillreg/config.json`
16
+ - `workspace_path` 有没有值
17
+ - 指向的路径是不是用户真正想操作的那个 workspace
18
+
19
+ 如果 `workspace_path` 为空或错误,很多问题都会是表象。
20
+
21
+ ## 2. workspace 问题
22
+
23
+ 典型现象:
24
+
25
+ - skills 列表为空
26
+ - dashboard 里看不到预期 skill
27
+ - sync 用的是错误仓库
28
+
29
+ 检查:
30
+
31
+ ```bash
32
+ ls <workspace>
33
+ ls <workspace>/skills
34
+ ```
35
+
36
+ 确认:
37
+
38
+ - workspace 路径存在
39
+ - 它是目录,不是文件
40
+ - 至少包含 `skills/`
41
+
42
+ 如果用户已经有 workspace,但 CLI 当前没指向它:
43
+
44
+ - 打开 dashboard
45
+ - 在 header 中切换到正确 workspace
46
+
47
+ ## 3. dashboard 起不来
48
+
49
+ 先区分是“命令起不来”,还是“服务起来了但页面打不开”。
50
+
51
+ ### 命令起不来
52
+
53
+ 优先检查:
54
+
55
+ ```bash
56
+ which skillreg
57
+ skillreg config
58
+ ```
59
+
60
+ 如果 `skillreg` 不存在,回到 `install.md` 重新安装。
61
+
62
+ ### 服务起来但网页打不开
63
+
64
+ 可直接用开发命令确认后端是否可起:
65
+
66
+ ```bash
67
+ python -m uvicorn skillreg.server:app --host 127.0.0.1 --port 8787
68
+ ```
69
+
70
+ 再检查:
71
+
72
+ - 端口是否被占用
73
+ - `dashboard/dist` 是否存在
74
+ - 浏览器访问的是不是正确端口
75
+
76
+ ## 4. register / convert 异常
77
+
78
+ ### register 失败
79
+
80
+ 优先检查源目录:
81
+
82
+ - 是否存在 `SKILL.md`
83
+ - frontmatter 里是否有 `name`
84
+ - `name` 是否满足 `[A-Za-z0-9][A-Za-z0-9_-]*`
85
+
86
+ 常见冲突:
87
+
88
+ - `skills/<name>` 已存在
89
+ - 没有传 `force=true`
90
+
91
+ ### convert 失败
92
+
93
+ 优先检查:
94
+
95
+ - `skills/<name>/SKILL.md` 是否存在
96
+ - `repos/<name>-cli` 是否已经存在
97
+
98
+ 语义上要记住:
99
+
100
+ - `convert` 是迁移,不是复制
101
+ - 成功后原来的 `skills/<name>` 会移入 `repos/<name>-cli/skill/<name>/`
102
+
103
+ ## 5. sync 状态不对
104
+
105
+ 先别急着改代码,先确认状态解释有没有搞错:
106
+
107
+ - `synced`:一致
108
+ - `modified`:目标侧有差异
109
+ - `missing`:配置/期望存在,但目标缺失
110
+ - `not-installed`:当前 target 还没安装这个 skill
111
+
112
+ 如果用户说“怎么全是 missing / not-installed”,先检查:
113
+
114
+ - 当前 target 路径是不是对的
115
+ - target 目录是否真的存在
116
+ - workspace 当前选中的 skill 是否在目标里出现
117
+
118
+ ## 6. builtin skill 没注入
119
+
120
+ `skillreg` 会把 builtin `skillreg-skill` 注入到:
121
+
122
+ ```text
123
+ <workspace>/.skillreg/builtin/skillreg-skill/
124
+ ```
125
+
126
+ 如果用户说看不到:
127
+
128
+ 1. 先确认当前 workspace 是对的
129
+ 2. 再检查这个目录是否存在
130
+ 3. 如果是开发态,确认源码里 `src/skillreg/builtin/skillreg-skill/` 存在
131
+ 4. 如果是安装态,确认 wheel 中包含 builtin 目录
132
+
133
+ ## 7. 什么时候该改代码
134
+
135
+ 只有在下面这些都确认之后,再进入代码修复:
136
+
137
+ - 配置路径正确
138
+ - workspace 正确
139
+ - 输入目录和 skill 元数据合法
140
+ - target 路径和目标状态核对过
141
+ - CLI 安装和 PATH 没问题
142
+
143
+ 也就是说,先排除“环境 / 配置 / 路径 / 术语理解”问题,再动实现。
skillreg/cli.py ADDED
@@ -0,0 +1,137 @@
1
+ """skillreg CLI entry point.
2
+
3
+ Minimal command set for M1 (issue #02):
4
+
5
+ - ``skillreg config`` — print config status (auto-creates config).
6
+ - ``skillreg dashboard open`` — start the FastAPI backend + open a browser.
7
+
8
+ Built with `click`. The entry point ``skillreg = "skillreg.cli:main"`` is
9
+ declared in ``pyproject.toml``.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import threading
15
+ import time
16
+ import webbrowser
17
+ from typing import Optional, Sequence
18
+
19
+ import click
20
+ import uvicorn
21
+
22
+ from . import __version__
23
+ from .config import CONFIG_FILE, load_config
24
+
25
+ DEFAULT_HOST = "127.0.0.1"
26
+ DEFAULT_PORT = 8787
27
+
28
+
29
+ def _format_csv(values: Sequence[str]) -> str:
30
+ return ", ".join(values) if values else "(none)"
31
+
32
+
33
+ def _echo_workspace_summary(*, heading: str = "skillreg context") -> None:
34
+ """Print the current workspace context after user-facing commands."""
35
+ cfg = load_config()
36
+ click.echo(heading)
37
+ click.echo(f" config file : {CONFIG_FILE}")
38
+ click.echo(f" current workspace : {cfg.workspace_path or '(not configured)'}")
39
+ click.echo(f" sync targets : {_format_csv(cfg.targets)}")
40
+
41
+
42
+ @click.group()
43
+ @click.version_option(version=__version__, package_name="skillreg")
44
+ def cli() -> None:
45
+ """skillreg — skill registry control plane."""
46
+
47
+
48
+ @cli.command()
49
+ def config() -> None:
50
+ """Print skillreg configuration status.
51
+
52
+ Auto-creates ``~/.skillreg/config.json`` with the default empty structure
53
+ on first run.
54
+ """
55
+ cfg = load_config()
56
+ click.echo("skillreg config")
57
+ click.echo(f" config file : {CONFIG_FILE}")
58
+ click.echo(f" workspace : {cfg.workspace_path or '(not configured)'}")
59
+ click.echo(
60
+ f" targets : "
61
+ f"{', '.join(cfg.targets) if cfg.targets else '(none)'}"
62
+ )
63
+ click.echo(
64
+ f" agents : "
65
+ f"{', '.join(cfg.agents.keys()) if cfg.agents else '(none)'}"
66
+ )
67
+
68
+
69
+ @cli.group()
70
+ def dashboard() -> None:
71
+ """Manage the skillreg dashboard."""
72
+
73
+
74
+ @cli.group()
75
+ def workspace() -> None:
76
+ """Manage skillreg workspaces."""
77
+
78
+
79
+ @workspace.command("create")
80
+ @click.argument("location", type=click.Path())
81
+ def create_workspace(location: str) -> None:
82
+ """Create a new workspace at LOCATION.
83
+
84
+ Initializes a git repo with skills/ and repos/ directories,
85
+ writes .gitignore and README, and updates the config pointer.
86
+ """
87
+ from .services.importer import create_workspace as _create
88
+
89
+ try:
90
+ result = _create(location)
91
+ click.echo(f"✓ Workspace created at {result['workspace_path']}")
92
+ click.echo(f" git init : {'✓' if result['has_git'] else '✗'}")
93
+ click.echo(f" skills/ : {'✓' if result['has_skills_dir'] else '✗'}")
94
+ click.echo(f" repos/ : {'✓' if result['has_repos_dir'] else '✗'}")
95
+ _echo_workspace_summary(heading="skillreg context after workspace create")
96
+ except ValueError as e:
97
+ click.echo(f"✗ {e}", err=True)
98
+ raise SystemExit(1)
99
+
100
+
101
+ @dashboard.command("open")
102
+ @click.option("--host", default=DEFAULT_HOST, show_default=True, help="Bind host.")
103
+ @click.option("--port", default=DEFAULT_PORT, show_default=True, type=int, help="Bind port.")
104
+ @click.option(
105
+ "--no-browser",
106
+ is_flag=True,
107
+ default=False,
108
+ help="Do not open a browser; just start the server.",
109
+ )
110
+ def open_dashboard(host: str, port: int, no_browser: bool) -> None:
111
+ """Start the FastAPI backend and open the dashboard in a browser."""
112
+ url = f"http://{host}:{port}"
113
+ if not no_browser:
114
+ # Delay browser launch so uvicorn has a moment to bind the socket.
115
+ threading.Thread(
116
+ target=lambda: (time.sleep(1.0), webbrowser.open(url)),
117
+ daemon=True,
118
+ ).start()
119
+ click.echo(f"skillreg backend starting at {url}")
120
+ _echo_workspace_summary(heading="skillreg context for dashboard")
121
+ click.echo(" press Ctrl+C to stop.")
122
+ uvicorn.run(
123
+ "skillreg.server:app",
124
+ host=host,
125
+ port=port,
126
+ log_config=None,
127
+ )
128
+
129
+
130
+ def main(argv: Optional[Sequence[str]] = None) -> int:
131
+ """Entry point for the ``skillreg`` console script."""
132
+ cli(args=list(argv) if argv is not None else None, standalone_mode=True)
133
+ return 0
134
+
135
+
136
+ if __name__ == "__main__":
137
+ main()
skillreg/config.py ADDED
@@ -0,0 +1,81 @@
1
+ """Configuration management for skillreg.
2
+
3
+ Per PRD §2.3, all skillreg-local state lives in ``~/.skillreg/config.json``:
4
+
5
+ - ``workspace_path`` — pointer to the workspace repo (skills storage). The
6
+ workspace itself only holds ``skills/`` + ``repos/``; it no longer carries
7
+ ``sync-skills.json`` or ``infra/``.
8
+ - ``targets`` — install targets (e.g. ``~/.claude/skills``).
9
+ - ``agents`` — agent conventions map (claude / codebuddy / codex …).
10
+
11
+ Exclude rules and manifest settings stay code-internal (not in this file).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ from pathlib import Path
18
+ from typing import Any, Dict, List, Optional
19
+
20
+ from pydantic import BaseModel, Field
21
+
22
+ # --- locations -------------------------------------------------------------
23
+
24
+ CONFIG_DIR: Path = Path.home() / ".skillreg"
25
+ CONFIG_FILE: Path = CONFIG_DIR / "config.json"
26
+
27
+
28
+ # --- model -----------------------------------------------------------------
29
+
30
+
31
+ class SkillregConfig(BaseModel):
32
+ """Schema for ``~/.skillreg/config.json``."""
33
+
34
+ workspace_path: Optional[str] = Field(
35
+ default=None,
36
+ description="Absolute path to the workspace repo (skills storage). "
37
+ "null until the user configures it.",
38
+ )
39
+ targets: List[str] = Field(
40
+ default_factory=list,
41
+ description="Install targets (agent skill dirs), e.g. ~/.claude/skills.",
42
+ )
43
+ agents: Dict[str, Any] = Field(
44
+ default_factory=dict,
45
+ description="Agent conventions map. Keys are agent names "
46
+ "(claude / codebuddy / codex …); values are agent-specific convention "
47
+ "records (structure expanded in later issues).",
48
+ )
49
+
50
+
51
+ def default_config() -> SkillregConfig:
52
+ """Return a fresh empty config with all expected fields."""
53
+ return SkillregConfig()
54
+
55
+
56
+ # --- I/O -------------------------------------------------------------------
57
+
58
+
59
+ def load_config(path: Optional[Path] = None) -> SkillregConfig:
60
+ """Load config from ``path`` (default: ``~/.skillreg/config.json``).
61
+
62
+ If the file does not exist, it is created with the default empty structure
63
+ and returned. The config directory is created on demand.
64
+ """
65
+ if path is None:
66
+ path = CONFIG_FILE
67
+ if not path.exists():
68
+ cfg = default_config()
69
+ save_config(cfg, path)
70
+ return cfg
71
+ data = json.loads(path.read_text(encoding="utf-8"))
72
+ # Coerce into the schema; unknown keys are dropped, missing keys defaulted.
73
+ return SkillregConfig.model_validate(data)
74
+
75
+
76
+ def save_config(cfg: SkillregConfig, path: Optional[Path] = None) -> None:
77
+ """Persist ``cfg`` to ``path`` (default: ``~/.skillreg/config.json``)."""
78
+ if path is None:
79
+ path = CONFIG_FILE
80
+ path.parent.mkdir(parents=True, exist_ok=True)
81
+ path.write_text(cfg.model_dump_json(indent=2), encoding="utf-8")
@@ -0,0 +1,148 @@
1
+ """FastAPI backend for skillreg.
2
+
3
+ Exposes API routes under ``/api`` and (when present) serves the built dashboard
4
+ static files via ``StaticFiles``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+ from fastapi import FastAPI, HTTPException
14
+ from fastapi.responses import FileResponse
15
+
16
+ from .. import __version__
17
+
18
+
19
+ def dashboard_dir() -> Optional[Path]:
20
+ """Locate the dashboard static directory."""
21
+ env = os.environ.get("SKILLREG_DASHBOARD_DIR")
22
+ if env:
23
+ p = Path(env)
24
+ if p.is_dir():
25
+ return p
26
+ dashboard_root = Path(__file__).resolve().parents[3] / "dashboard"
27
+ dist = dashboard_root / "dist"
28
+ if dist.is_dir():
29
+ return dist
30
+ if dashboard_root.is_dir():
31
+ return dashboard_root
32
+ return None
33
+
34
+
35
+ def create_app() -> FastAPI:
36
+ """Build the FastAPI application with all routes mounted."""
37
+ app = FastAPI(title="skillreg", version=__version__)
38
+
39
+ # Inject self-skill on startup if workspace is configured
40
+ from ..config import load_config
41
+ from ..services.self_skill import init_self_skill
42
+
43
+ cfg = load_config()
44
+ if cfg.workspace_path:
45
+ try:
46
+ init_self_skill(cfg.workspace_path)
47
+ except Exception:
48
+ pass # Non-fatal: dashboard still works without self-skill
49
+
50
+ # Register route modules
51
+ from .health import router as health_router
52
+
53
+ app.include_router(health_router)
54
+
55
+ # Skills / registry / sync routes (built progressively)
56
+ try:
57
+ from .skills import router as skills_router
58
+ app.include_router(skills_router)
59
+ except ImportError:
60
+ pass
61
+
62
+ try:
63
+ from .sync import router as sync_router
64
+ app.include_router(sync_router)
65
+ except ImportError:
66
+ pass
67
+
68
+ try:
69
+ from .registry import router as registry_router
70
+ app.include_router(registry_router)
71
+ except ImportError:
72
+ pass
73
+
74
+ try:
75
+ from .files import router as files_router
76
+ app.include_router(files_router)
77
+ except ImportError:
78
+ pass
79
+
80
+ try:
81
+ from .git import router as git_router
82
+ app.include_router(git_router)
83
+ except ImportError:
84
+ pass
85
+
86
+ try:
87
+ from .submodules import router as submodules_router
88
+ app.include_router(submodules_router)
89
+ except ImportError:
90
+ pass
91
+
92
+ try:
93
+ from .hooks import router as hooks_router
94
+ app.include_router(hooks_router)
95
+ except ImportError:
96
+ pass
97
+
98
+ try:
99
+ from .import_ import router as import_router
100
+ app.include_router(import_router)
101
+ except ImportError:
102
+ pass
103
+
104
+ try:
105
+ from .compat import router as compat_router
106
+ app.include_router(compat_router)
107
+ except ImportError:
108
+ pass
109
+
110
+ try:
111
+ from .workspace import router as workspace_router
112
+ app.include_router(workspace_router)
113
+ except ImportError:
114
+ pass
115
+
116
+ # Dashboard static serving (SPA fallback, after /api routes)
117
+ _d = dashboard_dir()
118
+ if _d is not None:
119
+ index_file = _d / "index.html"
120
+ if index_file.is_file():
121
+ @app.get("/assets/{asset_path:path}", include_in_schema=False)
122
+ def dashboard_assets(asset_path: str):
123
+ file_path = (_d / "assets" / asset_path).resolve()
124
+ assets_root = (_d / "assets").resolve()
125
+ try:
126
+ file_path.relative_to(assets_root)
127
+ except ValueError as exc:
128
+ raise HTTPException(404) from exc
129
+ if not file_path.is_file():
130
+ raise HTTPException(404)
131
+ return FileResponse(file_path)
132
+
133
+ @app.get("/{full_path:path}", include_in_schema=False)
134
+ def dashboard_spa_fallback(full_path: str):
135
+ request_path = (_d / full_path).resolve()
136
+ try:
137
+ request_path.relative_to(_d.resolve())
138
+ except ValueError:
139
+ return FileResponse(index_file)
140
+ if full_path and request_path.is_file():
141
+ return FileResponse(request_path)
142
+ return FileResponse(index_file)
143
+
144
+ return app
145
+
146
+
147
+ # Module-level app for uvicorn (skillreg.server:app)
148
+ app = create_app()