gen-service-skills.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. #!/usr/bin/env python3
  2. """批量生成 fmode-skills 服务层子技能 ESM 结构(统一规范)"""
  3. import os, textwrap
  4. BASE = '/opt/data/projects/fmode-skills/skills'
  5. skills = [
  6. {
  7. "dir": "fmode-account",
  8. "name": "fmode-account",
  9. "slug": "fmode-skill-fmode-account",
  10. "version": "0.1.0",
  11. "title": "Fmode 账号(手机验证码注册/登录)",
  12. "summary": "Fmode 账号注册登录:手机验证码 → sessionToken → 企业开通。数字分身初始化第一步。",
  13. "triggers": "注册、登录、验证码、sessionToken、账号、手机号、数字分身初始化、fmod账号",
  14. "endpoints": [
  15. ("POST https://server.fmode.cn/api/apig/message", "发送验证码 {mobile,company}"),
  16. ("GET https://server.fmode.cn/api/fmode/mobile", "验证码登录 {mobile,code} → data.token/data.newapiToken"),
  17. ("GET https://server.fmode.cn/parse/users/me", "校验会话 Bearer token"),
  18. ],
  19. "keypoints": ["token 走受保护 profile(0700),不进聊天/project/Git/日志", "验证码只用于立即登录请求", "Windows 用当前用户目录,POSIX 0700/0600"],
  20. },
  21. {
  22. "dir": "fmode-enterprise",
  23. "name": "fmode-enterprise",
  24. "slug": "fmode-skill-fmode-enterprise",
  25. "version": "0.1.0",
  26. "title": "Fmode 企业(开通状态/独立部署)",
  27. "summary": "Fmode 企业状态:查询订阅、申请独立部署、轮询开通。数字分身企业开通。",
  28. "triggers": "企业、开通、订阅、enterprise、Company、独立部署、sub、trial",
  29. "endpoints": [
  30. ("POST https://server.fmode.cn/api/fmode/enterprise/subscription/status", "查企业状态 {sessionToken}"),
  31. ("POST https://server.fmode.cn/api/fmode/enterprise/subscription/trial-standalone", "申请独立部署 {name,sessionToken}"),
  32. ("POST https://server.fmode.cn/api/fmode/enterprise/subscription/provision/retry", "重试开通(仅失败后)"),
  33. ],
  34. "keypoints": ["不在企业时问人名(非企业名)再trial-standalone", "仅 provision pending/running 时轮询状态", "绝不索要数据库URI"],
  35. },
  36. {
  37. "dir": "fmode-cloud-functions",
  38. "name": "fmode-cloud-functions",
  39. "slug": "fmode-skill-fmode-cloud-functions",
  40. "version": "0.1.0",
  41. "title": "Fmode 云函数(Function 合同 + /api/functions)",
  42. "summary": "Fmode 云函数部署与调用:Function 合同表存动态函数源,/api/functions 发布执行。",
  43. "triggers": "云函数、Function、/api/functions、后端函数、业务接口、动态函数",
  44. "endpoints": [
  45. ("POST https://server.fmode.cn/api/functions", "调用/注册云函数 {token, path, params}"),
  46. ],
  47. "keypoints": ["CRUD 不建用户维护服务器,动态函数进 Function 合同表", "handler(request,response) 签名 + 会话鉴权 + 租户校验 + 幂等", "masterKey/dbURI/平台session 绝不进浏览器 bundle"],
  48. },
  49. {
  50. "dir": "fmode-cloud-resources",
  51. "name": "fmode-cloud-resources",
  52. "slug": "fmode-skill-fmode-cloud-resources",
  53. "version": "0.1.0",
  54. "title": "Fmode 云资源(华为云 STS 部署)",
  55. "summary": "Fmode 云资源领取:平台签发项目级 STS,华为云 OBS 部署,无需用户华为账号。",
  56. "triggers": "云资源、领取、部署、华为云、STS、obs、huaweicloud、deploy",
  57. "endpoints": [
  58. ("POST https://server.fmode.cn/api/apig/deploy/huaweicloud", "取 STS {token, projectId} → accessKey/secretKey/securityToken/expiresAt/obsPath"),
  59. ],
  60. "keypoints": ["STS 平台签发,非用户华为账号,用户无需华为账号/服务器", "过期(<5min)丢弃整套后重取,最多重试2次", "绝不替换为永久密钥"],
  61. },
  62. {
  63. "dir": "fmode-static-publish",
  64. "name": "fmode-static-publish",
  65. "slug": "fmode-skill-fmode-static-publish",
  66. "version": "0.1.0",
  67. "title": "Fmode 静态发布(OBS+CDN 公开 URL)",
  68. "summary": "Fmode 前后端发布:构建 → 注册 Project → STS → obsutil 同步 OBS → CDN 刷新 → 验证公开 URL。",
  69. "triggers": "发布、部署、上线、静态站、前端、课件、CDN、obsutil、public URL、huaweicloud-cdn-refresh",
  70. "endpoints": [
  71. ("POST https://server.fmode.cn/api/functions", "注册 Project {token,path:/fmode/project-register,params:{name,version,gitUrl}}"),
  72. ("POST https://server.fmode.cn/api/apig/deploy/huaweicloud", "取 STS {token,projectId}"),
  73. ("POST https://server.fmode.cn/api/apig/deploy/huaweicloud-cdn-refresh", "CDN 刷新 {token,projectId,path}"),
  74. ],
  75. "keypoints": ["注册Project→构建→扫token→取STS→上传obsPath→CDN刷新→验证URL 逐门禁报告", "obsPath 必须是 dev/<projectId>/", "会话过期先重播登录再取新STS,不求华为凭据"],
  76. },
  77. {
  78. "dir": "fmode-git-archive",
  79. "name": "fmode-git-archive",
  80. "slug": "fmode-skill-fmode-git-archive",
  81. "version": "0.1.0",
  82. "title": "Fmode Git 归档(Gitea + 源码入库)",
  83. "summary": "Fmode 项目归档:源码进 git.fmode.cn,建 Gogs 账号/个人 token/建仓/推送/验证 SHA。",
  84. "triggers": "归档、git、Gogs、Gitea、git.fmode.cn、仓库、源码入库、git archive",
  85. "endpoints": [
  86. ("POST https://server.fmode.cn/api/functions", "Gogs admin proxy 建用户 {token,path:/gogs/admin/proxy,params:{method:POST,path:/admin/users,...}}"),
  87. ("POST https://git.fmode.cn/api/v1/users/<username>/tokens", "创建个人 token"),
  88. ("POST https://git.fmode.cn/api/v1/user/repos", "创建私有仓库"),
  89. ],
  90. "keypoints": ["用户名强制 fmode-<phone>,请求值忽略占位", "token 绝不进 remote URL,用 HTTPS + token 密码", "排除 .env/密钥/构建产物/日志", "git ls-remote 验证远端 SHA,发布与归档分开报告"],
  91. },
  92. ]
  93. # 通用 ESM 脚本模板(fetch 封装)
  94. FETCH_TMPL = '''#!/usr/bin/env node
  95. /**
  96. * {title} — 辅助脚本(零 npm 依赖,Node>=18 自带 fetch)
  97. * Copyright (c) 未来飞马 MPL-2.0
  98. *
  99. * 用法:node index.mjs <action> [jsonPayload]
  100. */
  101. const BASE = "https://server.fmode.cn";
  102. async function call(method, path, body, token) {{
  103. const headers = {{ "content-type": "application/json" }};
  104. if (token) headers["authorization"] = "Bearer " + token;
  105. const res = await fetch(BASE + path, {{
  106. method, headers, body: body ? JSON.stringify(body) : undefined,
  107. }});
  108. const text = await res.text();
  109. // 脱敏输出:token/密钥不打印
  110. const safe = text.replace(/(token|authorization|accessKey|secretKey|securityToken)["'\\s:]+([^",}}]+)/gi, "$1:***");
  111. console.log(safe);
  112. return res;
  113. }}
  114. const [action, raw] = process.argv.slice(2);
  115. let payload = {{}};
  116. try {{ if (raw) payload = JSON.parse(raw); }} catch {{ payload = {{ _raw: raw }}; }}
  117. {actions}
  118. '''
  119. def actions_for(epoints):
  120. lines = []
  121. for ep in epoints:
  122. method, path = ep[0].split(' ')
  123. parts = path.replace('https://server.fmode.cn','').strip()
  124. # 生成一个如 call("<METHOD>","<PATH>", payload) 的示例
  125. lines.append(f'// 示例端点: {method} {ep[1]}')
  126. return "\n".join(lines)
  127. for s in skills:
  128. d = os.path.join(BASE, s['dir'])
  129. os.makedirs(os.path.join(d, 'scripts'), exist_ok=True)
  130. os.makedirs(os.path.join(d, 'references'), exist_ok=True)
  131. # SKILL.md
  132. ep_lines = "\n".join(f"| `{m} {p}` | {desc} |" for m, p, desc in [
  133. (m, p, dsc) for m, p, dsc in [(ep[0].split(' ')[0], ep[0].split(' ')[1], ep[1]) for ep in s['endpoints']]
  134. ])
  135. kp_lines = "\n".join(f"- ⚠️ {k}" for k in s['keypoints'])
  136. skill_md = f"""---
  137. slug: {s['slug']}
  138. displayName: {s['dir']}
  139. version: {s['version']}
  140. name: {s['dir']}
  141. description: {s['summary']} 触发词:{s['triggers']}
  142. tags: [FmodeAgent, FmodeCode, 服务级, {s['dir'].replace('fmode-','')}]
  143. platform: Both
  144. level: 服务级
  145. category: 平台基础设施
  146. icon: ⚙️
  147. license: MPL-2.0
  148. author: Fmode (未来飞马)
  149. ---
  150. # {s['title']}
  151. ## 触发词
  152. {s['triggers']}
  153. ## 快速开始(ESM)
  154. ```bash
  155. node scripts/index.mjs <action> '{{"...": ...}}'
  156. ```
  157. 或 Agent 直接按流程调用下述端点。
  158. ## 端点清单
  159. | 端点 | 作用 |
  160. |------|------|
  161. {ep_lines}
  162. ## 关键纪律
  163. {kp_lines}
  164. ## References
  165. - 详见 `references/` 目录
  166. - 完整平台规范见 skill-core-guide(`fmode-skills` 自带)
  167. ## License
  168. MPL-2.0 © 2026 Fmode (未来飞马)
  169. """
  170. with open(os.path.join(d, 'SKILL.md'), 'w', encoding='utf-8') as f:
  171. f.write(skill_md)
  172. # scripts/index.mjs(零依赖 fetch 封装)
  173. fetch_js = FETCH_TMPL.format(title=s['title'], actions=actions_for(s['endpoints']))
  174. with open(os.path.join(d, 'scripts', 'index.mjs'), 'w', encoding='utf-8') as f:
  175. f.write(fetch_js)
  176. # references/endpoints.md
  177. with open(os.path.join(d, 'references', 'endpoints.md'), 'w', encoding='utf-8') as f:
  178. f.write(f"# {s['title']} 端点参考\n\n" + ep_lines + "\n")
  179. print(f"✅ 生成 {s['dir']}")
  180. print("🎉 服务层子技能生成完毕")