-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathmain_skill.py
More file actions
198 lines (161 loc) · 5.67 KB
/
Copy pathmain_skill.py
File metadata and controls
198 lines (161 loc) · 5.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
"""Paper Reader main skill entry point.
Routes commands to appropriate sub-skills based on command path.
Supports both full paths and aliases.
Commands:
- setup config <action> - Configuration management
- setup mineru <action> - MinerU PDF parser management (detect, install, status)
- fetch <source> - Paper retrieval from various sources
- analyze <action> - Paper analysis and summarization
"""
import logging
logger = logging.getLogger(__name__)
# Command routing table
# Maps command paths to sub-skill names that can be invoked via Skill tool
_ROUTES = {
# Setup commands
"setup": {
"config": "paper-reader:config",
"mineru": "paper-reader:mineru",
},
# Direct sub-skill commands
"config": "paper-reader:config",
"mineru": "paper-reader:mineru",
"fetch": "paper-reader:fetch",
"analyze": "paper-reader:analyze",
}
# Alias mappings
_ALIASES = {
"cfg": "config",
"c": "config",
"m": "mineru",
"mu": "mineru",
"f": "fetch",
"get": "fetch",
"a": "analyze",
"analysis": "analyze",
}
def _normalize_command(command: str) -> list[str]:
"""Normalize command string to list of parts.
Handles:
- Empty string → []
- Single words → ["word"]
- Multiple words → ["word1", "word2", ...]
- Extra whitespace → trimmed
"""
if not command:
return []
return [part.strip() for part in command.strip().split() if part.strip()]
def _resolve_alias(word: str) -> str:
"""Resolve command alias to full command name."""
return _ALIASES.get(word.lower(), word.lower())
def _build_skill_args(parts: list[str]) -> str:
"""Build arguments string for sub-skill from command parts."""
if not parts:
return ""
# Join remaining parts as arguments for the sub-skill
return " ".join(parts)
def route_command(command: str) -> tuple[str | None, str]:
"""Route command to appropriate sub-skill.
Args:
command: Command string after /paper-reader (e.g., "setup mineru detect")
Returns:
Tuple of (sub_skill_name, args_for_skill)
Returns (None, error_message) if no valid route found
"""
parts = _normalize_command(command)
if not parts:
return None, "No command provided. Use: setup, config, mineru, fetch, or analyze"
# First word - resolve aliases
first = _resolve_alias(parts[0])
remaining = parts[1:] if len(parts) > 1 else []
# Check if it's a setup command
if first == "setup":
if not remaining:
return None, "Setup requires a target. Use: setup config or setup mineru"
target = _resolve_alias(remaining[0])
sub_args = remaining[1:] if len(remaining) > 1 else []
if target in _ROUTES.get("setup", {}):
skill_name = _ROUTES["setup"][target]
args = _build_skill_args(sub_args)
return skill_name, args
else:
return None, f"Unknown setup target: {target}. Use: setup config or setup mineru"
# Direct command (config, mineru, fetch, analyze)
if first in _ROUTES:
skill_name = _ROUTES[first]
args = _build_skill_args(remaining)
return skill_name, args
return None, f"Unknown command: {first}. Use: setup, config, mineru, fetch, or analyze"
def get_available_commands() -> dict:
"""Return all available commands and their descriptions."""
return {
"setup config": "Configuration management",
"setup mineru": "MinerU detection (default)",
"setup mineru detect": "Run MinerU detection",
"setup mineru install": "Install MinerU",
"setup mineru status": "Show MinerU status",
"config": "Configuration management (alias for setup config)",
"mineru": "MinerU management (alias for setup mineru)",
"fetch": "Paper retrieval from various sources",
"analyze": "Paper analysis and summarization",
}
# Main skill entry point - called when /paper-reader is invoked
def execute(command: str) -> dict:
"""Execute paper-reader skill with given command.
Args:
command: Command string after /paper-reader
Returns:
Dict with:
- skill: Sub-skill name to invoke via Skill tool
- args: Arguments to pass to the sub-skill
- error: Error message if routing failed (skill will be None)
"""
logger.info(f"Routing command: {command}")
skill_name, args = route_command(command)
if skill_name is None:
# Return error info - the skill should display help
return {
"skill": None,
"args": "",
"error": args,
"available_commands": get_available_commands(),
}
logger.info(f"Routed to skill: {skill_name} with args: {args}")
return {
"skill": skill_name,
"args": args,
"error": None,
}
# For direct testing
if __name__ == "__main__":
# Test routing
test_commands = [
"",
"setup",
"setup config",
"setup config show",
"setup mineru",
"setup mineru detect",
"setup mineru install",
"setup mineru status",
"config",
"config show",
"mineru",
"mineru detect",
"fetch",
"fetch arxiv 2401.12345",
"analyze",
"analyze summary paper.pdf",
"cfg show", # alias test
"m detect", # alias test
]
print("Testing command routing:")
print("-" * 60)
for cmd in test_commands:
result = execute(cmd)
print(f"\nCommand: '{cmd}'")
if result["error"]:
print(f" Error: {result['error']}")
else:
print(f" Skill: {result['skill']}")
print(f" Args: '{result['args']}'")