sophhub 0.1.0

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.
Files changed (125) hide show
  1. package/bin/sophhub.js +21 -0
  2. package/package.json +32 -0
  3. package/skills/VERSIONS.md +27 -0
  4. package/skills/builtin/clawhub/SKILL.md +77 -0
  5. package/skills/builtin/flight-booking/SKILL.md +288 -0
  6. package/skills/builtin/flight-booking/scripts/flight_booking.py +1232 -0
  7. package/skills/builtin/inventory-management/SKILL.md +241 -0
  8. package/skills/builtin/inventory-management/scripts/inventory.py +1844 -0
  9. package/skills/builtin/schedule-reminder/SKILL.md +619 -0
  10. package/skills/builtin/schedule-reminder/schedule_template.md +68 -0
  11. package/skills/builtin/schedule-reminder/scripts/append_event.py +204 -0
  12. package/skills/builtin/schedule-reminder/scripts/create_reminders.sh +163 -0
  13. package/skills/builtin/schedule-reminder/scripts/daily_activate.sh +175 -0
  14. package/skills/builtin/schedule-reminder/scripts/parse_schedule.py +704 -0
  15. package/skills/builtin/schedule-reminder/scripts/setup.sh +242 -0
  16. package/skills/builtin/schedule-reminder//347/224/250/346/210/267/346/214/207/345/215/227.md +311 -0
  17. package/skills/builtin/skill-creator/SKILL.md +370 -0
  18. package/skills/builtin/skill-creator/license.txt +202 -0
  19. package/skills/builtin/skill-creator/scripts/init_skill.py +378 -0
  20. package/skills/builtin/skill-creator/scripts/package_skill.py +111 -0
  21. package/skills/builtin/skill-creator/scripts/quick_validate.py +101 -0
  22. package/skills/builtin/sophnet-customer-management/SKILL.md +271 -0
  23. package/skills/builtin/sophnet-customer-management/pyproject.toml +15 -0
  24. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/__init__.py +2 -0
  25. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/__main__.py +5 -0
  26. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/cli.py +67 -0
  27. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/__init__.py +2 -0
  28. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/customer.py +60 -0
  29. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/export_file.py +18 -0
  30. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/import_file.py +15 -0
  31. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/reminder.py +26 -0
  32. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/schema.py +28 -0
  33. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/config.py +54 -0
  34. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/__init__.py +2 -0
  35. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/exporter.py +85 -0
  36. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/models.py +84 -0
  37. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/normalizer.py +144 -0
  38. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/parser.py +241 -0
  39. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/query.py +109 -0
  40. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/reminder.py +121 -0
  41. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/repository.py +397 -0
  42. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/schema.py +106 -0
  43. package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/service.py +565 -0
  44. package/skills/builtin/sophnet-customer-management/uv.lock +48 -0
  45. package/skills/builtin/sophnet-customized-marketing/SKILL.md +144 -0
  46. package/skills/builtin/sophnet-customized-marketing/playbooks/campaign-planning.md +187 -0
  47. package/skills/builtin/sophnet-customized-marketing/playbooks/content-generation.md +124 -0
  48. package/skills/builtin/sophnet-customized-marketing/playbooks/marketing-calendar.md +59 -0
  49. package/skills/builtin/sophnet-customized-marketing/playbooks/multi-channel-bundle.md +94 -0
  50. package/skills/builtin/sophnet-customized-marketing/playbooks/poster-generation.md +182 -0
  51. package/skills/builtin/sophnet-customized-marketing/playbooks/style-profile-workflow.md +103 -0
  52. package/skills/builtin/sophnet-customized-marketing/pyproject.toml +9 -0
  53. package/skills/builtin/sophnet-customized-marketing/references/campaign-mechanics.md +168 -0
  54. package/skills/builtin/sophnet-customized-marketing/references/content-safety.md +26 -0
  55. package/skills/builtin/sophnet-customized-marketing/references/marketing-date-checklist.md +99 -0
  56. package/skills/builtin/sophnet-customized-marketing/references/platform-writing-guidelines.md +88 -0
  57. package/skills/builtin/sophnet-customized-marketing/references/quality-checklist.md +44 -0
  58. package/skills/builtin/sophnet-customized-marketing/scripts/generate_poster.py +585 -0
  59. package/skills/builtin/sophnet-customized-marketing/scripts/style_profile.py +215 -0
  60. package/skills/builtin/sophnet-face-search/SKILL.md +115 -0
  61. package/skills/builtin/sophnet-face-search/pyproject.toml +11 -0
  62. package/skills/builtin/sophnet-face-search/scripts/face_search.py +336 -0
  63. package/skills/builtin/sophnet-face-search/uv.lock +508 -0
  64. package/skills/builtin/sophnet-image-edit/SKILL.md +140 -0
  65. package/skills/builtin/sophnet-image-edit/pyproject.toml +9 -0
  66. package/skills/builtin/sophnet-image-edit/scripts/edit_and_preview.sh +68 -0
  67. package/skills/builtin/sophnet-image-edit/scripts/edit_image.py +279 -0
  68. package/skills/builtin/sophnet-image-edit/uv.lock +234 -0
  69. package/skills/builtin/sophnet-image-generate/SKILL.md +62 -0
  70. package/skills/builtin/sophnet-image-generate/pyproject.toml +9 -0
  71. package/skills/builtin/sophnet-image-generate/scripts/generate_image.py +156 -0
  72. package/skills/builtin/sophnet-image-generate/uv.lock +234 -0
  73. package/skills/builtin/sophnet-image-ocr/SKILL.md +167 -0
  74. package/skills/builtin/sophnet-image-ocr/pyproject.toml +13 -0
  75. package/skills/builtin/sophnet-image-ocr/scripts/ocr.py +226 -0
  76. package/skills/builtin/sophnet-image-ocr/uv.lock +234 -0
  77. package/skills/builtin/sophnet-infinite-talk/SKILL.md +140 -0
  78. package/skills/builtin/sophnet-infinite-talk/pyproject.toml +9 -0
  79. package/skills/builtin/sophnet-infinite-talk/scripts/gen.py +172 -0
  80. package/skills/builtin/sophnet-oss/SKILL.md +109 -0
  81. package/skills/builtin/sophnet-oss/pyproject.toml +8 -0
  82. package/skills/builtin/sophnet-oss/scripts/upload_file.py +43 -0
  83. package/skills/builtin/sophnet-qa-install/SKILL.md +210 -0
  84. package/skills/builtin/sophnet-qa-install/pyproject.toml +6 -0
  85. package/skills/builtin/sophnet-qa-install/scripts/backup_md.py +35 -0
  86. package/skills/builtin/sophnet-qa-install/scripts/check_installed.py +143 -0
  87. package/skills/builtin/sophnet-qa-install/scripts/update_config.py +142 -0
  88. package/skills/builtin/sophnet-qa-install/scripts/update_md.py +73 -0
  89. package/skills/builtin/sophnet-training-install/SKILL.md +211 -0
  90. package/skills/builtin/sophnet-training-install/pyproject.toml +6 -0
  91. package/skills/builtin/sophnet-training-install/scripts/backup_md.py +35 -0
  92. package/skills/builtin/sophnet-training-install/scripts/check_installed.py +144 -0
  93. package/skills/builtin/sophnet-training-install/scripts/update_config.py +142 -0
  94. package/skills/builtin/sophnet-training-install/scripts/update_md.py +73 -0
  95. package/skills/builtin/sophnet-tts/SKILL.md +79 -0
  96. package/skills/builtin/sophnet-tts/pyproject.toml +9 -0
  97. package/skills/builtin/sophnet-tts/scripts/gen_tts.py +130 -0
  98. package/skills/builtin/sophnet-video-generate/SKILL.md +116 -0
  99. package/skills/builtin/sophnet-video-generate/scripts/gen_video.py +304 -0
  100. package/skills/builtin/video-understand/SKILL.md +79 -0
  101. package/skills/builtin/video-understand/scripts/video_understand.py +204 -0
  102. package/skills/builtin/weather/SKILL.md +112 -0
  103. package/skills/builtin/web-scraper/SKILL.md +101 -0
  104. package/skills/builtin/web-scraper/scripts/scrape.py +270 -0
  105. package/skills/builtin/website-builder/SKILL.md +266 -0
  106. package/skills/builtin/website-builder/scripts/deploy_site.sh +46 -0
  107. package/skills/store/didi-ride/SKILL.md +309 -0
  108. package/skills/store/didi-ride/_meta.json +6 -0
  109. package/skills/store/didi-ride/assets/PREFERENCE.md +58 -0
  110. package/skills/store/didi-ride/package.json +15 -0
  111. package/skills/store/didi-ride/references/api_references.md +171 -0
  112. package/skills/store/didi-ride/references/error_handling.md +68 -0
  113. package/skills/store/didi-ride/references/setup.md +73 -0
  114. package/skills/store/didi-ride/references/workflow.md +150 -0
  115. package/skills/store/flyai/SKILL.md +119 -0
  116. package/skills/store/flyai/references/fliggy-fast-search.md +53 -0
  117. package/skills/store/flyai/references/search-flight.md +89 -0
  118. package/skills/store/flyai/references/search-hotels.md +57 -0
  119. package/skills/store/flyai/references/search-poi.md +49 -0
  120. package/src/commands/download.js +103 -0
  121. package/src/commands/list.js +67 -0
  122. package/src/utils/config.js +24 -0
  123. package/src/utils/gitlab.js +67 -0
  124. package/src/utils/paths.js +19 -0
  125. package/src/utils/versions.js +38 -0
@@ -0,0 +1,144 @@
1
+ """
2
+ 校验智能培训系统安装状态。
3
+
4
+ 三种状态(退出码):
5
+ 0 — INSTALLED:已安装,无需操作
6
+ 1 — NOT_INSTALLED:未安装
7
+ 2 — UPDATABLE:可更新
8
+
9
+ 同时输出配置状态(CONFIG_FOUND / CONFIG_NOT_FOUND)。
10
+ 当状态为 UPDATABLE 且配置存在时,额外输出已有的 ADMIN_NAME / QA_NAME。
11
+ """
12
+
13
+ import argparse
14
+ import json
15
+ import os
16
+ import sys
17
+
18
+
19
+ def get_workspace_root(config_path: str) -> str:
20
+ """从 openclaw.json 所在目录推导 workspace 根目录(.openclaw)。"""
21
+ return os.path.dirname(os.path.abspath(config_path))
22
+
23
+
24
+ def step2_artifacts_exist(workspace_root: str) -> bool:
25
+ """检查 Step 2 要创建的目录和软链接是否已存在。"""
26
+ base = os.path.join(workspace_root, "workspace-training")
27
+ workspace_qa = os.path.join(base, "workspace-qa")
28
+
29
+ if not os.path.isdir(workspace_qa):
30
+ return False
31
+
32
+ return True
33
+
34
+
35
+ def agents_already_in_config(config_path: str) -> bool:
36
+ """检查 openclaw.json 中是否已包含 training-admin 和 training-qa。"""
37
+ if not os.path.exists(config_path):
38
+ return False
39
+ try:
40
+ with open(config_path, "r", encoding="utf-8") as f:
41
+ config = json.load(f)
42
+ except (json.JSONDecodeError, OSError):
43
+ return False
44
+ agents = config.get("agents", {}).get("list", [])
45
+ ids = {a.get("id") for a in agents if isinstance(a, dict) and a.get("id")}
46
+ return "training-admin" in ids and "training-qa" in ids
47
+
48
+
49
+ def get_agent_names(config_path: str) -> tuple[str, str]:
50
+ """从 openclaw.json 读取 training-admin 和 training-qa 的显示名称。"""
51
+ admin_name, qa_name = "培训管理员", "培训助手"
52
+ try:
53
+ with open(config_path, "r", encoding="utf-8") as f:
54
+ config = json.load(f)
55
+ for agent in config.get("agents", {}).get("list", []):
56
+ if not isinstance(agent, dict):
57
+ continue
58
+ if agent.get("id") == "training-admin" and agent.get("name"):
59
+ admin_name = agent["name"]
60
+ elif agent.get("id") == "training-qa" and agent.get("name"):
61
+ qa_name = agent["name"]
62
+ except (json.JSONDecodeError, OSError):
63
+ pass
64
+ return admin_name, qa_name
65
+
66
+
67
+ def parse_version(v: str) -> tuple:
68
+ """将版本号字符串解析为可比较的整数元组。"""
69
+ try:
70
+ return tuple(int(x) for x in v.strip().split("."))
71
+ except (ValueError, AttributeError):
72
+ return ()
73
+
74
+
75
+ def get_installed_version(workspace_root: str) -> str | None:
76
+ """读取 workspace-training/git_version 中的版本号。"""
77
+ version_file = os.path.join(workspace_root, "workspace-training", "git_version")
78
+ if not os.path.isfile(version_file):
79
+ return None
80
+ try:
81
+ with open(version_file, "r", encoding="utf-8") as f:
82
+ return f.read().strip()
83
+ except OSError:
84
+ return None
85
+
86
+
87
+ def check_installed(config_path: str, target_version: str) -> int:
88
+ """
89
+ 判断安装状态。
90
+ 返回: 0=已安装, 1=未安装, 2=可更新
91
+ """
92
+ workspace_root = get_workspace_root(config_path)
93
+
94
+ if not step2_artifacts_exist(workspace_root):
95
+ return 1
96
+
97
+ installed_version = get_installed_version(workspace_root)
98
+ if installed_version is None or parse_version(installed_version) < parse_version(target_version):
99
+ return 2
100
+
101
+ if not agents_already_in_config(config_path):
102
+ return 1
103
+
104
+ return 0
105
+
106
+
107
+ def main():
108
+ parser = argparse.ArgumentParser(description="校验智能培训系统安装状态")
109
+ parser.add_argument(
110
+ "--config",
111
+ default="/home/node/.openclaw/openclaw.json",
112
+ help="openclaw.json 路径",
113
+ )
114
+ parser.add_argument(
115
+ "--version",
116
+ required=True,
117
+ help="目标版本号(例:1.0.1)",
118
+ )
119
+ args = parser.parse_args()
120
+
121
+ config_path = args.config
122
+ status = check_installed(config_path, args.version)
123
+ config_exists = agents_already_in_config(config_path)
124
+
125
+ if status == 0:
126
+ print("INSTALLED")
127
+ print("CONFIG_FOUND" if config_exists else "CONFIG_NOT_FOUND")
128
+ sys.exit(0)
129
+ elif status == 2:
130
+ print("UPDATABLE")
131
+ print("CONFIG_FOUND" if config_exists else "CONFIG_NOT_FOUND")
132
+ if config_exists:
133
+ admin_name, qa_name = get_agent_names(config_path)
134
+ print(f"ADMIN_NAME={admin_name}")
135
+ print(f"QA_NAME={qa_name}")
136
+ sys.exit(2)
137
+ else:
138
+ print("NOT_INSTALLED")
139
+ print("CONFIG_FOUND" if config_exists else "CONFIG_NOT_FOUND")
140
+ sys.exit(1)
141
+
142
+
143
+ if __name__ == "__main__":
144
+ main()
@@ -0,0 +1,142 @@
1
+ """
2
+ OpenClaw 配置更新脚本
3
+
4
+ 向 openclaw.json 中追加 training-admin、training-qa 以及 main 三个 agent 的配置。
5
+ """
6
+
7
+ import argparse
8
+ import json
9
+ import os
10
+ import secrets
11
+ import sys
12
+
13
+ def get_api_secret(config_path: str = "/home/node/.openclaw/openclaw.json"):
14
+ if not os.path.exists(config_path):
15
+ raise FileNotFoundError(f"Config file not found: {config_path}")
16
+ try:
17
+ with open(config_path, "r", encoding="utf-8") as f:
18
+ config = json.load(f)
19
+ except json.JSONDecodeError:
20
+ raise ValueError(f"Invalid JSON in config file: {config_path}")
21
+ channels = config.get("channels", {})
22
+ bot_api_channel_config = channels.get("bot-api", {})
23
+ if not bot_api_channel_config:
24
+ return None
25
+ accounts_config = bot_api_channel_config.get("accounts", {})
26
+ if not accounts_config:
27
+ return None
28
+ qa_agent_config = accounts_config.get("training-qa", {})
29
+ if not qa_agent_config:
30
+ return None
31
+ return qa_agent_config.get("apiSecret", None)
32
+
33
+
34
+ def update_config(admin_name: str, qa_name: str, config_path: str = "/home/node/.openclaw/openclaw.json"):
35
+ if not os.path.exists(config_path):
36
+ raise FileNotFoundError(f"Config file not found: {config_path}")
37
+
38
+ with open(config_path, "r", encoding="utf-8") as f:
39
+ config = json.load(f)
40
+
41
+ admin_agent_config = {
42
+ "id": "training-admin",
43
+ "workspace": "/home/node/.openclaw/workspace-training",
44
+ "identity": {
45
+ "name": admin_name,
46
+ "theme": admin_name,
47
+ "emoji": "🤖"
48
+ },
49
+ "subagents": {
50
+ "allowAgents": ["training-qa"]
51
+ }
52
+ }
53
+ qa_agent_config = {
54
+ "id": "training-qa",
55
+ "workspace": "/home/node/.openclaw/workspace-training/workspace-qa",
56
+ "identity": {
57
+ "name": qa_name,
58
+ "theme": qa_name,
59
+ "emoji": "💁"
60
+ },
61
+ "tools": {
62
+ "deny": ["exec", "web_search", "web_fetch"]
63
+ }
64
+ }
65
+ main_agent_config = {
66
+ "id": "main",
67
+ "workspace": "/home/node/.openclaw/workspace",
68
+ "identity": {
69
+ "name": "main",
70
+ "theme": "main",
71
+ "emoji": "🦞"
72
+ }
73
+ }
74
+
75
+ qa_agent_account_config = {
76
+ "agentId": "training-qa",
77
+ "name": qa_name,
78
+ "apiSecret": secrets.token_hex(32),
79
+ "enabled": True
80
+ }
81
+ config.setdefault("agents", {})
82
+ agents = config.get("agents", {}).get("list", [])
83
+ has_main = False
84
+ for agent in agents:
85
+ aid = agent.get("id") if isinstance(agent, dict) else None
86
+ if aid == "main":
87
+ has_main = True
88
+ elif aid == "training-admin":
89
+ raise ValueError("培训管理员 agent 已存在")
90
+ elif aid == "training-qa":
91
+ raise ValueError("培训助手 agent 已存在")
92
+
93
+ if not has_main:
94
+ agents.append(main_agent_config)
95
+ agents.append(admin_agent_config)
96
+ agents.append(qa_agent_config)
97
+ config["agents"]["list"] = agents
98
+
99
+ # 启用 bot-api 插件(用 setdefault 保证 plugins.entries 与 bot-api 存在,避免 KeyError)
100
+ plugins = config.get("plugins", {})
101
+ entries = plugins.setdefault("entries", {})
102
+ entries.setdefault("bot-api", {})["enabled"] = True
103
+ config["plugins"] = plugins
104
+
105
+ # 添加培训子系统的channel配置
106
+ channels = config.get("channels", {})
107
+ bot_api_channel_config = channels.get("bot-api", {})
108
+ if bot_api_channel_config:
109
+ accounts_config = bot_api_channel_config.get("accounts", {})
110
+ accounts_config["training-qa"] = qa_agent_account_config
111
+ bot_api_channel_config["accounts"] = accounts_config
112
+ else:
113
+ bot_api_channel_config = {
114
+ "accounts": {
115
+ "training-qa": qa_agent_account_config
116
+ }
117
+ }
118
+ channels["bot-api"] = bot_api_channel_config
119
+ config["channels"] = channels
120
+
121
+ with open(config_path, "w", encoding="utf-8") as f:
122
+ json.dump(config, f, indent=4, ensure_ascii=False)
123
+ return True
124
+
125
+
126
+ def main():
127
+ parser = argparse.ArgumentParser(description="向 openclaw.json 注入培训 agent 配置")
128
+ parser.add_argument("--admin-name", default="培训管理员", help="培训管理员显示名称")
129
+ parser.add_argument("--qa-name", default="培训助手", help="培训助手显示名称")
130
+ parser.add_argument("--config", default="/home/node/.openclaw/openclaw.json", help="openclaw.json 路径")
131
+ args = parser.parse_args()
132
+ try:
133
+ update_config(args.admin_name, args.qa_name, args.config)
134
+ print(f"✅ 配置更新成功:管理员={args.admin_name}, 培训={args.qa_name}")
135
+ print(f"培训助手 API Secret: {get_api_secret(args.config)}")
136
+ except Exception as e:
137
+ print(f"❌ 配置更新失败:{e}")
138
+ sys.exit(1)
139
+
140
+
141
+ if __name__ == "__main__":
142
+ main()
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 修改指定目录下所有 .md 文件中的占位符:
4
+ - {{培训助手}} -> qa_name
5
+ - {{知识库管理员}} -> admin_name
6
+ 仅处理目标目录本身,不递归子文件夹。
7
+ """
8
+
9
+ import argparse
10
+ from pathlib import Path
11
+
12
+
13
+ def update_md_in_dir(
14
+ dir_path: str | Path,
15
+ qa_name: str = "培训助手",
16
+ admin_name: str = "培训管理员",
17
+ ) -> int:
18
+ """
19
+ 修改指定目录下所有 .md 文件中的占位符(仅该目录,不含子文件夹)。
20
+ 返回 0 成功,1 目录不存在,2 读写文件失败。
21
+ """
22
+ root = Path(dir_path)
23
+ if not root.is_dir():
24
+ print(f"错误:目录不存在 {root}")
25
+ return 1
26
+
27
+ md_files = [f for f in root.iterdir() if f.is_file() and f.suffix == ".md"]
28
+ if not md_files:
29
+ print(f"在 {root} 下未找到 .md 文件")
30
+ return 0
31
+
32
+ replacements = [
33
+ ("{{培训助手}}", qa_name),
34
+ ("{{知识库管理员}}", admin_name),
35
+ ]
36
+
37
+ for path in md_files:
38
+ try:
39
+ text = path.read_text(encoding="utf-8")
40
+ except OSError as e:
41
+ print(f"错误:无法读取 {path}:{e}")
42
+ return 2
43
+ original = text
44
+ for old, new in replacements:
45
+ text = text.replace(old, new)
46
+ if text != original:
47
+ try:
48
+ path.write_text(text, encoding="utf-8")
49
+ except OSError as e:
50
+ print(f"错误:无法写入 {path}:{e}")
51
+ return 2
52
+ print(f"已更新: {path}")
53
+ else:
54
+ print(f"无需修改: {path}")
55
+
56
+ return 0
57
+
58
+
59
+ def main():
60
+ parser = argparse.ArgumentParser(description="替换 .md 文件中的占位符")
61
+ parser.add_argument("--qa-name", default="培训助手", help="替换 {{培训助手}} 的值")
62
+ parser.add_argument("--admin-name", default="培训管理员", help="替换 {{知识库管理员}} 的值")
63
+ parser.add_argument(
64
+ "--dir",
65
+ default="/home/node/.openclaw/workspace-training",
66
+ help="要处理的目录路径(仅该目录下的 .md,不含子目录)",
67
+ )
68
+ args = parser.parse_args()
69
+ return update_md_in_dir(args.dir, qa_name=args.qa_name, admin_name=args.admin_name)
70
+
71
+
72
+ if __name__ == "__main__":
73
+ exit(main())
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: sophnet-tts
3
+ description: 当用户需要将文本转语音(TTS)时使用。支持自动识别输入中的音色;若未指定音色则先提供可选声音风格让用户选择,指定后直接生成音频并返回 24 小时有效下载链接。
4
+ ---
5
+
6
+ # Sophnet TTS
7
+
8
+ ## 概述
9
+
10
+ 使用 `scripts/gen_tts.py` 调用 Sophnet TTS 接口,把文本转换为音频并返回下载链接。
11
+
12
+ 脚本路径:`{baseDir}/scripts/gen_tts.py`
13
+
14
+ ## 何时使用
15
+
16
+ - 用户要求“生成语音 / 文本转语音 / 配音”
17
+ - 用户给出一段文本希望合成音频
18
+ - 用户指定了音色,或希望先选择音色
19
+
20
+ ## 声音风格(可选项)
21
+
22
+ - 清甜推销女
23
+ - 呆萌机器人
24
+ - 经典猴哥
25
+ - 毒舌心机女
26
+ - 欢脱粤语男
27
+ - 原味陕北男
28
+ - 甜美闽南女
29
+ - 娇率才女音
30
+ - 得道高僧
31
+ - 利落从容女
32
+ - 清爽利落男
33
+ - 优雅知性女
34
+ - 居家暖男
35
+ - 正经青年女
36
+ - 知性积极女
37
+ - 沉稳权威女声
38
+ - 沉稳青年男
39
+ - 精准干练女
40
+ - 博才干练男
41
+ - 沉稳播报女
42
+ - 典型播音女
43
+
44
+ ## 处理流程(必须遵循)
45
+
46
+ 1. 从用户输入中提取待合成文本。
47
+ 2. 判断输入中是否已经明确包含上述某个声音风格名称。
48
+ 3. 若**未包含音色**:
49
+ - 先回复用户可选声音风格列表,让用户选择其一。
50
+ - 不要立即调用脚本生成。
51
+ 4. 若**已包含音色**(或用户补充了音色):
52
+ - 调用脚本执行语音生成。
53
+ - 命令示例:
54
+
55
+ ```bash
56
+ uv run {baseDir}/scripts/gen_tts.py \
57
+ --text "要生成的文本" \
58
+ --voice "优雅知性女" \
59
+ --speech-rate 1.0
60
+ ```
61
+
62
+ 5. 输出格式
63
+
64
+ ```
65
+ 🎙️ **声音风格**: XXX
66
+ 📝 **播报内容**:XXX
67
+ 🎵 **生成音频**:<audio controls src="http:xxxx"></audio>
68
+ ⚠️ **提示**: 本内容有AI生成,音频保存24小时,请及时下载保存!
69
+ ```
70
+
71
+ ## 失败处理
72
+
73
+ - 若脚本失败或未返回可用链接,明确告知失败原因(若可提取到)。
74
+ - 引导用户重试(可建议缩短文本或更换音色)。
75
+
76
+ ## 注意事项
77
+
78
+ - `--text` 必填且不能为空。
79
+ - 优先使用用户明确指定的音色;未指定时必须先让用户选择。
@@ -0,0 +1,9 @@
1
+ [project]
2
+ name = "sophnet-tts"
3
+ version = "0.1.0"
4
+ description = "Skill-local runtime dependencies for sophnet-tts"
5
+ requires-python = ">=3.10"
6
+ dependencies = [
7
+ "requests>=2.28.0",
8
+ "sophnet-tools>=0.0.1",
9
+ ]
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Sophnet TTS Script
4
+ Supports text-to-speech generation using Sophnet TTS API.
5
+ """
6
+ import os
7
+ import sys
8
+ import argparse
9
+ import json
10
+ import tempfile
11
+ import requests
12
+ from typing import Optional, Dict, Any
13
+ import sophnet_tools
14
+
15
+ GENERATE_URL = "https://www.sophnet.com/api/open-apis/projects/easyllms/voice/synthesize-audio"
16
+
17
+ def create_request(
18
+ text: Optional[str],
19
+ voice: Optional[str] = "longyumi_v2",
20
+ speech_rate: Optional[float] = 1.0
21
+ ) -> dict:
22
+ """Build request body for TTS generation."""
23
+ payload = {
24
+ "text": [
25
+ text,
26
+ ],
27
+ "synthesis_param": {
28
+ "model": "cosyvoice-v2",
29
+ "voice": voice,
30
+ "format": "MP3_16000HZ_MONO_128KBPS",
31
+ "volume": 80,
32
+ "speechRate": speech_rate,
33
+ "pitchRate": 1
34
+ }
35
+ }
36
+ return payload
37
+
38
+ def gen_tts(
39
+ api_key: str, text: str, voice: str = "longyingxiao", speech_rate: float = 1.0
40
+ ) -> Dict[str, Any]:
41
+ """Make HTTP request with authentication."""
42
+ clean_text = (text or "").strip()
43
+ if not clean_text:
44
+ return {"error": "文本不能为空"}
45
+
46
+ headers = {
47
+ "Authorization": f"Bearer {api_key}",
48
+ "Content-Type": "application/json"
49
+ }
50
+ temp_file_path: Optional[str] = None
51
+ try:
52
+ response = requests.post(
53
+ GENERATE_URL,
54
+ headers=headers,
55
+ json=create_request(clean_text, voice, speech_rate),
56
+ timeout=60
57
+ )
58
+ if response.status_code != 200:
59
+ return {
60
+ "error": (
61
+ f"HTTP请求失败,状态码: {response.status_code}, 响应内容: {response.text}"
62
+ )
63
+ }
64
+
65
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as tmp:
66
+ temp_file_path = tmp.name
67
+ f = tmp
68
+ f.write(response.content)
69
+
70
+ result = sophnet_tools.upload_oss(temp_file_path, timeout=120)
71
+ if result:
72
+ return {"audio_url": result}
73
+
74
+ return {"error": "音频上传失败"}
75
+ except requests.exceptions.RequestException as e:
76
+ return {"error": f"HTTP请求失败: {e}"}
77
+ finally:
78
+ if temp_file_path and os.path.exists(temp_file_path):
79
+ os.remove(temp_file_path)
80
+
81
+
82
+ if __name__ == "__main__":
83
+ parser = argparse.ArgumentParser(description="Sophnet TTS Script")
84
+ parser.add_argument("--text", required=True, help="Text to generate audio")
85
+ parser.add_argument("--voice", type=str, default="优雅知性女", help="Voice name. Defaults to 优雅知性女")
86
+ parser.add_argument("--speech-rate", type=float, default=1.0, help="Speech rate. Defaults to 1.0")
87
+ args = parser.parse_args()
88
+ voice_dict = {
89
+ "清甜推销女": "longyingxiao",
90
+ "呆萌机器人": "longjiqi",
91
+ "经典猴哥":"longhouge",
92
+ "毒舌心机女":"longjixin",
93
+ "欢脱粤语男":"longanyue",
94
+ "原味陕北男":"longshange",
95
+ "甜美闽南女":"longanmin",
96
+ "娇率才女音":"longdaiyu",
97
+ "得道高僧":"longgaoseng",
98
+ "利落从容女":"longanli",
99
+ "清爽利落男": "longanlang",
100
+ "优雅知性女":"longanwen",
101
+ "居家暖男":"longanyun",
102
+ "正经青年女":"longyumi_v2",
103
+ "知性积极女": "longxiaochun_v2",
104
+ "沉稳权威女声":"longxiaoxia_v2",
105
+ "沉稳青年男": "longshu_v2",
106
+ "精准干练女": "loongbella_v2",
107
+ "博才干练男":"longshuo_v2",
108
+ "沉稳播报女":"longxiaobai_v2",
109
+ "典型播音女":"longjing_v2",
110
+ }
111
+ api_key = sophnet_tools.get_api_key()
112
+ if api_key is None:
113
+ print(f"❌ 未找到 Sophnet API Key,请联系客户支持")
114
+ sys.exit(1)
115
+
116
+ selected_voice = voice_dict.get(args.voice, voice_dict["优雅知性女"])
117
+ result = gen_tts(
118
+ api_key,
119
+ args.text,
120
+ voice_dict.get(selected_voice, voice_dict["优雅知性女"]),
121
+ args.speech_rate,
122
+ )
123
+
124
+ if "audio_url" in result:
125
+ print(f"🎙️ 声音风格: {selected_voice}")
126
+ print(f"📝 播报内容: {args.text}")
127
+ print(f"🎵 生成音频: ![audio]({result['audio_url']})")
128
+ else:
129
+ print(f"❌ 音频生成失败: {result.get('error', '音频生成失败')}")
130
+ sys.exit(1)
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: sophnet-video-generate
3
+ description: "Generate videos from text prompts or images using Sophnet API. Supports text-to-video (T2V) and image-to-video (I2V) generation with models including Wan2.6-T2V, Wan2.6-I2V, ViduQ2-pro, and Seedance-1.5-Pro. Use when users request video generation, ask to create videos from descriptions, convert images to videos, or specify video generation models. "
4
+ ---
5
+
6
+ # Video Generator
7
+
8
+ Generate videos using Sophnet's video generation API with support for both text-to-video and image-to-video workflows.
9
+
10
+ ## Supported Models
11
+
12
+ - **Wan2.6-T2V**: Text-to-video (default for text-only prompts)
13
+ - **Wan2.6-I2V**: Image-to-video (default when image is provided)
14
+ - **ViduQ2-pro**: Supports both T2V and I2V
15
+ - **Seedance-1.5-Pro**: Supports both T2V and I2V
16
+
17
+ ## Prerequisites
18
+
19
+ - **Python Environment**: Use `uv` to run Python scripts
20
+
21
+ ## Workflow
22
+
23
+ ### 1. Extract Image Path from Logs (if applicable)
24
+
25
+ When users upload images in chat channels, files are usually saved under `media/inbound/images/` in the workspace. Use Media Understanding logs to get exact resolved paths.
26
+
27
+ Typical log pattern:
28
+
29
+ ```
30
+ [Media Understanding] Resolved relative path: "media/inbound/images/xxx.jpg" -> "/absolute/path/to/workspace/media/inbound/images/xxx.jpg"
31
+ ```
32
+
33
+ Use the absolute path for the `--first-frame` argument.
34
+
35
+ ### 2. Select Model
36
+
37
+ - If image provided + user didn't specify model → use Wan2.6-I2V
38
+ - If no image + user didn't specify model → use Wan2.6-T2V
39
+ - If user specifies model → use their choice
40
+
41
+ ### 3. Run Generation Script
42
+
43
+ ```bash
44
+ uv run python {baseDir}/scripts/gen_video.py \
45
+ --prompt "Your video description" \
46
+ --model "Wan2.6-T2V" \
47
+ --size "1280*720" \
48
+ --duration 5 \
49
+ --first-frame "/absolute/path/to/image.jpg"
50
+ ```
51
+
52
+ **Parameters:**
53
+
54
+ - `--prompt`: Video description (required)
55
+ - `--model`: Model name (optional, auto-selected based on input)
56
+ - `--size`: Resolution, default "1280\*720"
57
+ - `--duration`: Duration in seconds, default 5
58
+ - `--first-frame`: Path to image file or URL (optional, for I2V)
59
+
60
+ ### 4. Output
61
+
62
+ The script will:
63
+
64
+ 1. Upload local images to OSS if needed
65
+ 2. Submit the video generation request
66
+ 3. Poll the API until completion
67
+ 4. Display the final video URL and local save path
68
+ 5. Prompt the user to download the video
69
+ 6. Other models: ViduQ2-pro, Seedance-1.5-Pro
70
+
71
+ Example output:
72
+
73
+ ```
74
+ 📹 **您的视频提示词为**: [prompt]
75
+ ✅ **视频生成成功**: <video controls width="100%" src="https://www.sophnet.com/api/open-apis/projects/download/xyz789"></video>
76
+ 📥 **视频本地保存路径**: /path/to/xyz789.mp4
77
+ 💬 **提示**:
78
+ ⚠️ 本内容有AI生成,视频保存24小时,请及时下载保存!
79
+ 🤖 当前使用的模型为 [model],还支持 [other models]
80
+ 🧠 如果想要指定模型,可以直接对我说:使用 [模型名称] 模型,生成 [视频描述]
81
+ ```
82
+
83
+ Present this information to the user in a clear format.
84
+
85
+ ## Examples
86
+
87
+ **Text-to-video:**
88
+
89
+ ```bash
90
+ uv run python {baseDir}/scripts/gen_video.py \
91
+ --prompt "A serene lake at sunset with swans swimming"
92
+ ```
93
+
94
+ **Image-to-video:**
95
+
96
+ ```bash
97
+ uv run python {baseDir}/scripts/gen_video.py \
98
+ --prompt "Camera slowly zooms in" \
99
+ --first-frame "/workspace/media/inbound/images/photo.jpg"
100
+ ```
101
+
102
+ **Specify model:**
103
+
104
+ ```bash
105
+ uv run python {baseDir}/scripts/gen_video.py \
106
+ --prompt "A cat playing with yarn" \
107
+ --model "ViduQ2-pro"
108
+ ```
109
+
110
+ ## Notes
111
+
112
+ - Video generation typically takes 1-3 minutes
113
+ - Videos are stored for 24 hours on Sophnet servers
114
+ - Local copies saved to `~/.openclaw/workspace/media/inbound/videos/`
115
+ - Generation typically takes 1-5 minutes depending on model and duration
116
+ - Script automatically polls for completion with 5-minute timeout