SKILL.md
5.3 KB查看排版效果| 1 | --- |
| 2 | name: lingzhan-publish |
| 3 | description: 把一个技能文件夹脱敏、打包并发布到灵栈(lingzhanskill.com)。用户说「把这个技能发到灵栈」「更新 XX 技能」「发布技能」时使用;涵盖隐私检查、打包、上传、等待审核全流程。 |
| 4 | --- |
| 5 | |
| 6 | # 发技能到灵栈 |
| 7 | |
| 8 | 能做什么:把一个本地技能文件夹检查指纹、脱敏打包、通过灵栈的 Agent 接口发布,并把公开链接回报给用户。 |
| 9 | 带脚本:不单独带,正文里有可直接复制的 Python / curl 片段。 |
| 10 | 联不联网:要,全程访问 https://lingzhanskill.com 。 |
| 11 | 写文件:会写临时 ZIP 和脱敏副本(放在临时目录),不修改作者原始技能文件夹。 |
| 12 | |
| 13 | 官方说明(每次先读一遍,可能更新):https://lingzhanskill.com/publish.md |
| 14 | |
| 15 | ## 前置 |
| 16 | |
| 17 | - 发布密钥在 `~/.lingzhan/agent-token`(`lzk_` 开头)。已有就直接用,不要重复走设备授权。 |
| 18 | - 校验密钥:`curl -s -H "Authorization: Bearer $(cat ~/.lingzhan/agent-token)" https://lingzhanskill.com/api/v1/agent/me`,返回 `owner` 说明可用。 |
| 19 | - 没有密钥时才走设备授权:`POST /api/v1/agent/authorize` → 让作者在 `verification_url` 核对确认码并点允许 → 轮询 `/api/v1/agent/token` → 把 token 存到 `~/.lingzhan/agent-token`(权限只给当前用户)。 |
| 20 | |
| 21 | ## 七步 |
| 22 | |
| 23 | 1. **隐私扫描**(必做,发布后公开且收不回) |
| 24 | 在待发文件里找:绝对路径与用户名(`C:\Users\<名字>\`、`/home/<名字>/`)、内网地址(`10.`、`192.168.`、`172.16~31`、`.internal`/`.corp`/`.local`)、密钥凭据(`sk-`、`ghp_`、`AKIA`、`BEGIN PRIVATE`、`password=`、`.env`)、手机号邮箱等个人信息。 |
| 25 | 把「文件:行号 + 该行内容(密钥只显示前 4 位)」列给作者,由作者决定。 |
| 26 | 2. **脱敏打包**(推荐,绝不改原文件夹) |
| 27 | 复制到临时目录再替换:本机绝对路径 → `~/.dsh-workspace` 之类占位符;解释器绝对路径 → `python`;本地服务端口 → `127.0.0.1:<端口>`。 |
| 28 | 在 SKILL.md 开头补一句「依赖本地 XX,路径按自己环境替换」。 |
| 29 | ZIP 要求:条目路径用 `/`、不能有 `./`、`..`、绝对路径;最多 500 文件、≤20MB、解压≤100MB;ZIP 不要放进技能文件夹。 |
| 30 | 3. **确认**:slug(3~32 位小写字母数字连字符,**发布后不可改**)、显示名、一句话描述——念给作者,明确同意再发。 |
| 31 | 4. **建版本**:`POST /api/v1/agent/releases`,body `{"slug","size","name","summary","category","changes"}`;返回 `put_url`(15 分钟临时凭据,不保存不外传)、`complete_url`、`version`。 |
| 32 | 5. **上传**:`PUT put_url`,`Content-Type: application/zip`,body 为 ZIP 字节。 |
| 33 | 6. **完成**:`POST complete_url`(空 body),返回 `stage: checking`。 |
| 34 | 7. **轮询**:`GET /api/v1/agent/releases/<slug>/<version>`,每 5 秒、最多 3 分钟。`published` → 把 `url` 给作者;`blocked`/`returned` → 按 `problems` 改完重发;`in_review`/`closed` → 让作者看 `manage_url`。 |
| 35 | |
| 36 | ## 坑 |
| 37 | |
| 38 | - **Cloudflare 1010**:Python urllib 默认 UA 会被 403。所有请求都要带 |
| 39 | `User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0 Safari/537.36` |
| 40 | 和 `Accept: application/json`。 |
| 41 | - 中文请求体必须 UTF-8 字节:`json.dumps(obj, ensure_ascii=False).encode('utf-8')`,Content-Type 带 `charset=utf-8`。 |
| 42 | - slug 发布后不能改;名称、简介可以改。 |
| 43 | - 已公开的版本不能修改,要改就发新版本号(不填 `version` 会自动 +1)。 |
| 44 | - 用户没看过内容、没同意之前不要发布;不要把 token、`put_url` 贴进聊天或写进技能包。 |
| 45 | - 失败重试是安全的(PUT 同一个 zip、complete 同一版本不会重复发布)。 |
| 46 | |
| 47 | ## Python 片段 |
| 48 | |
| 49 | ```python |
| 50 | import json, os, time, urllib.request |
| 51 | T = open(os.path.expanduser('~/.lingzhan/agent-token')).read().strip() |
| 52 | UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0 Safari/537.36' |
| 53 | H = {'Authorization': 'Bearer ' + T, 'User-Agent': UA, 'Accept': 'application/json', |
| 54 | 'Content-Type': 'application/json; charset=utf-8'} |
| 55 | |
| 56 | zip_path = 'skill.zip' |
| 57 | body = json.dumps({"slug": "my-skill", "size": os.path.getsize(zip_path), |
| 58 | "name": "技能名", "summary": "一句话说明", "changes": ["首个版本"]}, |
| 59 | ensure_ascii=False).encode() |
| 60 | r = json.loads(urllib.request.urlopen(urllib.request.Request( |
| 61 | 'https://lingzhanskill.com/api/v1/agent/releases', data=body, headers=H, method='POST')).read()) |
| 62 | |
| 63 | up = urllib.request.Request(r['put_url'], data=open(zip_path, 'rb').read(), |
| 64 | headers={**(r.get('headers') or {}), 'Content-Type': 'application/zip'}, |
| 65 | method='PUT') |
| 66 | urllib.request.urlopen(up, timeout=120) |
| 67 | |
| 68 | urllib.request.urlopen(urllib.request.Request( |
| 69 | r['complete_url'], data=b'', headers={'Authorization': 'Bearer ' + T, 'User-Agent': UA}, method='POST')) |
| 70 | |
| 71 | for _ in range(36): |
| 72 | d = json.loads(urllib.request.urlopen(urllib.request.Request( |
| 73 | f"https://lingzhanskill.com/api/v1/agent/releases/{r['slug']}/{r['version']}", |
| 74 | headers={'Authorization': 'Bearer ' + T, 'User-Agent': UA})).read()) |
| 75 | if d['stage'] in ('published', 'blocked', 'returned', 'closed', 'in_review'): |
| 76 | break |
| 77 | time.sleep(5) |
| 78 | print(d) |
| 79 | ``` |
文件内容是平台检查时读出的文本,只供查看。平台不会运行包里的任何脚本或程序。

