realtimex-deeptutor 0.5.0.post2__py3-none-any.whl → 0.5.0.post3__py3-none-any.whl
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.
- {realtimex_deeptutor-0.5.0.post2.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/METADATA +1 -1
- {realtimex_deeptutor-0.5.0.post2.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/RECORD +17 -8
- {realtimex_deeptutor-0.5.0.post2.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/top_level.txt +1 -0
- scripts/__init__.py +1 -0
- scripts/audit_prompts.py +179 -0
- scripts/check_install.py +460 -0
- scripts/generate_roster.py +327 -0
- scripts/install_all.py +653 -0
- scripts/migrate_kb.py +655 -0
- scripts/start.py +807 -0
- scripts/start_web.py +632 -0
- scripts/sync_prompts_from_en.py +147 -0
- src/cli/start.py +58 -66
- src/services/config/unified_config.py +2 -2
- {realtimex_deeptutor-0.5.0.post2.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/WHEEL +0 -0
- {realtimex_deeptutor-0.5.0.post2.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/entry_points.txt +0 -0
- {realtimex_deeptutor-0.5.0.post2.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Semi-automatic sync of prompt structure from prompts/en to prompts/zh|prompts/cn.
|
|
6
|
+
|
|
7
|
+
Behavior (safe by default):
|
|
8
|
+
- Dry-run: prints what would be added
|
|
9
|
+
- With --write: adds missing keys to zh/cn files without overwriting existing values
|
|
10
|
+
- With --create-missing-files: creates missing zh/cn files using the en structure
|
|
11
|
+
|
|
12
|
+
NOTE: This tool does NOT translate. It inserts TODO markers to be manually rewritten in Chinese.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
import sys
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
import yaml
|
|
23
|
+
|
|
24
|
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
25
|
+
AGENTS_DIR = PROJECT_ROOT / "src" / "agents"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _load_yaml(path: Path) -> Any:
|
|
29
|
+
with open(path, encoding="utf-8") as f:
|
|
30
|
+
return yaml.safe_load(f) or {}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _dump_yaml(path: Path, obj: Any) -> None:
|
|
34
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
36
|
+
yaml.safe_dump(obj, f, allow_unicode=True, sort_keys=False)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _merge_missing(en_obj: Any, zh_obj: Any) -> tuple[Any, int]:
|
|
40
|
+
"""
|
|
41
|
+
Add missing keys from en_obj into zh_obj without overwriting existing zh content.
|
|
42
|
+
Returns (new_obj, added_count).
|
|
43
|
+
"""
|
|
44
|
+
added = 0
|
|
45
|
+
|
|
46
|
+
if isinstance(en_obj, dict):
|
|
47
|
+
if not isinstance(zh_obj, dict):
|
|
48
|
+
zh_obj = {}
|
|
49
|
+
for k, v in en_obj.items():
|
|
50
|
+
if k not in zh_obj:
|
|
51
|
+
added += 1
|
|
52
|
+
if isinstance(v, str):
|
|
53
|
+
zh_obj[k] = f"<<TODO_TRANSLATE>> {v}"
|
|
54
|
+
else:
|
|
55
|
+
# For non-string nodes, insert scaffold recursively
|
|
56
|
+
zh_obj[k], inc = _merge_missing(
|
|
57
|
+
v, {} if isinstance(v, dict) else [] if isinstance(v, list) else None
|
|
58
|
+
)
|
|
59
|
+
added += inc
|
|
60
|
+
else:
|
|
61
|
+
zh_obj[k], inc = _merge_missing(v, zh_obj[k])
|
|
62
|
+
added += inc
|
|
63
|
+
return zh_obj, added
|
|
64
|
+
|
|
65
|
+
if isinstance(en_obj, list):
|
|
66
|
+
# Do not attempt to merge list structures; keep existing zh list.
|
|
67
|
+
return zh_obj, 0
|
|
68
|
+
|
|
69
|
+
# Primitive leaf: nothing to do
|
|
70
|
+
return zh_obj, 0
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def main() -> int:
|
|
74
|
+
parser = argparse.ArgumentParser()
|
|
75
|
+
parser.add_argument("--write", action="store_true", help="write changes to disk")
|
|
76
|
+
parser.add_argument(
|
|
77
|
+
"--create-missing-files",
|
|
78
|
+
action="store_true",
|
|
79
|
+
help="create missing zh/cn files from en structure with TODO markers",
|
|
80
|
+
)
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
"--target",
|
|
83
|
+
choices=["zh", "cn", "both"],
|
|
84
|
+
default="both",
|
|
85
|
+
help="which target language directory to sync",
|
|
86
|
+
)
|
|
87
|
+
args = parser.parse_args()
|
|
88
|
+
|
|
89
|
+
if not AGENTS_DIR.exists():
|
|
90
|
+
print(f"Agents directory not found: {AGENTS_DIR}", file=sys.stderr)
|
|
91
|
+
return 2
|
|
92
|
+
|
|
93
|
+
total_added = 0
|
|
94
|
+
total_files = 0
|
|
95
|
+
|
|
96
|
+
for module_dir in sorted([p for p in AGENTS_DIR.iterdir() if p.is_dir()]):
|
|
97
|
+
prompts_dir = module_dir / "prompts"
|
|
98
|
+
en_dir = prompts_dir / "en"
|
|
99
|
+
if not en_dir.exists():
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
targets: list[tuple[str, Path]] = []
|
|
103
|
+
if args.target in ("zh", "both"):
|
|
104
|
+
targets.append(("zh", prompts_dir / "zh"))
|
|
105
|
+
if args.target in ("cn", "both"):
|
|
106
|
+
targets.append(("cn", prompts_dir / "cn"))
|
|
107
|
+
|
|
108
|
+
en_files = [p for p in en_dir.rglob("*.yaml") if p.is_file()]
|
|
109
|
+
for en_file in sorted(en_files):
|
|
110
|
+
rel = en_file.relative_to(en_dir)
|
|
111
|
+
en_obj = _load_yaml(en_file)
|
|
112
|
+
for lang_name, lang_dir in targets:
|
|
113
|
+
zh_file = lang_dir / rel
|
|
114
|
+
if not zh_file.exists():
|
|
115
|
+
if not args.create_missing_files:
|
|
116
|
+
print(f"[MISSING {lang_name}] {module_dir.name}: {rel.as_posix()}")
|
|
117
|
+
continue
|
|
118
|
+
zh_obj = {}
|
|
119
|
+
else:
|
|
120
|
+
zh_obj = _load_yaml(zh_file)
|
|
121
|
+
|
|
122
|
+
new_obj, added = _merge_missing(en_obj, zh_obj)
|
|
123
|
+
if added == 0:
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
total_added += added
|
|
127
|
+
total_files += 1
|
|
128
|
+
print(f"[SYNC {lang_name}] {module_dir.name}: {rel.as_posix()} (+{added} keys)")
|
|
129
|
+
|
|
130
|
+
if args.write:
|
|
131
|
+
_dump_yaml(zh_file, new_obj)
|
|
132
|
+
|
|
133
|
+
if total_files == 0:
|
|
134
|
+
print("No changes needed.")
|
|
135
|
+
return 0
|
|
136
|
+
|
|
137
|
+
if args.write:
|
|
138
|
+
print(f"Updated {total_files} file(s), added {total_added} key(s).")
|
|
139
|
+
else:
|
|
140
|
+
print(
|
|
141
|
+
f"Dry-run: would update {total_files} file(s), add {total_added} key(s). Use --write to apply."
|
|
142
|
+
)
|
|
143
|
+
return 0
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
raise SystemExit(main())
|
src/cli/start.py
CHANGED
|
@@ -15,15 +15,15 @@ Usage:
|
|
|
15
15
|
|
|
16
16
|
import argparse
|
|
17
17
|
import os
|
|
18
|
+
from pathlib import Path
|
|
18
19
|
import subprocess
|
|
19
20
|
import sys
|
|
20
|
-
from pathlib import Path
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def create_parser() -> argparse.ArgumentParser:
|
|
24
24
|
"""
|
|
25
25
|
Create CLI argument parser following Unix conventions.
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
Design: Minimal CLI options, environment-driven configuration.
|
|
28
28
|
"""
|
|
29
29
|
parser = argparse.ArgumentParser(
|
|
@@ -46,79 +46,71 @@ Environment Variables:
|
|
|
46
46
|
For more information: https://github.com/therealtimex/DeepTutor-local-app
|
|
47
47
|
""",
|
|
48
48
|
)
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
# Mode selection (future extensibility)
|
|
51
51
|
mode_group = parser.add_mutually_exclusive_group()
|
|
52
52
|
mode_group.add_argument(
|
|
53
53
|
"--backend-only",
|
|
54
54
|
action="store_true",
|
|
55
|
-
help="Start backend only (FastAPI) [NOT YET IMPLEMENTED]"
|
|
55
|
+
help="Start backend only (FastAPI) [NOT YET IMPLEMENTED]",
|
|
56
56
|
)
|
|
57
57
|
mode_group.add_argument(
|
|
58
58
|
"--frontend-only",
|
|
59
59
|
action="store_true",
|
|
60
|
-
help="Start frontend only (Next.js) [NOT YET IMPLEMENTED]"
|
|
60
|
+
help="Start frontend only (Next.js) [NOT YET IMPLEMENTED]",
|
|
61
61
|
)
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
# Logging configuration
|
|
64
64
|
parser.add_argument(
|
|
65
65
|
"--log-level",
|
|
66
66
|
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
|
|
67
67
|
default=os.getenv("LOG_LEVEL", "INFO"),
|
|
68
|
-
help="Logging level (default: INFO)"
|
|
68
|
+
help="Logging level (default: INFO)",
|
|
69
69
|
)
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
return parser
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
def find_project_root() -> Path:
|
|
75
75
|
"""
|
|
76
76
|
Find DeepTutor project root directory.
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
Strategy:
|
|
79
|
-
1.
|
|
80
|
-
2. Fall back to
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
1. Use __file__ location (works for both installed and development)
|
|
80
|
+
2. Fall back to pyproject.toml search for development mode
|
|
81
|
+
|
|
83
82
|
Returns:
|
|
84
83
|
Path: Absolute path to project root
|
|
85
|
-
|
|
84
|
+
|
|
86
85
|
Raises:
|
|
87
86
|
RuntimeError: If project root cannot be determined
|
|
88
87
|
"""
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
# Development mode
|
|
99
|
-
|
|
100
|
-
for parent in [current.parent, *current.parents]:
|
|
88
|
+
# Primary strategy: Use __file__ location
|
|
89
|
+
# This file is at: <root>/src/cli/start.py
|
|
90
|
+
# So root is 3 levels up: start.py -> cli -> src -> root
|
|
91
|
+
package_root = Path(__file__).resolve().parent.parent.parent
|
|
92
|
+
|
|
93
|
+
# Validate by checking for essential package content (not pyproject.toml)
|
|
94
|
+
if (package_root / "src" / "api").is_dir():
|
|
95
|
+
return package_root
|
|
96
|
+
|
|
97
|
+
# Fallback: Development mode - look for pyproject.toml
|
|
98
|
+
for parent in Path(__file__).resolve().parents:
|
|
101
99
|
if (parent / "pyproject.toml").exists():
|
|
102
100
|
return parent
|
|
103
|
-
|
|
104
|
-
# Last
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return cwd
|
|
108
|
-
|
|
109
|
-
raise RuntimeError(
|
|
110
|
-
"Cannot find DeepTutor project root. "
|
|
111
|
-
"Make sure you are running from the project directory or have installed the package."
|
|
112
|
-
)
|
|
101
|
+
|
|
102
|
+
# Last fallback: return computed root without strict validation
|
|
103
|
+
# This allows the package to attempt startup even if structure is unusual
|
|
104
|
+
return package_root
|
|
113
105
|
|
|
114
106
|
|
|
115
107
|
def validate_environment(project_root: Path) -> None:
|
|
116
108
|
"""
|
|
117
109
|
Validate environment and dependencies.
|
|
118
|
-
|
|
110
|
+
|
|
119
111
|
Args:
|
|
120
112
|
project_root: Path to project root
|
|
121
|
-
|
|
113
|
+
|
|
122
114
|
Raises:
|
|
123
115
|
RuntimeError: If validation fails
|
|
124
116
|
"""
|
|
@@ -130,20 +122,24 @@ def validate_environment(project_root: Path) -> None:
|
|
|
130
122
|
f"Project root: {project_root}\n"
|
|
131
123
|
"Please ensure DeepTutor is properly installed."
|
|
132
124
|
)
|
|
133
|
-
|
|
134
|
-
# Check for web directory
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
125
|
+
|
|
126
|
+
# Check for web directory only in development mode
|
|
127
|
+
# In production mode, frontend is served via npx @realtimex/opentutor-web
|
|
128
|
+
dev_mode = os.environ.get("FRONTEND_DEV_MODE", "").lower() in ("true", "1", "yes")
|
|
129
|
+
if dev_mode:
|
|
130
|
+
web_dir = project_root / "web"
|
|
131
|
+
if not web_dir.exists():
|
|
132
|
+
raise RuntimeError(
|
|
133
|
+
f"Frontend directory not found: {web_dir}\n"
|
|
134
|
+
"FRONTEND_DEV_MODE is enabled but web/ directory is missing.\n"
|
|
135
|
+
"Either disable dev mode or run from the project source directory."
|
|
136
|
+
)
|
|
141
137
|
|
|
142
138
|
|
|
143
139
|
def main():
|
|
144
140
|
"""
|
|
145
141
|
Main CLI entry point.
|
|
146
|
-
|
|
142
|
+
|
|
147
143
|
Flow:
|
|
148
144
|
1. Parse arguments
|
|
149
145
|
2. Find and validate project root
|
|
@@ -152,63 +148,59 @@ def main():
|
|
|
152
148
|
"""
|
|
153
149
|
parser = create_parser()
|
|
154
150
|
args = parser.parse_args()
|
|
155
|
-
|
|
151
|
+
|
|
156
152
|
try:
|
|
157
153
|
# Find project root
|
|
158
154
|
project_root = find_project_root()
|
|
159
|
-
|
|
155
|
+
|
|
160
156
|
# Validate environment
|
|
161
157
|
validate_environment(project_root)
|
|
162
|
-
|
|
158
|
+
|
|
163
159
|
# Build environment for subprocess
|
|
164
160
|
# All configuration comes from environment variables
|
|
165
161
|
env = os.environ.copy()
|
|
166
|
-
|
|
162
|
+
|
|
167
163
|
# Set log level
|
|
168
164
|
env["LOG_LEVEL"] = args.log_level
|
|
169
|
-
|
|
165
|
+
|
|
170
166
|
# Handle mode selection
|
|
171
167
|
if args.backend_only:
|
|
172
168
|
print("⚠️ Backend-only mode not yet implemented")
|
|
173
169
|
print(" Use 'uvx realtimex-deeptutor' for full-stack startup")
|
|
174
170
|
print(" Or use 'deeptutor-backend' for backend-only")
|
|
175
171
|
sys.exit(1)
|
|
176
|
-
|
|
172
|
+
|
|
177
173
|
if args.frontend_only:
|
|
178
174
|
print("⚠️ Frontend-only mode not yet implemented")
|
|
179
175
|
print(" Use 'uvx realtimex-deeptutor' for full-stack startup")
|
|
180
176
|
print(" Or use 'npx @realtimex/opentutor-web' for frontend-only")
|
|
181
177
|
sys.exit(1)
|
|
182
|
-
|
|
178
|
+
|
|
183
179
|
# Full-stack mode: delegate to start_web.py
|
|
184
180
|
print("🚀 Starting DeepTutor (Full Stack)...")
|
|
185
181
|
print(f"📁 Project root: {project_root}")
|
|
186
|
-
|
|
182
|
+
|
|
187
183
|
script_path = project_root / "scripts" / "start_web.py"
|
|
188
|
-
|
|
184
|
+
|
|
189
185
|
# Launch start_web.py with updated environment
|
|
190
|
-
subprocess.run(
|
|
191
|
-
|
|
192
|
-
env=env,
|
|
193
|
-
cwd=project_root,
|
|
194
|
-
check=True
|
|
195
|
-
)
|
|
196
|
-
|
|
186
|
+
subprocess.run([sys.executable, str(script_path)], env=env, cwd=project_root, check=True)
|
|
187
|
+
|
|
197
188
|
except KeyboardInterrupt:
|
|
198
189
|
print("\n🛑 Shutting down...")
|
|
199
190
|
sys.exit(0)
|
|
200
|
-
|
|
191
|
+
|
|
201
192
|
except RuntimeError as e:
|
|
202
193
|
print(f"❌ Error: {e}", file=sys.stderr)
|
|
203
194
|
sys.exit(1)
|
|
204
|
-
|
|
195
|
+
|
|
205
196
|
except subprocess.CalledProcessError as e:
|
|
206
197
|
# start_web.py already printed error messages
|
|
207
198
|
sys.exit(e.returncode)
|
|
208
|
-
|
|
199
|
+
|
|
209
200
|
except Exception as e:
|
|
210
201
|
print(f"❌ Unexpected error: {e}", file=sys.stderr)
|
|
211
202
|
import traceback
|
|
203
|
+
|
|
212
204
|
traceback.print_exc()
|
|
213
205
|
sys.exit(1)
|
|
214
206
|
|
|
@@ -478,7 +478,7 @@ class UnifiedConfigManager:
|
|
|
478
478
|
|
|
479
479
|
# Get user's active selection (or use defaults)
|
|
480
480
|
active = get_rtx_active_config(config_type.value)
|
|
481
|
-
|
|
481
|
+
|
|
482
482
|
if active:
|
|
483
483
|
provider = active.get("provider", "realtimexai")
|
|
484
484
|
model = active.get("model", "")
|
|
@@ -596,7 +596,7 @@ class UnifiedConfigManager:
|
|
|
596
596
|
|
|
597
597
|
if should_use_realtimex_sdk():
|
|
598
598
|
rtx_active = get_rtx_active_config(config_type.value)
|
|
599
|
-
|
|
599
|
+
|
|
600
600
|
if rtx_active:
|
|
601
601
|
return {
|
|
602
602
|
"id": "rtx",
|
{realtimex_deeptutor-0.5.0.post2.dist-info → realtimex_deeptutor-0.5.0.post3.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|