Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
custom-skills
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
eazy-template
custom-skills
Commits
489af456
Commit
489af456
authored
Mar 21, 2026
by
yuxiaodi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
init
parent
b1c588a4
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
286 additions
and
19 deletions
+286
-19
README.md
README.md
+1
-1
app.py
app.py
+285
-18
No files found.
README.md
View file @
489af456
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
这是一个基于 Python
`FastAPI`
的演示项目,提供“智能体技能开发模板”的最小服务能力:
这是一个基于 Python
`FastAPI`
的演示项目,提供“智能体技能开发模板”的最小服务能力:
1.
用
`GET /`
返回
开发引导文案(包含可下载地址
)
1.
用
`GET /`
返回
前端页面(开发引导说明、默认下载区、列出
`skills/`
下各
`.skill`
的独立下载与复制链接
)
2.
用
`GET /download`
下载打包好的
`.skill`
压缩包
2.
用
`GET /download`
下载打包好的
`.skill`
压缩包
## 目录结构
## 目录结构
...
...
app.py
View file @
489af456
from
fastapi
import
FastAPI
,
HTTPException
,
Query
,
Request
import
html
from
fastapi.responses
import
FileResponse
from
pathlib
import
Path
import
os
import
os
import
re
import
re
from
typing
import
Optional
from
pathlib
import
Path
from
typing
import
List
,
Optional
from
urllib.parse
import
quote
from
fastapi
import
FastAPI
,
HTTPException
,
Query
,
Request
from
fastapi.responses
import
FileResponse
,
HTMLResponse
import
uvicorn
import
uvicorn
app
=
FastAPI
()
app
=
FastAPI
()
...
@@ -12,17 +15,6 @@ BASE_DIR = Path(__file__).resolve().parent
...
@@ -12,17 +15,6 @@ BASE_DIR = Path(__file__).resolve().parent
SKILLS_DIR
=
BASE_DIR
/
"skills"
SKILLS_DIR
=
BASE_DIR
/
"skills"
def
_build_welcome_message
(
download_url
:
str
)
->
str
:
return
(
"欢迎来到 Eazybot 自定义技能开发模板。"
"您可以在IDE的对话框中描述你想开发的技能内容,让Eazy Develop帮您开发。"
"当然您也可以自己在 IDE 中的 `skills/` 目录下开发您的自定义技能并在开发完成后,使用 `make skill` 命令打包。"
"部署后,你可以访问: "
f
"{download_url} (该地址在开发过程中为临时链接,部署后会展示稳定链接)"
"下载打包好的 `.skill` 压缩包;也可以直接把该链接交给 Eazybot,让其自行安装技能。"
)
def
_build_public_base_url
(
request
:
Request
)
->
str
:
def
_build_public_base_url
(
request
:
Request
)
->
str
:
host
=
request
.
headers
.
get
(
"host"
,
""
)
.
strip
()
host
=
request
.
headers
.
get
(
"host"
,
""
)
.
strip
()
if
host
:
if
host
:
...
@@ -30,10 +22,285 @@ def _build_public_base_url(request: Request) -> str:
...
@@ -30,10 +22,285 @@ def _build_public_base_url(request: Request) -> str:
return
"https://localhost"
return
"https://localhost"
@
app
.
get
(
"/"
)
def
_list_skill_packages
()
->
List
[
dict
]:
"""列出 skills 目录下所有 .skill 包(文件名不含路径)。"""
if
not
SKILLS_DIR
.
is_dir
():
return
[]
files
=
sorted
(
SKILLS_DIR
.
glob
(
"*.skill"
),
key
=
lambda
p
:
p
.
name
.
lower
())
return
[{
"filename"
:
p
.
name
,
"stem"
:
p
.
stem
}
for
p
in
files
if
p
.
is_file
()]
def
_render_home_html
(
request
:
Request
)
->
str
:
base
=
_build_public_base_url
(
request
)
default_download
=
f
"{base}/download"
skills
=
_list_skill_packages
()
rows_html
=
""
for
item
in
skills
:
fname
=
item
[
"filename"
]
stem
=
item
[
"stem"
]
# 下载接口接受 stem 或带 .skill 的文件名
dl_url
=
f
"{base}/download?skill={quote(stem, safe='')}"
safe_fname
=
html
.
escape
(
fname
)
safe_url
=
html
.
escape
(
dl_url
,
quote
=
True
)
rows_html
+=
f
"""
<tr class="skill-row">
<td class="skill-name"><code>{safe_fname}</code></td>
<td class="skill-actions">
<a class="btn btn-primary" href="{safe_url}" download>下载</a>
<button type="button" class="btn btn-secondary copy-btn" data-url="{safe_url}">复制链接</button>
</td>
</tr>"""
if
not
rows_html
:
rows_html
=
"""
<tr>
<td colspan="2" class="empty-hint">
暂无已打包的 <code>.skill</code> 文件。请在 <code>skills/</code> 下开发技能并执行打包后刷新本页。
</td>
</tr>"""
safe_default
=
html
.
escape
(
default_download
,
quote
=
True
)
return
f
"""<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Eazybot 自定义技能模板</title>
<style>
:root {{
--bg: #0f1419;
--surface: #1a2332;
--border: #2d3a4d;
--text: #e7ecf3;
--muted: #8b9cb3;
--accent: #3d8bfd;
--accent-hover: #5ba3ff;
--success: #3ecf8e;
}}
* {{ box-sizing: border-box; }}
body {{
margin: 0;
min-height: 100vh;
font-family: "SF Pro Text", "Segoe UI", system-ui, sans-serif;
background: radial-gradient(ellipse 120
% 80%
at 50
% -20%
, #1e3a5f 0
%
, var(--bg) 55
%
);
color: var(--text);
line-height: 1.6;
}}
.wrap {{
max-width: 720px;
margin: 0 auto;
padding: 2.5rem 1.25rem 3rem;
}}
.card {{
background: var(--surface);
border: 1px solid var(--border);
border-radius: 16px;
padding: 1.75rem 1.5rem;
box-shadow: 0 12px 40px rgba(0,0,0,.35);
}}
h1 {{
font-size: 1.5rem;
font-weight: 600;
margin: 0 0 0.5rem;
letter-spacing: -0.02em;
}}
.lead {{
color: var(--muted);
font-size: 0.95rem;
margin-bottom: 1.25rem;
}}
.intro p {{
margin: 0 0 0.85rem;
font-size: 0.95rem;
}}
.intro code {{
background: rgba(0,0,0,.25);
padding: 0.12em 0.35em;
border-radius: 6px;
font-size: 0.88em;
}}
.default-dl {{
margin: 1.25rem 0 1.5rem;
padding: 1rem;
background: rgba(0,0,0,.2);
border-radius: 12px;
border: 1px solid var(--border);
}}
.default-dl label {{
display: block;
font-size: 0.8rem;
color: var(--muted);
margin-bottom: 0.35rem;
}}
.url-row {{
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: center;
}}
.url-row input {{
flex: 1;
min-width: 200px;
padding: 0.5rem 0.65rem;
border-radius: 8px;
border: 1px solid var(--border);
background: var(--bg);
color: var(--text);
font-size: 0.85rem;
}}
.btn {{
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.45rem 0.9rem;
border-radius: 8px;
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
border: none;
text-decoration: none;
transition: background .15s, transform .1s;
}}
.btn:active {{ transform: scale(0.98); }}
.btn-primary {{
background: var(--accent);
color: #fff;
}}
.btn-primary:hover {{ background: var(--accent-hover); }}
.btn-secondary {{
background: transparent;
color: var(--text);
border: 1px solid var(--border);
}}
.btn-secondary:hover {{ background: rgba(255,255,255,.06); }}
h2 {{
font-size: 1.05rem;
font-weight: 600;
margin: 0 0 0.75rem;
color: var(--muted);
}}
table {{
width: 100
%
;
border-collapse: collapse;
font-size: 0.9rem;
}}
th, td {{
text-align: left;
padding: 0.65rem 0.5rem;
border-bottom: 1px solid var(--border);
}}
th {{
color: var(--muted);
font-weight: 500;
font-size: 0.8rem;
}}
.skill-name code {{
font-size: 0.88rem;
color: var(--success);
}}
.skill-actions {{
white-space: nowrap;
text-align: right;
}}
.skill-actions .btn {{ margin-left: 0.35rem; }}
.empty-hint {{
color: var(--muted);
font-size: 0.9rem;
padding: 1rem 0 !important;
}}
.toast {{
position: fixed;
bottom: 1.25rem;
left: 50
%
;
transform: translateX(-50
%
) translateY(100px);
opacity: 0;
background: var(--surface);
border: 1px solid var(--border);
color: var(--text);
padding: 0.6rem 1rem;
border-radius: 10px;
font-size: 0.85rem;
pointer-events: none;
transition: opacity .2s, transform .2s;
z-index: 100;
}}
.toast.show {{
opacity: 1;
transform: translateX(-50
%
) translateY(0);
}}
</style>
</head>
<body>
<div class="wrap">
<div class="card">
<h1>Eazybot 自定义技能开发模板</h1>
<p class="lead">在 IDE 中开发技能、打包后直接复制链接交给 Eazybot 安装或自行下载保存</p>
<div class="intro">
<p>您可以在 IDE 对话框中描述想开发的技能,由 <strong>Eazy Develop</strong> 协助完成;也可自行在 <code>skills/</code> 目录下开发,并使用 <code>make skill</code> 打包。</p>
<p>部署后可通过下方链接下载 <code>.skill</code> 压缩包,或将链接提供给 <strong>Eazybot</strong> 自动安装。(开发环境中链接可能为临时地址,部署后为稳定链接。)</p>
</div>
<div class="default-dl">
<label>默认下载(第一个 .skill)</label>
<div class="url-row">
<input type="text" readonly value="{safe_default}" id="default-url" />
<a class="btn btn-primary" href="{safe_default}" download>下载</a>
<button type="button" class="btn btn-secondary copy-btn" data-url="{safe_default}">复制链接</button>
</div>
</div>
<h2>已打包的技能</h2>
<table>
<thead>
<tr>
<th>文件名</th>
<th style="text-align:right">操作</th>
</tr>
</thead>
<tbody>
{rows_html}
</tbody>
</table>
</div>
</div>
<div class="toast" id="toast" role="status">已复制到剪贴板</div>
<script>
(function () {{
var toast = document.getElementById("toast");
function showToast() {{
toast.classList.add("show");
clearTimeout(showToast._t);
showToast._t = setTimeout(function () {{ toast.classList.remove("show"); }}, 2000);
}}
document.querySelectorAll(".copy-btn").forEach(function (btn) {{
btn.addEventListener("click", function () {{
var url = btn.getAttribute("data-url");
if (navigator.clipboard && navigator.clipboard.writeText) {{
navigator.clipboard.writeText(url).then(showToast).catch(function () {{
fallbackCopy(url);
}});
}} else {{
fallbackCopy(url);
}}
}});
}});
function fallbackCopy(text) {{
var input = document.createElement("input");
input.value = text;
document.body.appendChild(input);
input.select();
try {{ document.execCommand("copy"); showToast(); }} catch (e) {{}}
document.body.removeChild(input);
}}
}})();
</script>
</body>
</html>"""
@
app
.
get
(
"/"
,
response_class
=
HTMLResponse
)
def
read_root
(
request
:
Request
):
def
read_root
(
request
:
Request
):
download_url
=
f
"{_build_public_base_url(request)}/download"
return
HTMLResponse
(
content
=
_render_home_html
(
request
))
return
_build_welcome_message
(
download_url
)
@
app
.
get
(
"/download"
)
@
app
.
get
(
"/download"
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment