@rookiestar/eng-lang-tutor 1.2.2 → 1.2.5
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.
- package/README.md +16 -0
- package/package.json +8 -6
- package/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/__pycache__/audio_composer.cpython-313.pyc +0 -0
- package/scripts/__pycache__/audio_converter.cpython-313.pyc +0 -0
- package/scripts/__pycache__/audio_enhancer.cpython-313.pyc +0 -0
- package/scripts/__pycache__/audio_utils.cpython-313.pyc +0 -0
- package/scripts/__pycache__/command_parser.cpython-313.pyc +0 -0
- package/scripts/__pycache__/constants.cpython-313.pyc +0 -0
- package/scripts/__pycache__/cron_push.cpython-313.pyc +0 -0
- package/scripts/__pycache__/dedup.cpython-313.pyc +0 -0
- package/scripts/__pycache__/error_notebook.cpython-313.pyc +0 -0
- package/scripts/__pycache__/feishu_voice.cpython-313.pyc +0 -0
- package/scripts/__pycache__/gamification.cpython-313.pyc +0 -0
- package/scripts/__pycache__/scorer.cpython-313.pyc +0 -0
- package/scripts/__pycache__/state_manager.cpython-313.pyc +0 -0
- package/scripts/__pycache__/utils.cpython-313.pyc +0 -0
- package/scripts/audio/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/audio/__pycache__/composer.cpython-313.pyc +0 -0
- package/scripts/audio/__pycache__/converter.cpython-313.pyc +0 -0
- package/scripts/audio/__pycache__/feishu_voice.cpython-313.pyc +0 -0
- package/scripts/audio/__pycache__/utils.cpython-313.pyc +0 -0
- package/scripts/audio/composer.py +9 -4
- package/scripts/audio/tts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/audio/tts/__pycache__/base.cpython-313.pyc +0 -0
- package/scripts/audio/tts/__pycache__/manager.cpython-313.pyc +0 -0
- package/scripts/audio/tts/providers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/audio/tts/providers/__pycache__/edge.cpython-313.pyc +0 -0
- package/scripts/audio/tts/providers/__pycache__/xunfei.cpython-313.pyc +0 -0
- package/scripts/cli/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/cli/__pycache__/cli.cpython-313.pyc +0 -0
- package/scripts/cli/__pycache__/command_parser.cpython-313.pyc +0 -0
- package/scripts/core/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/core/__pycache__/constants.cpython-313.pyc +0 -0
- package/scripts/core/__pycache__/error_notebook.cpython-313.pyc +0 -0
- package/scripts/core/__pycache__/gamification.cpython-313.pyc +0 -0
- package/scripts/core/__pycache__/scorer.cpython-313.pyc +0 -0
- package/scripts/core/__pycache__/state_manager.cpython-313.pyc +0 -0
- package/scripts/core/state_manager.py +4 -1
- package/scripts/release.py +255 -0
- package/scripts/scheduling/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/scheduling/__pycache__/cron_push.cpython-313.pyc +0 -0
- package/scripts/utils/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/utils/__pycache__/dedup.cpython-313.pyc +0 -0
- package/scripts/utils/__pycache__/helpers.cpython-313.pyc +0 -0
- package/.claude/settings.local.json +0 -22
- package/.gitignore +0 -32
- package/CHANGELOG.md +0 -42
- package/CLAUDE.md +0 -275
- package/bin/eng-lang-tutor.js +0 -177
- package/docs/OPENCLAW_DEPLOYMENT.md +0 -241
package/README.md
CHANGED
|
@@ -84,6 +84,21 @@ openclaw skills list
|
|
|
84
84
|
openclaw skills info eng-lang-tutor
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
### 卸载
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# 卸载 npm 包并清除所有数据(包括状态和媒体文件)
|
|
91
|
+
npm uninstall -g @rookiestar/eng-lang-tutor && rm -rf ~/.openclaw/state/eng-lang-tutor ~/.openclaw/media/eng-lang-tutor
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**如需保留学习数据,仅卸载软件:**
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm uninstall -g @rookiestar/eng-lang-tutor
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
> 注意:`~/.openclaw/state/eng-lang-tutor/` 存储学习进度、XP、连胜等数据;`~/.openclaw/media/eng-lang-tutor/` 存储生成的音频文件。
|
|
101
|
+
|
|
87
102
|
**重启 Gateway:**
|
|
88
103
|
|
|
89
104
|
```bash
|
|
@@ -277,6 +292,7 @@ eng-lang-tutor/
|
|
|
277
292
|
│ ├── cli/ # 命令行模块
|
|
278
293
|
│ │ ├── cli.py # CLI 入口点
|
|
279
294
|
│ │ └── command_parser.py # 用户命令解析
|
|
295
|
+
│ ├── setup.py # 依赖安装脚本 (npm bin)
|
|
280
296
|
│ ├── scheduling/ # 调度模块
|
|
281
297
|
│ │ └── cron_push.py # 定时内容推送
|
|
282
298
|
│ ├── utils/ # 工具模块
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rookiestar/eng-lang-tutor",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.5",
|
|
4
4
|
"description": "地道美式英语导师 - OpenClaw Skill for learning authentic American English",
|
|
5
5
|
"main": "scripts/cli/cli.py",
|
|
6
6
|
"bin": {
|
|
@@ -29,11 +29,13 @@
|
|
|
29
29
|
},
|
|
30
30
|
"homepage": "https://github.com/rookiestar/Skills/tree/main/eng-lang-tutor#readme",
|
|
31
31
|
"files": [
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
32
|
+
"scripts/",
|
|
33
|
+
"templates/",
|
|
34
|
+
"examples/",
|
|
35
|
+
"references/",
|
|
36
|
+
"SKILL.md",
|
|
37
|
+
"README.md",
|
|
38
|
+
"requirements.txt"
|
|
37
39
|
],
|
|
38
40
|
"engines": {
|
|
39
41
|
"node": ">=14.0.0"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -87,9 +87,9 @@ class AudioComposer:
|
|
|
87
87
|
lead_in_silence: float = 1.0, # 引导语后留白
|
|
88
88
|
section_silence: float = 2.0, # 内容后留白(段落间隔)
|
|
89
89
|
dialogue_silence: float = 0.5, # 对话行之间留白
|
|
90
|
-
narrator_voice: str =
|
|
91
|
-
voice_a: str =
|
|
92
|
-
voice_b: str =
|
|
90
|
+
narrator_voice: str = None, # 旁白音色(None 时使用 TTS provider 默认值)
|
|
91
|
+
voice_a: str = None, # 对话 A 音色(None 时使用 TTS provider 默认值)
|
|
92
|
+
voice_b: str = None, # 对话 B 音色(None 时使用 TTS provider 默认值)
|
|
93
93
|
speed: float = 0.9 # 语速
|
|
94
94
|
) -> CompositionResult:
|
|
95
95
|
"""
|
|
@@ -110,6 +110,11 @@ class AudioComposer:
|
|
|
110
110
|
CompositionResult: 合成结果
|
|
111
111
|
"""
|
|
112
112
|
try:
|
|
113
|
+
# 使用 TTS provider 的默认音色(自动适配 Edge-TTS 或讯飞)
|
|
114
|
+
narrator_voice = narrator_voice or self.tts.get_voice_by_role("narrator")
|
|
115
|
+
voice_a = voice_a or self.tts.get_voice_by_role("dialogue_a")
|
|
116
|
+
voice_b = voice_b or self.tts.get_voice_by_role("dialogue_b")
|
|
117
|
+
|
|
113
118
|
output_path = Path(output_path)
|
|
114
119
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
115
120
|
|
|
@@ -213,7 +218,7 @@ class AudioComposer:
|
|
|
213
218
|
if not text:
|
|
214
219
|
continue
|
|
215
220
|
|
|
216
|
-
# A =
|
|
221
|
+
# A = EricNeural (男声), B = JennyNeural (女声)
|
|
217
222
|
voice = voice_a if speaker.upper() == "A" else voice_b
|
|
218
223
|
|
|
219
224
|
segment = self._synthesize_segment(
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -810,5 +810,8 @@ class StateManager:
|
|
|
810
810
|
|
|
811
811
|
# CLI interface - delegated to cli.py for better code organization
|
|
812
812
|
if __name__ == "__main__":
|
|
813
|
-
|
|
813
|
+
try:
|
|
814
|
+
from ..cli.cli import main
|
|
815
|
+
except ImportError:
|
|
816
|
+
from scripts.cli.cli import main
|
|
814
817
|
main()
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Release Script - Manages file visibility across local repo, remote repo, and npm package.
|
|
4
|
+
|
|
5
|
+
This script handles three layers:
|
|
6
|
+
1. Local repo - All tracked files (managed by .gitignore)
|
|
7
|
+
2. Remote repo - Public files only
|
|
8
|
+
3. npm package - Minimal user-facing files
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
python3 scripts/release.py --check # Show file distribution
|
|
12
|
+
python3 scripts/release.py --sync-remote # Sync to remote (dry-run)
|
|
13
|
+
python3 scripts/release.py --npm-pack # Create npm tarball
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import subprocess
|
|
18
|
+
import json
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Set
|
|
21
|
+
|
|
22
|
+
# Files/directories to EXCLUDE from remote repo (not publicly visible)
|
|
23
|
+
REMOTE_EXCLUDE = {
|
|
24
|
+
"tests/",
|
|
25
|
+
"package.json",
|
|
26
|
+
"package-lock.json",
|
|
27
|
+
"bin/",
|
|
28
|
+
"scripts/setup.py",
|
|
29
|
+
".gitignore",
|
|
30
|
+
".gitattributes",
|
|
31
|
+
".npmignore",
|
|
32
|
+
"*.egg-info/",
|
|
33
|
+
".pytest_cache/",
|
|
34
|
+
"__pycache__/",
|
|
35
|
+
".coverage",
|
|
36
|
+
"htmlcov/",
|
|
37
|
+
".tox/",
|
|
38
|
+
".nox/",
|
|
39
|
+
".claude/",
|
|
40
|
+
"*.pyc",
|
|
41
|
+
"*.pyo",
|
|
42
|
+
"*.tgz",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Files/directories to EXCLUDE from npm package (in addition to REMOTE_EXCLUDE)
|
|
46
|
+
# npm package should be minimal - only what users need to run
|
|
47
|
+
NPM_EXCLUDE = REMOTE_EXCLUDE | {
|
|
48
|
+
"CLAUDE.md", # Development guide for Claude AI
|
|
49
|
+
"docs/", # Internal documentation
|
|
50
|
+
"examples/", # Sample files (not needed for runtime)
|
|
51
|
+
"references/", # Reference materials
|
|
52
|
+
"*.md", # All markdown except README.md
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Files that MUST be included in npm package
|
|
56
|
+
NPM_INCLUDE = {
|
|
57
|
+
"README.md", # Keep README for npm display
|
|
58
|
+
"package.json", # Required for npm
|
|
59
|
+
"requirements.txt", # Python dependencies
|
|
60
|
+
"scripts/", # All scripts (excluding setup.py)
|
|
61
|
+
"templates/", # Prompt templates
|
|
62
|
+
"SKILL.md", # Skill documentation
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_tracked_files() -> Set[str]:
|
|
67
|
+
"""Get all files tracked by git."""
|
|
68
|
+
result = subprocess.run(
|
|
69
|
+
["git", "ls-files"],
|
|
70
|
+
capture_output=True,
|
|
71
|
+
text=True,
|
|
72
|
+
cwd=Path(__file__).parent.parent
|
|
73
|
+
)
|
|
74
|
+
return set(result.stdout.strip().split("\n"))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def should_exclude(path: str, exclude_patterns: Set[str]) -> bool:
|
|
78
|
+
"""Check if a path should be excluded based on patterns."""
|
|
79
|
+
for pattern in exclude_patterns:
|
|
80
|
+
if pattern.endswith("/"):
|
|
81
|
+
if path.startswith(pattern) or "/" + pattern in path:
|
|
82
|
+
return True
|
|
83
|
+
elif pattern.startswith("*."):
|
|
84
|
+
if path.endswith(pattern[1:]):
|
|
85
|
+
return True
|
|
86
|
+
else:
|
|
87
|
+
if path == pattern or path.startswith(pattern + "/"):
|
|
88
|
+
return True
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def categorize_files():
|
|
93
|
+
"""Categorize files into local, remote, and npm groups."""
|
|
94
|
+
tracked = get_tracked_files()
|
|
95
|
+
|
|
96
|
+
local_only = set()
|
|
97
|
+
remote_files = set()
|
|
98
|
+
npm_files = set()
|
|
99
|
+
|
|
100
|
+
for file in tracked:
|
|
101
|
+
# Skip empty strings
|
|
102
|
+
if not file:
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
# Check if in remote exclude list
|
|
106
|
+
if should_exclude(file, REMOTE_EXCLUDE):
|
|
107
|
+
local_only.add(file)
|
|
108
|
+
else:
|
|
109
|
+
remote_files.add(file)
|
|
110
|
+
|
|
111
|
+
# Check if should be in npm package
|
|
112
|
+
if should_exclude(file, NPM_EXCLUDE):
|
|
113
|
+
# Check if explicitly included
|
|
114
|
+
included = False
|
|
115
|
+
for inc_pattern in NPM_INCLUDE:
|
|
116
|
+
if inc_pattern.endswith("/"):
|
|
117
|
+
if file.startswith(inc_pattern):
|
|
118
|
+
included = True
|
|
119
|
+
break
|
|
120
|
+
elif file == inc_pattern:
|
|
121
|
+
included = True
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
if included:
|
|
125
|
+
npm_files.add(file)
|
|
126
|
+
else:
|
|
127
|
+
npm_files.add(file)
|
|
128
|
+
|
|
129
|
+
return local_only, remote_files, npm_files
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def show_distribution():
|
|
133
|
+
"""Show file distribution across three layers."""
|
|
134
|
+
local_only, remote_files, npm_files = categorize_files()
|
|
135
|
+
|
|
136
|
+
print("=" * 60)
|
|
137
|
+
print("FILE DISTRIBUTION ACROSS THREE LAYERS")
|
|
138
|
+
print("=" * 60)
|
|
139
|
+
|
|
140
|
+
print(f"\n📦 LOCAL REPO ONLY ({len(local_only)} files)")
|
|
141
|
+
print("-" * 40)
|
|
142
|
+
for f in sorted(local_only):
|
|
143
|
+
print(f" {f}")
|
|
144
|
+
|
|
145
|
+
print(f"\n🌐 REMOTE REPO (public, {len(remote_files)} files)")
|
|
146
|
+
print("-" * 40)
|
|
147
|
+
for f in sorted(remote_files):
|
|
148
|
+
print(f" {f}")
|
|
149
|
+
|
|
150
|
+
print(f"\n📦 NPM PACKAGE ({len(npm_files)} files)")
|
|
151
|
+
print("-" * 40)
|
|
152
|
+
for f in sorted(npm_files):
|
|
153
|
+
print(f" {f}")
|
|
154
|
+
|
|
155
|
+
print("\n" + "=" * 60)
|
|
156
|
+
print(f"SUMMARY: Local={len(local_only) + len(remote_files)} | Remote={len(remote_files)} | npm={len(npm_files)}")
|
|
157
|
+
print("=" * 60)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def create_gitattributes():
|
|
161
|
+
"""Create .gitattributes with export-ignore for local-only files."""
|
|
162
|
+
eng_lang_tutor_dir = Path(__file__).parent.parent
|
|
163
|
+
gitattributes_path = eng_lang_tutor_dir / ".gitattributes"
|
|
164
|
+
|
|
165
|
+
# Patterns to exclude from git archive (remote export)
|
|
166
|
+
export_ignore_patterns = [
|
|
167
|
+
"tests/",
|
|
168
|
+
".pytest_cache/",
|
|
169
|
+
"*.pyc",
|
|
170
|
+
"*.pyo",
|
|
171
|
+
".coverage",
|
|
172
|
+
"htmlcov/",
|
|
173
|
+
".tox/",
|
|
174
|
+
".nox/",
|
|
175
|
+
".claude/",
|
|
176
|
+
"*.egg-info/",
|
|
177
|
+
"*.tgz",
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
content = "# Files to exclude from git archive export\n"
|
|
181
|
+
content += "# These files are tracked locally but not exported to remote archives\n\n"
|
|
182
|
+
|
|
183
|
+
for pattern in export_ignore_patterns:
|
|
184
|
+
content += f"{pattern} export-ignore\n"
|
|
185
|
+
|
|
186
|
+
gitattributes_path.write_text(content)
|
|
187
|
+
print(f"✓ Created {gitattributes_path}")
|
|
188
|
+
print("\nNote: This affects 'git archive' only.")
|
|
189
|
+
print("For selective push to remote, consider using a separate public branch.")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def npm_pack():
|
|
193
|
+
"""Create npm tarball with proper file filtering."""
|
|
194
|
+
eng_lang_tutor_dir = Path(__file__).parent.parent
|
|
195
|
+
|
|
196
|
+
# Update package.json files field for npm
|
|
197
|
+
package_json_path = eng_lang_tutor_dir / "package.json"
|
|
198
|
+
|
|
199
|
+
with open(package_json_path) as f:
|
|
200
|
+
package = json.load(f)
|
|
201
|
+
|
|
202
|
+
# Set files field to include only what we want
|
|
203
|
+
package["files"] = [
|
|
204
|
+
"scripts/",
|
|
205
|
+
"templates/",
|
|
206
|
+
"SKILL.md",
|
|
207
|
+
"README.md",
|
|
208
|
+
"requirements.txt",
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
# Remove tests from npm package
|
|
212
|
+
if "!tests/**" in package.get("files", []):
|
|
213
|
+
pass # Already excluded
|
|
214
|
+
|
|
215
|
+
print("npm pack would include:")
|
|
216
|
+
print(json.dumps(package.get("files", []), indent=2))
|
|
217
|
+
|
|
218
|
+
print("\nRun the following to create npm package:")
|
|
219
|
+
print(f" cd {eng_lang_tutor_dir} && npm pack")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def main():
|
|
223
|
+
parser = argparse.ArgumentParser(
|
|
224
|
+
description="Manage file visibility across local repo, remote repo, and npm package"
|
|
225
|
+
)
|
|
226
|
+
parser.add_argument(
|
|
227
|
+
"--check",
|
|
228
|
+
action="store_true",
|
|
229
|
+
help="Show file distribution across three layers"
|
|
230
|
+
)
|
|
231
|
+
parser.add_argument(
|
|
232
|
+
"--create-gitattributes",
|
|
233
|
+
action="store_true",
|
|
234
|
+
help="Create .gitattributes with export-ignore rules"
|
|
235
|
+
)
|
|
236
|
+
parser.add_argument(
|
|
237
|
+
"--npm-pack",
|
|
238
|
+
action="store_true",
|
|
239
|
+
help="Show npm pack configuration"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
args = parser.parse_args()
|
|
243
|
+
|
|
244
|
+
if args.check:
|
|
245
|
+
show_distribution()
|
|
246
|
+
elif args.create_gitattributes:
|
|
247
|
+
create_gitattributes()
|
|
248
|
+
elif args.npm_pack:
|
|
249
|
+
npm_pack()
|
|
250
|
+
else:
|
|
251
|
+
parser.print_help()
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
if __name__ == "__main__":
|
|
255
|
+
main()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(python3 -m pytest:*)",
|
|
5
|
-
"Bash(pytest:*)",
|
|
6
|
-
"Bash(find:*)",
|
|
7
|
-
"Bash(git add:*)",
|
|
8
|
-
"Bash(git commit:*)",
|
|
9
|
-
"Bash(git push)",
|
|
10
|
-
"mcp__brave-search__brave_web_search",
|
|
11
|
-
"mcp__web-search-prime__webSearchPrime",
|
|
12
|
-
"mcp__zread__get_repo_structure",
|
|
13
|
-
"mcp__zread__read_file",
|
|
14
|
-
"mcp__zread__search_doc",
|
|
15
|
-
"mcp__web-reader__webReader",
|
|
16
|
-
"WebSearch",
|
|
17
|
-
"mcp__fetch__fetch",
|
|
18
|
-
"Bash(wc:*)",
|
|
19
|
-
"Bash(python3 -m py_compile:*)"
|
|
20
|
-
]
|
|
21
|
-
}
|
|
22
|
-
}
|
package/.gitignore
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# Python
|
|
2
|
-
__pycache__/
|
|
3
|
-
*.py[cod]
|
|
4
|
-
*$py.class
|
|
5
|
-
*.so
|
|
6
|
-
.Python
|
|
7
|
-
.venv/
|
|
8
|
-
venv/
|
|
9
|
-
ENV/
|
|
10
|
-
|
|
11
|
-
# IDE
|
|
12
|
-
.idea/
|
|
13
|
-
.vscode/
|
|
14
|
-
*.swp
|
|
15
|
-
*.swo
|
|
16
|
-
|
|
17
|
-
# OS
|
|
18
|
-
.DS_Store
|
|
19
|
-
Thumbs.db
|
|
20
|
-
|
|
21
|
-
# Backups
|
|
22
|
-
backups/
|
|
23
|
-
*.bak
|
|
24
|
-
|
|
25
|
-
# User data
|
|
26
|
-
data/state.json
|
|
27
|
-
data/logs/
|
|
28
|
-
data/daily/
|
|
29
|
-
|
|
30
|
-
# npm
|
|
31
|
-
node_modules/
|
|
32
|
-
*.tgz
|
package/CHANGELOG.md
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
## [1.0.1] - 2025-02-27
|
|
6
|
-
|
|
7
|
-
### Changed
|
|
8
|
-
- Version bump for npm publishing (1.0.0 was previously unpublished)
|
|
9
|
-
|
|
10
|
-
## [1.0.0] - 2025-02-26
|
|
11
|
-
|
|
12
|
-
Initial release.
|
|
13
|
-
|
|
14
|
-
### Added
|
|
15
|
-
|
|
16
|
-
**Core Features:**
|
|
17
|
-
- Daily knowledge points with authentic American English expressions
|
|
18
|
-
- Quiz system with 4 question types (multiple choice, Chinglish fix, fill blank, dialogue completion)
|
|
19
|
-
- Duolingo-style gamification (XP, levels 1-20, streaks, badges, gems)
|
|
20
|
-
- Error notebook for tracking and reviewing mistakes
|
|
21
|
-
- 14-day content deduplication
|
|
22
|
-
|
|
23
|
-
**CEFR Support:**
|
|
24
|
-
- Complete CEFR level definitions (A1-C2) with Can-Do Statements
|
|
25
|
-
- 12 sample JSON files covering all 6 CEFR levels
|
|
26
|
-
- Content difficulty adjusted by user's CEFR level
|
|
27
|
-
|
|
28
|
-
**Audio:**
|
|
29
|
-
- TTS audio generation (Edge-TTS default, XunFei optional)
|
|
30
|
-
- Async audio generation with background threading
|
|
31
|
-
- Context manager pattern for reliable temp file cleanup
|
|
32
|
-
|
|
33
|
-
**Configuration:**
|
|
34
|
-
- 7-step onboarding flow
|
|
35
|
-
- Initialization guard check before content generation
|
|
36
|
-
- Centralized configuration constants
|
|
37
|
-
- Prompt version control via `_meta.prompt_version` field
|
|
38
|
-
|
|
39
|
-
**Documentation:**
|
|
40
|
-
- SKILL.md, README.md, CLAUDE.md
|
|
41
|
-
- OpenClaw deployment guide
|
|
42
|
-
- Prompt templates (keypoint generation, quiz generation, etc.)
|
package/CLAUDE.md
DELETED
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
# SKILL: eng-lang-tutor
|
|
2
|
-
|
|
3
|
-
## 1. 项目目标
|
|
4
|
-
|
|
5
|
-
本项目主要为Agent添加「地道美式英语导师」的Skill,配合Agent所在环境的cron job实现定期的知识推送与测验。
|
|
6
|
-
|
|
7
|
-
### 1.1. 学习目标
|
|
8
|
-
|
|
9
|
-
- 以提升地道的美式英语常用口语表达为主,兼顾听力与书面表达(正式场合)。
|
|
10
|
-
- 避免传统的Chinglish的硬翻式尴尬表达。
|
|
11
|
-
- 融入轻量游戏化教学,覆盖影视、新闻、游戏、体育、职场、社交、生活等用户感兴趣的场景来提升用户的粘性。
|
|
12
|
-
|
|
13
|
-
### 1.2. 语言范围
|
|
14
|
-
|
|
15
|
-
- 以美式英语为主,包含俚语、口头语、缩写(gonna, gotta, kinda 等),能帮助用户区分「正式 vs 口语」。
|
|
16
|
-
|
|
17
|
-
### 1.3. 技能维度
|
|
18
|
-
|
|
19
|
-
- 涉及词汇/短语、固定搭配、场景句型等口语与书面语中的表达方式。
|
|
20
|
-
|
|
21
|
-
## 2. 项目范围
|
|
22
|
-
|
|
23
|
-
### 2.1. MVP版本功能清单
|
|
24
|
-
|
|
25
|
-
#### 2.1.1 导师模式
|
|
26
|
-
|
|
27
|
-
Agent结合难度与偏好的设置,以及用户的历史学习进度动态生成并调整个性化的教学大纲与教学内容:
|
|
28
|
-
|
|
29
|
-
| 功能 | 描述 | 频次 | 对应脚本 |
|
|
30
|
-
|-----|------|-----|---------|
|
|
31
|
-
| **知识点** | 地道美式英语日常沟通素材,包含场景、可替换说法、Chinglish陷阱+修正 | 每天 | state_manager.py |
|
|
32
|
-
| **Quiz** | 3道轻量测验:1多选+1Chinglish修正+1随机(填空/对话)| 每天 | scorer.py |
|
|
33
|
-
| **错题本** | 记录错误答案,支持手动添加,便于针对性复习 | 实时 | state_manager.py |
|
|
34
|
-
|
|
35
|
-
**Quiz 结构:**
|
|
36
|
-
|
|
37
|
-
| 题型 | 描述 | XP值 | 每日Quiz |
|
|
38
|
-
|-----|------|-----|---------|
|
|
39
|
-
| multiple_choice | 四选一,测试表达识别 | 10 | 1 (必选) |
|
|
40
|
-
| chinglish_fix | 识别并修正Chinglish表达 | 15 | 1 (必选) |
|
|
41
|
-
| fill_blank | 填空,完成对话 | 12 | 0-1 (随机) |
|
|
42
|
-
| dialogue_completion | 选择合适的对话回应 | 15 | 0-1 (随机) |
|
|
43
|
-
|
|
44
|
-
- **每日Quiz**: 3题,约37 XP,答对2题即过关
|
|
45
|
-
|
|
46
|
-
#### 2.1.2 状态管理
|
|
47
|
-
|
|
48
|
-
保障整个教学过程可备份、可追溯、可回滚:
|
|
49
|
-
|
|
50
|
-
| 组件 | 文件 | 用途 |
|
|
51
|
-
|-----|------|-----|
|
|
52
|
-
| 核心状态 | state.json | streak/xp/偏好/近期主题/错题本 |
|
|
53
|
-
| 事件日志 | logs/events_YYYY-MM.jsonl | 追加式事件流水账 |
|
|
54
|
-
| 每日内容 | daily/YYYY-MM-DD/*.json | 知识点/Quiz/用户答案 |
|
|
55
|
-
|
|
56
|
-
#### 2.1.3 成就体系(Duolingo风格)
|
|
57
|
-
|
|
58
|
-
**双等级体系说明:**
|
|
59
|
-
- **能力等级 (CEFR)**:A1-C2,决定内容难度,与 App 无关的绝对语言能力
|
|
60
|
-
- **活跃等级 (Level)**:1-20,衡量 App 使用深度,通过 XP 累积升级
|
|
61
|
-
|
|
62
|
-
| 活跃等级 | 阶段 | XP 需求 |
|
|
63
|
-
|---------|------|---------|
|
|
64
|
-
| 1-5 | 启程者 (Starter) | 0-350 |
|
|
65
|
-
| 6-10 | 行路人 (Traveler) | 550-2000 |
|
|
66
|
-
| 11-15 | 探索者 (Explorer) | 2600-6000 |
|
|
67
|
-
| 16-20 | 开拓者 (Pioneer) | 7200-15000 |
|
|
68
|
-
|
|
69
|
-
| 组件 | 描述 |
|
|
70
|
-
|-----|------|
|
|
71
|
-
| **XP & 等级** | 1-20级,XP累积升级(启程者 → 行路人 → 探索者 → 开拓者) |
|
|
72
|
-
| **连胜系统** | 连续学习天数,支持连胜冻结(50宝石) |
|
|
73
|
-
| **徽章系统** | First Steps、Week Warrior、Month Master、Perfect 10、Vocab Hunter、Error Slayer |
|
|
74
|
-
| **宝石系统** | 用于购买连胜冻结、提示等 |
|
|
75
|
-
|
|
76
|
-
**徽章详情:**
|
|
77
|
-
|
|
78
|
-
| 徽章 | 触发条件 | 宝石奖励 |
|
|
79
|
-
|-----|---------|---------|
|
|
80
|
-
| First Steps | 完成首次Quiz | 10 |
|
|
81
|
-
| Week Warrior | 7天连胜 | 25 |
|
|
82
|
-
| Month Master | 30天连胜 | 100 |
|
|
83
|
-
| Perfect 10 | 10次完美Quiz | 50 |
|
|
84
|
-
| Vocab Hunter | 学习100个表达 | 75 |
|
|
85
|
-
| Error Slayer | 清除30个错题 | 30 |
|
|
86
|
-
|
|
87
|
-
#### 2.1.4 系统配置
|
|
88
|
-
|
|
89
|
-
| 配置项 | 描述 | 默认值 |
|
|
90
|
-
|-------|------|-------|
|
|
91
|
-
| CEFR等级 | A1-C2语言级别 | B1 |
|
|
92
|
-
| 口语/书面比例 | 口语表达占比 | 70% |
|
|
93
|
-
| 主题配比 | movies/news/gaming/sports/workplace/social/daily_life | 各主题权重 |
|
|
94
|
-
| 导师风格 | humorous/rigorous/casual/professional | humorous |
|
|
95
|
-
| 去重天数 | 避免重复内容的时间窗口 | 14天 |
|
|
96
|
-
|
|
97
|
-
**配置常量:** 集中定义于 `scripts/core/constants.py`(宝石消耗、XP值、超时阈值等)
|
|
98
|
-
|
|
99
|
-
### 2.2. MVP版本不做 / 延后
|
|
100
|
-
|
|
101
|
-
- **周周练**:5道综合性场景练习,覆盖本周知识点,约62 XP,答对3题即过关。计划周日推送。
|
|
102
|
-
- **语音模式**:支持tts便于用户跟读,支持asr便于导师对口语表达进行打分。
|
|
103
|
-
|
|
104
|
-
### 2.3. 项目约束
|
|
105
|
-
|
|
106
|
-
**核心原则:别问"怎么翻译",先问"这个场景美国人会怎么说",会让你立刻更像美国人。**
|
|
107
|
-
|
|
108
|
-
#### 2.3.1 输出格式约束
|
|
109
|
-
|
|
110
|
-
- **必须输出合法 JSON**(不要 markdown,不要代码块,不要多余文本)
|
|
111
|
-
- 所有输出必须符合 `templates/` 目录下的 JSON Schema
|
|
112
|
-
|
|
113
|
-
#### 2.3.2 内容质量约束
|
|
114
|
-
|
|
115
|
-
- 语言风格:**地道美式**、轻松、可用、短句为主
|
|
116
|
-
- 每条知识点必须包含:**场景、可替换说法、Chinglish 陷阱 + 修正**
|
|
117
|
-
- 包含发音提示(gonna, gotta, wanna 等自然发音)
|
|
118
|
-
- 提供文化背景和使用场景说明
|
|
119
|
-
|
|
120
|
-
#### 2.3.3 去重策略(14天窗口)
|
|
121
|
-
|
|
122
|
-
使用 `scripts/utils/dedup.py` 实现三层去重:
|
|
123
|
-
|
|
124
|
-
| 层级 | 方法 | 描述 |
|
|
125
|
-
|-----|------|-----|
|
|
126
|
-
| 1 | topic_fingerprint | 主题指纹匹配 |
|
|
127
|
-
| 2 | 表达重叠 | 相同短语超过50%视为重复 |
|
|
128
|
-
| 3 | 词根匹配 | 核心词汇结构相似 |
|
|
129
|
-
|
|
130
|
-
#### 2.3.4 反直译原则
|
|
131
|
-
|
|
132
|
-
- 优先给"美国人会怎么说",而不是"中文怎么翻英文"
|
|
133
|
-
- 参考 `references/resources.md` 中的主题资源库
|
|
134
|
-
- 使用 `references/prompt_templates.md` 中的LLM提示模板
|
|
135
|
-
|
|
136
|
-
## 3. 技术栈与SKILL结构
|
|
137
|
-
|
|
138
|
-
### 3.1. 技术栈与输出格式
|
|
139
|
-
|
|
140
|
-
- 技术栈:Python 3.x
|
|
141
|
-
- 触发方式:cron 触发 Agent 调用 Skill
|
|
142
|
-
|
|
143
|
-
### 3.2. 目录结构
|
|
144
|
-
|
|
145
|
-
```
|
|
146
|
-
eng-lang-tutor/
|
|
147
|
-
├── SKILL.md # 核心技能文档
|
|
148
|
-
├── CLAUDE.md # 项目说明(本文件)
|
|
149
|
-
├── scripts/
|
|
150
|
-
│ ├── __init__.py # 包入口,重导出核心类
|
|
151
|
-
│ ├── core/
|
|
152
|
-
│ │ ├── state_manager.py # 状态持久化与事件日志
|
|
153
|
-
│ │ ├── error_notebook.py # 错题本管理
|
|
154
|
-
│ │ ├── scorer.py # 答案评估与XP计算
|
|
155
|
-
│ │ ├── gamification.py # 连胜/等级/徽章
|
|
156
|
-
│ │ └── constants.py # 共享常量和工具函数
|
|
157
|
-
│ ├── cli/
|
|
158
|
-
│ │ ├── cli.py # CLI入口点
|
|
159
|
-
│ │ └── command_parser.py # 命令解析
|
|
160
|
-
│ ├── audio/
|
|
161
|
-
│ │ ├── tts/ # TTS引擎(Coqui/Azure/Edge)
|
|
162
|
-
│ │ ├── composer.py # 音频合成
|
|
163
|
-
│ │ ├── converter.py # 格式转换
|
|
164
|
-
│ │ ├── feishu_voice.py # 飞书语音发送
|
|
165
|
-
│ │ └── utils.py # 音频工具函数
|
|
166
|
-
│ ├── scheduling/
|
|
167
|
-
│ │ └── cron_push.py # 定时推送
|
|
168
|
-
│ └── utils/
|
|
169
|
-
│ ├── dedup.py # 14天去重逻辑
|
|
170
|
-
│ └── helpers.py # 通用工具函数
|
|
171
|
-
├── templates/
|
|
172
|
-
│ ├── state_schema.json # 状态 JSON Schema
|
|
173
|
-
│ ├── keypoint_schema.json # 知识点 JSON Schema
|
|
174
|
-
│ └── quiz_schema.json # Quiz JSON Schema
|
|
175
|
-
├── references/
|
|
176
|
-
│ ├── resources.md # 主题化英语学习资源库
|
|
177
|
-
│ └── prompt_templates.md # LLM Prompt 模板
|
|
178
|
-
├── examples/ # 示例文件(按 CEFR 级别命名)
|
|
179
|
-
│ ├── sample_keypoint_*.json # 知识点示例 (a1-c2)
|
|
180
|
-
│ └── sample_quiz_*.json # Quiz示例 (a1-c2)
|
|
181
|
-
└── (数据存储在外部目录)
|
|
182
|
-
~/.openclaw/state/eng-lang-tutor/ # 实际数据存储位置
|
|
183
|
-
├── state.json # 运行时状态
|
|
184
|
-
├── logs/
|
|
185
|
-
│ └── events_YYYY-MM.jsonl
|
|
186
|
-
└── daily/
|
|
187
|
-
└── YYYY-MM-DD/
|
|
188
|
-
├── keypoint.json
|
|
189
|
-
├── quiz.json
|
|
190
|
-
└── user_answers.json
|
|
191
|
-
|
|
192
|
-
注: 可通过 OPENCLAW_STATE_DIR 环境变量自定义存储位置
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
## 4. 功能切片实现
|
|
196
|
-
|
|
197
|
-
### 4.1 功能切片与脚本对应
|
|
198
|
-
|
|
199
|
-
| 切片 | 对应脚本 | 输入 | 输出 |
|
|
200
|
-
|-----|---------|------|-----|
|
|
201
|
-
| 状态加载/保存 | core/state_manager.py | - | state.json |
|
|
202
|
-
| 事件日志 | core/state_manager.py | event_type, data | events_YYYY-MM.jsonl |
|
|
203
|
-
| 每日内容保存 | core/state_manager.py | content_type, content | daily/YYYY-MM-DD/*.json |
|
|
204
|
-
| 答案评估 | core/scorer.py | quiz.json + user_answers | results + updated state |
|
|
205
|
-
| XP计算 | core/scorer.py | correct_count, streak | XP with multiplier |
|
|
206
|
-
| 连胜更新 | core/gamification.py | study_date | new_streak |
|
|
207
|
-
| 等级更新 | core/gamification.py | XP | level |
|
|
208
|
-
| 徽章检查 | core/gamification.py | progress | new_badges |
|
|
209
|
-
| 去重检查 | utils/dedup.py | new_content, recent_content | is_duplicate |
|
|
210
|
-
| 异步音频生成 | core/state_manager.py | keypoint.json | audio/*.mp3 |
|
|
211
|
-
|
|
212
|
-
### 4.2 核心工作流
|
|
213
|
-
|
|
214
|
-
#### 4.2.1 每日知识点生成
|
|
215
|
-
|
|
216
|
-
```
|
|
217
|
-
1. state_manager.load_state()
|
|
218
|
-
2. dedup.get_excluded_topics(state)
|
|
219
|
-
3. LLM生成知识点(使用prompt_templates.md)
|
|
220
|
-
4. dedup.check_duplicate(new_content, recent_content)
|
|
221
|
-
5. 验证JSON Schema
|
|
222
|
-
6. state_manager.save_daily_content('keypoint', content)
|
|
223
|
-
7. state_manager.append_event('keypoint_generated', {...})
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
#### 4.2.2 Quiz评估流程
|
|
227
|
-
|
|
228
|
-
```
|
|
229
|
-
1. state_manager.load_daily_content('quiz')
|
|
230
|
-
2. 读取 user_answers.json
|
|
231
|
-
3. scorer.evaluate_quiz(quiz, answers, state)
|
|
232
|
-
4. gamification.update_streak(state, today)
|
|
233
|
-
5. gamification.update_level(state)
|
|
234
|
-
6. gamification.check_badges(state)
|
|
235
|
-
7. state_manager.save_state(state)
|
|
236
|
-
8. state_manager.append_event('quiz_completed', results)
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
## 5. 开发准则
|
|
240
|
-
|
|
241
|
-
### 5.1 Skill文档规范
|
|
242
|
-
|
|
243
|
-
- SKILL.md文档要详细但不能过长,聚焦核心步骤,细节可以放在代码示例里。建议**800-1500 tokens**。
|
|
244
|
-
- Skills 必须包含可执行的步骤、代码示例、验证检查点。
|
|
245
|
-
- 设计 Skills 时考虑复用性(模块化)。一个 Skill 解决一类问题,不是一次性的硬编码。
|
|
246
|
-
|
|
247
|
-
### 5.2 确定性验证器
|
|
248
|
-
|
|
249
|
-
每个任务都有**确定性验证器**(deterministic verifier):这不是用 LLM 评判,而是程序化断言。
|
|
250
|
-
|
|
251
|
-
| 验证项 | 验证方法 | 对应工具 |
|
|
252
|
-
|-------|---------|---------|
|
|
253
|
-
| JSON格式 | json.loads() | Python标准库 |
|
|
254
|
-
| JSON Schema | schema validation | templates/*.json |
|
|
255
|
-
| XP计算 | 公式验证 | scorer.py |
|
|
256
|
-
| 连胜更新 | 日期比较 | gamification.py |
|
|
257
|
-
| 去重检查 | 指纹/表达匹配 | dedup.py |
|
|
258
|
-
|
|
259
|
-
### 5.3 聚焦核心路径
|
|
260
|
-
|
|
261
|
-
不要过度工程化:比如应该聚焦 80% 场景的核心路径,而非覆盖尽可能多的edge cases。
|
|
262
|
-
|
|
263
|
-
### 5.4 可复用资源
|
|
264
|
-
|
|
265
|
-
| 资源 | 位置 | 用途 |
|
|
266
|
-
|-----|------|-----|
|
|
267
|
-
| skill-creator | github.com/anthropics/skills | 自动生成skill骨架 |
|
|
268
|
-
| awesome-language-learning | github.com/Vuizur/awesome-language-learning | 语言学习工具集合 |
|
|
269
|
-
| 主题资源库 | references/resources.md | 美剧/新闻/游戏/体育/职场/生活 |
|
|
270
|
-
| LLM提示模板 | references/prompt_templates.md | 知识点/Quiz生成模板 |
|
|
271
|
-
|
|
272
|
-
### 5.5 行动准则
|
|
273
|
-
|
|
274
|
-
- 未经允许不得擅自执行git commit、push和publish等相关动作
|
|
275
|
-
- 针对耗费较长时间做debug或者反复掉坑的情况,需要及时总结复盘,并将经验精炼到本文档中
|
package/bin/eng-lang-tutor.js
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* eng-lang-tutor CLI
|
|
5
|
-
*
|
|
6
|
-
* Commands:
|
|
7
|
-
* install - Install the skill to ~/.openclaw/skills/eng-lang-tutor/
|
|
8
|
-
* uninstall - Remove the skill from ~/.openclaw/skills/eng-lang-tutor/
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const fs = require('fs');
|
|
13
|
-
const os = require('os');
|
|
14
|
-
|
|
15
|
-
const SKILL_NAME = 'eng-lang-tutor';
|
|
16
|
-
const SKILLS_DIR = path.join(os.homedir(), '.openclaw', 'skills');
|
|
17
|
-
const SKILL_TARGET = path.join(SKILLS_DIR, SKILL_NAME);
|
|
18
|
-
|
|
19
|
-
// Get the package root directory (where package.json is)
|
|
20
|
-
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
21
|
-
|
|
22
|
-
function getSkillSourceDir() {
|
|
23
|
-
return PACKAGE_ROOT;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function install() {
|
|
27
|
-
console.log(`Installing ${SKILL_NAME} skill...`);
|
|
28
|
-
|
|
29
|
-
const sourceDir = getSkillSourceDir();
|
|
30
|
-
|
|
31
|
-
// Check if source directory exists
|
|
32
|
-
if (!fs.existsSync(sourceDir)) {
|
|
33
|
-
console.error(`Error: Source directory not found: ${sourceDir}`);
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Create skills directory if it doesn't exist
|
|
38
|
-
if (!fs.existsSync(SKILLS_DIR)) {
|
|
39
|
-
fs.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
40
|
-
console.log(`Created skills directory: ${SKILLS_DIR}`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Remove existing installation if present
|
|
44
|
-
if (fs.existsSync(SKILL_TARGET)) {
|
|
45
|
-
console.log(`Removing existing installation at ${SKILL_TARGET}...`);
|
|
46
|
-
fs.rmSync(SKILL_TARGET, { recursive: true, force: true });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Create target directory
|
|
50
|
-
fs.mkdirSync(SKILL_TARGET, { recursive: true });
|
|
51
|
-
|
|
52
|
-
// Files and directories to copy
|
|
53
|
-
const itemsToCopy = [
|
|
54
|
-
'scripts',
|
|
55
|
-
'templates',
|
|
56
|
-
'references',
|
|
57
|
-
'examples',
|
|
58
|
-
'docs',
|
|
59
|
-
'SKILL.md',
|
|
60
|
-
'CLAUDE.md',
|
|
61
|
-
'README.md',
|
|
62
|
-
'README_EN.md',
|
|
63
|
-
'requirements.txt'
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
// Copy each item
|
|
67
|
-
for (const item of itemsToCopy) {
|
|
68
|
-
const sourcePath = path.join(sourceDir, item);
|
|
69
|
-
const targetPath = path.join(SKILL_TARGET, item);
|
|
70
|
-
|
|
71
|
-
if (!fs.existsSync(sourcePath)) {
|
|
72
|
-
console.log(` Skipping ${item} (not found)`);
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
if (fs.statSync(sourcePath).isDirectory()) {
|
|
78
|
-
copyDir(sourcePath, targetPath);
|
|
79
|
-
} else {
|
|
80
|
-
fs.copyFileSync(sourcePath, targetPath);
|
|
81
|
-
}
|
|
82
|
-
console.log(` Copied ${item}`);
|
|
83
|
-
} catch (err) {
|
|
84
|
-
console.error(` Error copying ${item}: ${err.message}`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
console.log(`\n✓ ${SKILL_NAME} installed to ${SKILL_TARGET}`);
|
|
89
|
-
console.log('\nNext steps:');
|
|
90
|
-
console.log(' 1. Install Python dependencies:');
|
|
91
|
-
console.log(` pip install -r ${SKILL_TARGET}/requirements.txt`);
|
|
92
|
-
console.log(' 2. Restart your OpenClaw agent');
|
|
93
|
-
console.log(' 3. Configure the skill through the onboarding flow');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function uninstall() {
|
|
97
|
-
console.log(`Uninstalling ${SKILL_NAME} skill...`);
|
|
98
|
-
|
|
99
|
-
if (!fs.existsSync(SKILL_TARGET)) {
|
|
100
|
-
console.log(`${SKILL_NAME} is not installed.`);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
fs.rmSync(SKILL_TARGET, { recursive: true, force: true });
|
|
106
|
-
console.log(`✓ ${SKILL_NAME} removed from ${SKILL_TARGET}`);
|
|
107
|
-
console.log('\nNote: Your learning data is preserved at:');
|
|
108
|
-
console.log(' ~/.openclaw/state/eng-lang-tutor/');
|
|
109
|
-
console.log('\nTo completely remove all data, run:');
|
|
110
|
-
console.log(' rm -rf ~/.openclaw/state/eng-lang-tutor');
|
|
111
|
-
} catch (err) {
|
|
112
|
-
console.error(`Error uninstalling: ${err.message}`);
|
|
113
|
-
process.exit(1);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function copyDir(src, dest) {
|
|
118
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
119
|
-
|
|
120
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
121
|
-
|
|
122
|
-
for (const entry of entries) {
|
|
123
|
-
const srcPath = path.join(src, entry.name);
|
|
124
|
-
const destPath = path.join(dest, entry.name);
|
|
125
|
-
|
|
126
|
-
if (entry.isDirectory()) {
|
|
127
|
-
copyDir(srcPath, destPath);
|
|
128
|
-
} else {
|
|
129
|
-
fs.copyFileSync(srcPath, destPath);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function showHelp() {
|
|
135
|
-
console.log(`
|
|
136
|
-
eng-lang-tutor - English Language Tutor Skill for OpenClaw
|
|
137
|
-
|
|
138
|
-
Usage:
|
|
139
|
-
eng-lang-tutor <command>
|
|
140
|
-
|
|
141
|
-
Commands:
|
|
142
|
-
install Install the skill to ~/.openclaw/skills/eng-lang-tutor/
|
|
143
|
-
uninstall Remove the skill (preserves learning data)
|
|
144
|
-
help Show this help message
|
|
145
|
-
|
|
146
|
-
Environment Variables:
|
|
147
|
-
OPENCLAW_STATE_DIR Custom directory for learning data
|
|
148
|
-
(default: ~/.openclaw/state/eng-lang-tutor)
|
|
149
|
-
|
|
150
|
-
Examples:
|
|
151
|
-
eng-lang-tutor install
|
|
152
|
-
eng-lang-tutor uninstall
|
|
153
|
-
`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Main
|
|
157
|
-
const command = process.argv[2];
|
|
158
|
-
|
|
159
|
-
switch (command) {
|
|
160
|
-
case 'install':
|
|
161
|
-
install();
|
|
162
|
-
break;
|
|
163
|
-
case 'uninstall':
|
|
164
|
-
uninstall();
|
|
165
|
-
break;
|
|
166
|
-
case 'help':
|
|
167
|
-
case '--help':
|
|
168
|
-
case '-h':
|
|
169
|
-
showHelp();
|
|
170
|
-
break;
|
|
171
|
-
default:
|
|
172
|
-
if (command) {
|
|
173
|
-
console.error(`Unknown command: ${command}`);
|
|
174
|
-
}
|
|
175
|
-
showHelp();
|
|
176
|
-
process.exit(command ? 1 : 0);
|
|
177
|
-
}
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
# OpenClaw 部署指南
|
|
2
|
-
|
|
3
|
-
本文档说明如何将 `eng-lang-tutor` skill 部署到 OpenClaw 服务器,并通过 Discord 等渠道使用。
|
|
4
|
-
|
|
5
|
-
## 1. 前置条件
|
|
6
|
-
|
|
7
|
-
- 一台已安装 OpenClaw 的服务器
|
|
8
|
-
- OpenClaw Gateway 正在运行
|
|
9
|
-
- 已配置好 Discord Bot(可选其他渠道)
|
|
10
|
-
|
|
11
|
-
## 2. 部署步骤
|
|
12
|
-
|
|
13
|
-
### 2.1 上传 Skill 到服务器
|
|
14
|
-
|
|
15
|
-
**方式 A:使用 SCP 上传**
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
# 在本地机器上执行
|
|
19
|
-
scp -r /path/to/eng-lang-tutor user@your-server:~/.openclaw/skills/
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
**方式 B:使用 Git Clone**
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
# 在服务器上执行
|
|
26
|
-
cd ~/.openclaw/skills/
|
|
27
|
-
git clone https://github.com/rookiestar/eng-lang-tutor.git
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### 2.2 确认目录结构
|
|
31
|
-
|
|
32
|
-
确保目录结构如下:
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
~/.openclaw/skills/eng-lang-tutor/
|
|
36
|
-
├── SKILL.md # 核心技能文件(必需)
|
|
37
|
-
├── scripts/ # Python 脚本
|
|
38
|
-
│ ├── core/ # 核心模块
|
|
39
|
-
│ │ ├── state_manager.py
|
|
40
|
-
│ │ ├── scorer.py
|
|
41
|
-
│ │ ├── gamification.py
|
|
42
|
-
│ │ ├── constants.py
|
|
43
|
-
│ │ └── error_notebook.py
|
|
44
|
-
│ ├── cli/ # 命令行模块
|
|
45
|
-
│ ├── scheduling/ # 调度模块
|
|
46
|
-
│ ├── utils/ # 工具模块
|
|
47
|
-
│ └── audio/ # 音频模块
|
|
48
|
-
├── templates/ # JSON Schema + Prompt 模板
|
|
49
|
-
├── references/ # 参考资源
|
|
50
|
-
├── examples/ # 示例文件(按 CEFR 级别)
|
|
51
|
-
└── (数据存储在外部目录)
|
|
52
|
-
~/.openclaw/state/eng-lang-tutor/ # 运行时数据(自动创建)
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### 2.3 验证 Skill 加载
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
# 查看已加载的 skills
|
|
59
|
-
openclaw skills list
|
|
60
|
-
|
|
61
|
-
# 查看特定 skill 详情
|
|
62
|
-
openclaw skills info eng-lang-tutor
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## 3. 配置 Discord 渠道
|
|
66
|
-
|
|
67
|
-
### 3.1 创建 Discord Bot
|
|
68
|
-
|
|
69
|
-
1. 访问 [Discord Developer Portal](https://discord.com/developers/applications)
|
|
70
|
-
2. 创建新应用,命名为你的 Bot
|
|
71
|
-
3. 进入 Bot 页面,创建 Bot 并获取 Token
|
|
72
|
-
4. 启用 Message Content Intent
|
|
73
|
-
|
|
74
|
-
### 3.2 配置 OpenClaw
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
# 设置 Discord Token
|
|
78
|
-
openclaw config set discord.token YOUR_BOT_TOKEN
|
|
79
|
-
|
|
80
|
-
# 设置服务器 ID(可选)
|
|
81
|
-
openclaw config set discord.guildId YOUR_SERVER_ID
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### 3.3 邀请 Bot 到服务器
|
|
85
|
-
|
|
86
|
-
1. 在 Discord Developer Portal 中,进入 OAuth2 > URL Generator
|
|
87
|
-
2. 选择 `bot` 和 `applications.commands` scope
|
|
88
|
-
3. 选择所需权限(Send Messages, Read Messages 等)
|
|
89
|
-
4. 复制生成的邀请链接并在浏览器中打开
|
|
90
|
-
5. 选择要添加 Bot 的服务器
|
|
91
|
-
|
|
92
|
-
### 3.4 完成配对
|
|
93
|
-
|
|
94
|
-
首次与 Bot 对话时,会收到配对码:
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
# 在服务器上执行配对
|
|
98
|
-
openclaw pairing approve discord YOUR_PAIRING_CODE
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## 4. 使用 Skill
|
|
102
|
-
|
|
103
|
-
### 4.1 通过 Discord 使用
|
|
104
|
-
|
|
105
|
-
在 Discord 中与 Bot 对话,触发英语学习功能:
|
|
106
|
-
|
|
107
|
-
```
|
|
108
|
-
# 触发今日知识点
|
|
109
|
-
今天有什么英语知识点?
|
|
110
|
-
|
|
111
|
-
# 触发 Quiz
|
|
112
|
-
给我出个 Quiz
|
|
113
|
-
|
|
114
|
-
# 查看学习进度
|
|
115
|
-
我的学习进度怎么样?
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### 4.2 设置定时推送(cron)
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
# 编辑 crontab
|
|
122
|
-
crontab -e
|
|
123
|
-
|
|
124
|
-
# 添加每日定时推送
|
|
125
|
-
# 早上 6:45 推送知识点
|
|
126
|
-
45 6 * * * openclaw system event --text "Use eng-lang-tutor skill. Push today's keypoint." --mode now
|
|
127
|
-
|
|
128
|
-
# 晚上 22:45 推送 Quiz
|
|
129
|
-
45 22 * * * openclaw system event --text "Use eng-lang-tutor skill. Push today's quiz invitation." --mode now
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## 5. 进阶配置
|
|
133
|
-
|
|
134
|
-
### 5.1 配置用户偏好
|
|
135
|
-
|
|
136
|
-
在 Discord 中与 Bot 对话设置:
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
设置我的 CEFR 等级为 B2
|
|
140
|
-
我的导师风格设为严谨
|
|
141
|
-
调整主题配比,增加职场内容的比例
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### 5.2 配置环境变量
|
|
145
|
-
|
|
146
|
-
在服务器上设置 API 密钥(如果使用外部 LLM):
|
|
147
|
-
|
|
148
|
-
```bash
|
|
149
|
-
# 编辑 OpenClaw 配置
|
|
150
|
-
openclaw config set model.provider anthropic
|
|
151
|
-
openclaw config set model.api_key YOUR_API_KEY
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### 5.3 数据持久化
|
|
155
|
-
|
|
156
|
-
数据存储在 `~/.openclaw/state/eng-lang-tutor/` 目录,确保有正确的写入权限:
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
chmod -R 755 ~/.openclaw/state/eng-lang-tutor/
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
可通过环境变量 `OPENCLAW_STATE_DIR` 自定义数据目录。
|
|
163
|
-
|
|
164
|
-
## 6. 故障排查
|
|
165
|
-
|
|
166
|
-
### 6.1 Skill 未加载
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
# 检查 skill 目录
|
|
170
|
-
ls -la ~/.openclaw/skills/eng-lang-tutor/
|
|
171
|
-
|
|
172
|
-
# 检查 SKILL.md 是否存在
|
|
173
|
-
cat ~/.openclaw/skills/eng-lang-tutor/SKILL.md
|
|
174
|
-
|
|
175
|
-
# 重启 Gateway
|
|
176
|
-
openclaw gateway restart
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### 6.2 Discord 无响应
|
|
180
|
-
|
|
181
|
-
```bash
|
|
182
|
-
# 检查 Gateway 日志
|
|
183
|
-
openclaw logs
|
|
184
|
-
|
|
185
|
-
# 验证 Discord Token
|
|
186
|
-
openclaw config get discord.token
|
|
187
|
-
|
|
188
|
-
# 检查 Bot 状态
|
|
189
|
-
openclaw doctor
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### 6.3 Python 脚本错误
|
|
193
|
-
|
|
194
|
-
```bash
|
|
195
|
-
# 检查 Python 环境
|
|
196
|
-
python3 --version
|
|
197
|
-
|
|
198
|
-
# 安装依赖(如有需要)
|
|
199
|
-
pip3 install -r ~/.openclaw/skills/eng-lang-tutor/requirements.txt
|
|
200
|
-
|
|
201
|
-
# 手动测试脚本
|
|
202
|
-
cd ~/.openclaw/skills/eng-lang-tutor/
|
|
203
|
-
python3 -m scripts.cli.cli stats
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
## 7. 架构图
|
|
207
|
-
|
|
208
|
-
```
|
|
209
|
-
┌─────────────────────────────────────────────────────────┐
|
|
210
|
-
│ Discord │
|
|
211
|
-
│ (用户交互) │
|
|
212
|
-
└─────────────────────────┬───────────────────────────────┘
|
|
213
|
-
│
|
|
214
|
-
▼
|
|
215
|
-
┌─────────────────────────────────────────────────────────┐
|
|
216
|
-
│ OpenClaw Gateway │
|
|
217
|
-
│ (消息路由 + 会话管理) │
|
|
218
|
-
└─────────────────────────┬───────────────────────────────┘
|
|
219
|
-
│
|
|
220
|
-
▼
|
|
221
|
-
┌─────────────────────────────────────────────────────────┐
|
|
222
|
-
│ eng-lang-tutor Skill │
|
|
223
|
-
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
224
|
-
│ │ SKILL.md │ │ scripts/ │ │ ~/.openclaw │ │
|
|
225
|
-
│ │ (技能描述) │ │ (Python代码) │ │ (状态存储) │ │
|
|
226
|
-
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
227
|
-
└─────────────────────────────────────────────────────────┘
|
|
228
|
-
│
|
|
229
|
-
▼
|
|
230
|
-
┌─────────────────────────────────────────────────────────┐
|
|
231
|
-
│ LLM Provider │
|
|
232
|
-
│ (Claude/GPT/Qwen 等) │
|
|
233
|
-
└─────────────────────────────────────────────────────────┘
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
## 8. 参考链接
|
|
237
|
-
|
|
238
|
-
- [OpenClaw 官方文档](https://docs.openclaw.ai)
|
|
239
|
-
- [OpenClaw GitHub](https://github.com/openclaw/openclaw)
|
|
240
|
-
- [Discord 开发者门户](https://discord.com/developers/applications)
|
|
241
|
-
- [OpenClaw Skills 开发指南](https://docs.openclaw.ai/skills)
|