magmascript 1.0.0__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.
- magmascript/__init__.py +41 -0
- magmascript/cli.py +628 -0
- magmascript/core/__init__.py +40 -0
- magmascript/core/cache.py +193 -0
- magmascript/core/config.py +150 -0
- magmascript/core/exceptions.py +59 -0
- magmascript/core/github.py +179 -0
- magmascript/core/output.py +136 -0
- magmascript/core/registry.py +31 -0
- magmascript/core/rpc.py +184 -0
- magmascript/domains/__init__.py +10 -0
- magmascript/domains/gh/__init__.py +15 -0
- magmascript/domains/gh/client.py +212 -0
- magmascript/domains/gh/tools.py +116 -0
- magmascript/domains/mcp/__init__.py +35 -0
- magmascript/domains/mcp/client.py +251 -0
- magmascript/domains/mcp/tools.py +411 -0
- magmascript/domains/media/__init__.py +21 -0
- magmascript/domains/media/client.py +202 -0
- magmascript/domains/media/providers/__init__.py +33 -0
- magmascript/domains/media/providers/archive.py +89 -0
- magmascript/domains/media/providers/met_museum.py +110 -0
- magmascript/domains/media/providers/openverse.py +90 -0
- magmascript/domains/media/providers/pexels.py +106 -0
- magmascript/domains/media/providers/pixabay.py +116 -0
- magmascript/domains/media/providers/smithsonian.py +94 -0
- magmascript/domains/media/tools.py +53 -0
- magmascript/domains/pi/__init__.py +19 -0
- magmascript/domains/pi/client.py +259 -0
- magmascript/domains/pi/tools.py +98 -0
- magmascript/domains/scores/__init__.py +21 -0
- magmascript/domains/scores/client.py +201 -0
- magmascript/domains/scores/tools.py +55 -0
- magmascript-1.0.0.dist-info/METADATA +140 -0
- magmascript-1.0.0.dist-info/RECORD +39 -0
- magmascript-1.0.0.dist-info/WHEEL +5 -0
- magmascript-1.0.0.dist-info/entry_points.txt +2 -0
- magmascript-1.0.0.dist-info/licenses/LICENSE +21 -0
- magmascript-1.0.0.dist-info/top_level.txt +1 -0
magmascript/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""magmascript — a scripting toolkit with domain-first subcommands."""
|
|
2
|
+
|
|
3
|
+
__version__ = "1.0.0"
|
|
4
|
+
|
|
5
|
+
from magmascript.core.config import Config, GHConfig, MediaConfig, PIConfig, get_config, load_config, set_config
|
|
6
|
+
from magmascript.core.registry import get_domain, list_domains, register_domain
|
|
7
|
+
from magmascript.core.rpc import RPCClient, RPCError, RPCResponse
|
|
8
|
+
from magmascript.core.output import format_output, format_table, format_json
|
|
9
|
+
|
|
10
|
+
# Import domains to trigger registration
|
|
11
|
+
from magmascript import domains # noqa: F401
|
|
12
|
+
|
|
13
|
+
# Convenience: expose clients at top level
|
|
14
|
+
from magmascript.domains.mcp import MCPClient
|
|
15
|
+
from magmascript.domains.pi import PIClient
|
|
16
|
+
from magmascript.domains.gh import GHClient
|
|
17
|
+
from magmascript.domains.media import MediaClient
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"__version__",
|
|
21
|
+
"Config",
|
|
22
|
+
"GHClient",
|
|
23
|
+
"GHConfig",
|
|
24
|
+
"MCPClient",
|
|
25
|
+
"MediaClient",
|
|
26
|
+
"MediaConfig",
|
|
27
|
+
"PIClient",
|
|
28
|
+
"PIConfig",
|
|
29
|
+
"RPCClient",
|
|
30
|
+
"RPCError",
|
|
31
|
+
"RPCResponse",
|
|
32
|
+
"format_output",
|
|
33
|
+
"format_json",
|
|
34
|
+
"format_table",
|
|
35
|
+
"get_config",
|
|
36
|
+
"get_domain",
|
|
37
|
+
"list_domains",
|
|
38
|
+
"load_config",
|
|
39
|
+
"register_domain",
|
|
40
|
+
"set_config",
|
|
41
|
+
]
|
magmascript/cli.py
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
"""CLI entry point for magmascript.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
magmascript mcp search "aphex twin"
|
|
5
|
+
magmascript mcp scores tetris
|
|
6
|
+
magmascript pi status
|
|
7
|
+
magmascript pi logs arcade-chat
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
from magmascript.core.config import get_config
|
|
15
|
+
from magmascript.core.exceptions import MagmascriptError
|
|
16
|
+
from magmascript.core.output import format_output
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def usage():
|
|
20
|
+
print("""magmascript — scripting toolkit with domain-first subcommands
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
magmascript <domain> <action> [args...]
|
|
24
|
+
|
|
25
|
+
Domains:
|
|
26
|
+
mcp MagmaCrunch MCP server tools
|
|
27
|
+
pi Raspberry Pi management (direct SSH)
|
|
28
|
+
gh GitHub operations (direct API)
|
|
29
|
+
media Multi-provider media search
|
|
30
|
+
scores Game high scores (direct SSH)
|
|
31
|
+
cache Cache management (stats, clear)
|
|
32
|
+
|
|
33
|
+
MCP Actions:
|
|
34
|
+
search <query> Search cached MusicBrainz entities
|
|
35
|
+
entities [type] List cached entities (artists, places, etc.)
|
|
36
|
+
entity <type> <key> Get full entity data
|
|
37
|
+
scoreboards List all game leaderboards
|
|
38
|
+
scores <game> [limit] Get leaderboard for a game
|
|
39
|
+
games List all arcade games
|
|
40
|
+
archive List all archive pages
|
|
41
|
+
bots List GitHub Actions workflows
|
|
42
|
+
bot-status <name> Get workflow details
|
|
43
|
+
trigger <name> Trigger a workflow
|
|
44
|
+
bot-runs <name> [limit] Get workflow run history
|
|
45
|
+
discogs <query> [type] Search Discogs
|
|
46
|
+
jukebox List jukebox songs
|
|
47
|
+
tv List TV channels
|
|
48
|
+
themes List theme catalog
|
|
49
|
+
plays List Last.fm play counts
|
|
50
|
+
artist-plays <name> Get artist play counts
|
|
51
|
+
|
|
52
|
+
Pi Actions:
|
|
53
|
+
status Check all arcade service statuses
|
|
54
|
+
logs <service> [lines] Get service logs
|
|
55
|
+
logs-errors [lines] Get error logs from all services
|
|
56
|
+
logs-today Get today's logs
|
|
57
|
+
restart <service> Restart a service
|
|
58
|
+
restart-all Restart all arcade services
|
|
59
|
+
info System info (uptime, memory, temp)
|
|
60
|
+
traffic [lines] Nginx access log analysis
|
|
61
|
+
deploy <path> [service] Deploy to Pi via rsync
|
|
62
|
+
reboot Reboot the Pi
|
|
63
|
+
shutdown Power off the Pi
|
|
64
|
+
|
|
65
|
+
GitHub Actions:
|
|
66
|
+
workflows List all workflows with status
|
|
67
|
+
workflow <name> Recent runs for one workflow
|
|
68
|
+
trigger <name> Trigger a workflow
|
|
69
|
+
issues [label] [state] List issues
|
|
70
|
+
issue create <title> [body] Create an issue
|
|
71
|
+
issue close <number> Close an issue
|
|
72
|
+
file <path> Read a file from the repo
|
|
73
|
+
repo Repo info (test connection)
|
|
74
|
+
|
|
75
|
+
Media Search:
|
|
76
|
+
search <query> Search all providers
|
|
77
|
+
search <query> --source s Search specific provider
|
|
78
|
+
providers List available providers
|
|
79
|
+
image <id> --source <src> Get single result by ID
|
|
80
|
+
|
|
81
|
+
Scores:
|
|
82
|
+
list List all games with entry counts
|
|
83
|
+
get <game> [limit] Get leaderboard for a game (default top 20)
|
|
84
|
+
report Generate full markdown report
|
|
85
|
+
|
|
86
|
+
Cache:
|
|
87
|
+
stats Show cache statistics
|
|
88
|
+
clear [--domain <name>] Clear cache entries
|
|
89
|
+
|
|
90
|
+
Options:
|
|
91
|
+
--json Output as JSON
|
|
92
|
+
--table Output as table (default)
|
|
93
|
+
--help Show this help
|
|
94
|
+
""")
|
|
95
|
+
sys.exit(0)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def main():
|
|
99
|
+
args = sys.argv[1:]
|
|
100
|
+
|
|
101
|
+
if not args or args[0] in ("--help", "-h", "help"):
|
|
102
|
+
usage()
|
|
103
|
+
|
|
104
|
+
domain = args[0]
|
|
105
|
+
action = args[1] if len(args) > 1 else ""
|
|
106
|
+
rest = args[2:]
|
|
107
|
+
|
|
108
|
+
# Parse output format
|
|
109
|
+
fmt = "table"
|
|
110
|
+
if "--json" in rest:
|
|
111
|
+
fmt = "json"
|
|
112
|
+
rest.remove("--json")
|
|
113
|
+
elif "--table" in rest:
|
|
114
|
+
rest.remove("--table")
|
|
115
|
+
|
|
116
|
+
# Parse --no-cache flag
|
|
117
|
+
no_cache = False
|
|
118
|
+
if "--no-cache" in rest:
|
|
119
|
+
no_cache = True
|
|
120
|
+
rest.remove("--no-cache")
|
|
121
|
+
|
|
122
|
+
config = get_config()
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
if domain == "mcp":
|
|
126
|
+
from magmascript.domains.mcp import MCPClient
|
|
127
|
+
client = MCPClient(config)
|
|
128
|
+
try:
|
|
129
|
+
_dispatch_mcp(action, rest, client, fmt)
|
|
130
|
+
finally:
|
|
131
|
+
client.close()
|
|
132
|
+
|
|
133
|
+
elif domain == "pi":
|
|
134
|
+
from magmascript.domains.pi import PIClient
|
|
135
|
+
client = PIClient(config)
|
|
136
|
+
try:
|
|
137
|
+
_dispatch_pi(action, rest, client, fmt)
|
|
138
|
+
finally:
|
|
139
|
+
client.close()
|
|
140
|
+
|
|
141
|
+
elif domain == "gh":
|
|
142
|
+
from magmascript.domains.gh import GHClient
|
|
143
|
+
client = GHClient(config)
|
|
144
|
+
try:
|
|
145
|
+
_dispatch_gh(action, rest, client, fmt)
|
|
146
|
+
finally:
|
|
147
|
+
client.close()
|
|
148
|
+
|
|
149
|
+
elif domain == "media":
|
|
150
|
+
from magmascript.domains.media import MediaClient
|
|
151
|
+
client = MediaClient(config)
|
|
152
|
+
try:
|
|
153
|
+
_dispatch_media(action, rest, client, fmt)
|
|
154
|
+
finally:
|
|
155
|
+
client.close()
|
|
156
|
+
|
|
157
|
+
elif domain == "scores":
|
|
158
|
+
from magmascript.domains.scores import ScoresClient
|
|
159
|
+
client = ScoresClient(config)
|
|
160
|
+
try:
|
|
161
|
+
_dispatch_scores(action, rest, client, fmt)
|
|
162
|
+
finally:
|
|
163
|
+
client.close()
|
|
164
|
+
|
|
165
|
+
elif domain == "cache":
|
|
166
|
+
_dispatch_cache(action, rest, fmt)
|
|
167
|
+
|
|
168
|
+
else:
|
|
169
|
+
print(f"Unknown domain: {domain!r}. Available: mcp, pi, gh, media, scores, cache", file=sys.stderr)
|
|
170
|
+
sys.exit(1)
|
|
171
|
+
|
|
172
|
+
except KeyboardInterrupt:
|
|
173
|
+
print("\nInterrupted.", file=sys.stderr)
|
|
174
|
+
sys.exit(130)
|
|
175
|
+
except MagmascriptError as e:
|
|
176
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
177
|
+
sys.exit(1)
|
|
178
|
+
except ValueError as e:
|
|
179
|
+
print(f"Invalid input: {e}", file=sys.stderr)
|
|
180
|
+
sys.exit(1)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _dispatch_mcp(action: str, args: list[str], client, fmt: str):
|
|
184
|
+
"""Dispatch MCP subcommands."""
|
|
185
|
+
if not action or action == "--help":
|
|
186
|
+
usage()
|
|
187
|
+
|
|
188
|
+
if action == "search":
|
|
189
|
+
if not args:
|
|
190
|
+
print("Usage: mcp search <query>", file=sys.stderr)
|
|
191
|
+
sys.exit(1)
|
|
192
|
+
results = client.search(args[0])
|
|
193
|
+
print(format_output(results, fmt))
|
|
194
|
+
|
|
195
|
+
elif action == "entities":
|
|
196
|
+
entity_type = args[0] if args else ""
|
|
197
|
+
results = client.list_entities(entity_type)
|
|
198
|
+
print(format_output(results, fmt))
|
|
199
|
+
|
|
200
|
+
elif action == "entity":
|
|
201
|
+
if len(args) < 2:
|
|
202
|
+
print("Usage: mcp entity <type> <key>", file=sys.stderr)
|
|
203
|
+
sys.exit(1)
|
|
204
|
+
result = client.get_entity(args[0], args[1])
|
|
205
|
+
print(format_output(result, fmt))
|
|
206
|
+
|
|
207
|
+
elif action == "scoreboards":
|
|
208
|
+
results = client.scoreboards()
|
|
209
|
+
print(format_output(results, fmt))
|
|
210
|
+
|
|
211
|
+
elif action == "scores":
|
|
212
|
+
if not args:
|
|
213
|
+
print("Usage: mcp scores <game> [limit]", file=sys.stderr)
|
|
214
|
+
sys.exit(1)
|
|
215
|
+
limit = int(args[1]) if len(args) > 1 else 10
|
|
216
|
+
results = client.scores(args[0], limit)
|
|
217
|
+
print(format_output(results, fmt))
|
|
218
|
+
|
|
219
|
+
elif action == "games":
|
|
220
|
+
results = client.arcade_games()
|
|
221
|
+
print(format_output(results, fmt))
|
|
222
|
+
|
|
223
|
+
elif action == "archive":
|
|
224
|
+
results = client.archive_pages()
|
|
225
|
+
print(format_output(results, fmt))
|
|
226
|
+
|
|
227
|
+
elif action == "bots":
|
|
228
|
+
results = client.bots()
|
|
229
|
+
print(format_output(results, fmt))
|
|
230
|
+
|
|
231
|
+
elif action == "bot-status":
|
|
232
|
+
if not args:
|
|
233
|
+
print("Usage: mcp bot-status <name>", file=sys.stderr)
|
|
234
|
+
sys.exit(1)
|
|
235
|
+
result = client.bot_status(args[0])
|
|
236
|
+
print(result)
|
|
237
|
+
|
|
238
|
+
elif action == "trigger":
|
|
239
|
+
if not args:
|
|
240
|
+
print("Usage: mcp trigger <name>", file=sys.stderr)
|
|
241
|
+
sys.exit(1)
|
|
242
|
+
result = client.trigger_bot(args[0])
|
|
243
|
+
print(result)
|
|
244
|
+
|
|
245
|
+
elif action == "bot-runs":
|
|
246
|
+
if not args:
|
|
247
|
+
print("Usage: mcp bot-runs <name> [limit]", file=sys.stderr)
|
|
248
|
+
sys.exit(1)
|
|
249
|
+
limit = int(args[1]) if len(args) > 1 else 10
|
|
250
|
+
result = client.bot_runs(args[0], limit)
|
|
251
|
+
print(result)
|
|
252
|
+
|
|
253
|
+
elif action == "discogs":
|
|
254
|
+
if not args:
|
|
255
|
+
print("Usage: mcp discogs <query> [type]", file=sys.stderr)
|
|
256
|
+
sys.exit(1)
|
|
257
|
+
search_type = args[1] if len(args) > 1 else "release"
|
|
258
|
+
results = client.discogs_search(args[0], search_type)
|
|
259
|
+
print(format_output(results, fmt))
|
|
260
|
+
|
|
261
|
+
elif action == "jukebox":
|
|
262
|
+
result = client.jukebox_songs()
|
|
263
|
+
print(result)
|
|
264
|
+
|
|
265
|
+
elif action == "tv":
|
|
266
|
+
result = client.tv_channels()
|
|
267
|
+
print(result)
|
|
268
|
+
|
|
269
|
+
elif action == "themes":
|
|
270
|
+
result = client.themes()
|
|
271
|
+
print(result)
|
|
272
|
+
|
|
273
|
+
elif action == "plays":
|
|
274
|
+
results = client.play_counts()
|
|
275
|
+
print(format_output(results, fmt))
|
|
276
|
+
|
|
277
|
+
elif action == "artist-plays":
|
|
278
|
+
if not args:
|
|
279
|
+
print("Usage: mcp artist-plays <name>", file=sys.stderr)
|
|
280
|
+
sys.exit(1)
|
|
281
|
+
result = client.artist_play_counts(args[0])
|
|
282
|
+
print(result)
|
|
283
|
+
|
|
284
|
+
else:
|
|
285
|
+
print(f"Unknown MCP action: {action!r}", file=sys.stderr)
|
|
286
|
+
print("Run 'magmascript mcp --help' for available actions.", file=sys.stderr)
|
|
287
|
+
sys.exit(1)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _dispatch_pi(action: str, args: list[str], client, fmt: str):
|
|
291
|
+
"""Dispatch Pi subcommands."""
|
|
292
|
+
if not action or action == "--help":
|
|
293
|
+
usage()
|
|
294
|
+
|
|
295
|
+
if action == "status":
|
|
296
|
+
results = client.services()
|
|
297
|
+
print(format_output(results, fmt))
|
|
298
|
+
|
|
299
|
+
elif action == "logs":
|
|
300
|
+
if not args:
|
|
301
|
+
print("Usage: pi logs <service> [lines]", file=sys.stderr)
|
|
302
|
+
sys.exit(1)
|
|
303
|
+
lines = int(args[1]) if len(args) > 1 else 50
|
|
304
|
+
result = client.logs(args[0], lines)
|
|
305
|
+
print(result)
|
|
306
|
+
|
|
307
|
+
elif action == "logs-errors":
|
|
308
|
+
lines = int(args[0]) if args else 100
|
|
309
|
+
result = client.logs_errors(lines)
|
|
310
|
+
print(result)
|
|
311
|
+
|
|
312
|
+
elif action == "logs-today":
|
|
313
|
+
result = client.logs_today()
|
|
314
|
+
print(result)
|
|
315
|
+
|
|
316
|
+
elif action == "restart":
|
|
317
|
+
if not args:
|
|
318
|
+
print("Usage: pi restart <service>", file=sys.stderr)
|
|
319
|
+
sys.exit(1)
|
|
320
|
+
result = client.restart(args[0])
|
|
321
|
+
print(result)
|
|
322
|
+
|
|
323
|
+
elif action == "restart-all":
|
|
324
|
+
result = client.restart_all()
|
|
325
|
+
print(result)
|
|
326
|
+
|
|
327
|
+
elif action == "info":
|
|
328
|
+
result = client.info()
|
|
329
|
+
print(format_output(result, fmt))
|
|
330
|
+
|
|
331
|
+
elif action == "traffic":
|
|
332
|
+
lines = int(args[0]) if args else 1000
|
|
333
|
+
result = client.traffic(lines)
|
|
334
|
+
print(f"=== Top IPs ===\n{result.top_ips}")
|
|
335
|
+
print(f"\n=== Status Codes ===\n{result.status_codes}")
|
|
336
|
+
print(f"\n=== User Agents ===\n{result.user_agents}")
|
|
337
|
+
print(f"\n=== Total Requests ===\n{result.total_requests}")
|
|
338
|
+
|
|
339
|
+
elif action == "deploy":
|
|
340
|
+
if not args:
|
|
341
|
+
print("Usage: pi deploy <path> [service]", file=sys.stderr)
|
|
342
|
+
sys.exit(1)
|
|
343
|
+
service = args[1] if len(args) > 1 else ""
|
|
344
|
+
result = client.deploy(args[0], service)
|
|
345
|
+
print(result)
|
|
346
|
+
|
|
347
|
+
elif action == "reboot":
|
|
348
|
+
result = client.reboot()
|
|
349
|
+
print(result)
|
|
350
|
+
|
|
351
|
+
elif action == "shutdown":
|
|
352
|
+
result = client.shutdown()
|
|
353
|
+
print(result)
|
|
354
|
+
|
|
355
|
+
else:
|
|
356
|
+
print(f"Unknown Pi action: {action!r}", file=sys.stderr)
|
|
357
|
+
print("Run 'magmascript pi --help' for available actions.", file=sys.stderr)
|
|
358
|
+
sys.exit(1)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _dispatch_gh(action: str, args: list[str], client, fmt: str):
|
|
362
|
+
"""Dispatch GitHub subcommands."""
|
|
363
|
+
if not action or action == "--help":
|
|
364
|
+
usage()
|
|
365
|
+
|
|
366
|
+
if action == "workflows":
|
|
367
|
+
results = client.workflows()
|
|
368
|
+
print(format_output(results, fmt))
|
|
369
|
+
|
|
370
|
+
elif action == "workflow":
|
|
371
|
+
if not args:
|
|
372
|
+
print("Usage: gh workflow <name>", file=sys.stderr)
|
|
373
|
+
sys.exit(1)
|
|
374
|
+
limit = int(args[1]) if len(args) > 1 else 10
|
|
375
|
+
results = client.workflow_runs(args[0], limit)
|
|
376
|
+
print(format_output(results, fmt))
|
|
377
|
+
|
|
378
|
+
elif action == "trigger":
|
|
379
|
+
if not args:
|
|
380
|
+
print("Usage: gh trigger <name>", file=sys.stderr)
|
|
381
|
+
sys.exit(1)
|
|
382
|
+
result = client.trigger(args[0])
|
|
383
|
+
print(result)
|
|
384
|
+
|
|
385
|
+
elif action == "issues":
|
|
386
|
+
labels = args[0] if args else ""
|
|
387
|
+
state = args[1] if len(args) > 1 else "open"
|
|
388
|
+
results = client.issues(labels=labels, state=state)
|
|
389
|
+
print(format_output(results, fmt))
|
|
390
|
+
|
|
391
|
+
elif action == "issue":
|
|
392
|
+
if not args:
|
|
393
|
+
print("Usage: gh issue create <title> [body] | gh issue close <number>", file=sys.stderr)
|
|
394
|
+
sys.exit(1)
|
|
395
|
+
sub = args[0]
|
|
396
|
+
if sub == "create":
|
|
397
|
+
if len(args) < 2:
|
|
398
|
+
print("Usage: gh issue create <title> [body]", file=sys.stderr)
|
|
399
|
+
sys.exit(1)
|
|
400
|
+
title = args[1]
|
|
401
|
+
body = args[2] if len(args) > 2 else ""
|
|
402
|
+
result = client.create_issue(title, body)
|
|
403
|
+
print(format_output(result, fmt))
|
|
404
|
+
elif sub == "close":
|
|
405
|
+
if len(args) < 2:
|
|
406
|
+
print("Usage: gh issue close <number>", file=sys.stderr)
|
|
407
|
+
sys.exit(1)
|
|
408
|
+
result = client.close_issue(int(args[1]))
|
|
409
|
+
print(result)
|
|
410
|
+
else:
|
|
411
|
+
print(f"Unknown issue action: {sub!r}. Use 'create' or 'close'.", file=sys.stderr)
|
|
412
|
+
sys.exit(1)
|
|
413
|
+
|
|
414
|
+
elif action == "file":
|
|
415
|
+
if not args:
|
|
416
|
+
print("Usage: gh file <path>", file=sys.stderr)
|
|
417
|
+
sys.exit(1)
|
|
418
|
+
content, sha = client.get_file(args[0])
|
|
419
|
+
print(content)
|
|
420
|
+
|
|
421
|
+
elif action == "repo":
|
|
422
|
+
info = client.repo_info()
|
|
423
|
+
print(f" Name: {info.get('full_name', '?')}")
|
|
424
|
+
print(f" Private: {info.get('private', '?')}")
|
|
425
|
+
print(f" Default branch: {info.get('default_branch', '?')}")
|
|
426
|
+
print(f" Stars: {info.get('stargazers_count', '?')}")
|
|
427
|
+
print(f" Forks: {info.get('forks_count', '?')}")
|
|
428
|
+
|
|
429
|
+
else:
|
|
430
|
+
print(f"Unknown GitHub action: {action!r}", file=sys.stderr)
|
|
431
|
+
print("Run 'magmascript gh --help' for available actions.", file=sys.stderr)
|
|
432
|
+
sys.exit(1)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _dispatch_media(action: str, args: list[str], client, fmt: str):
|
|
436
|
+
"""Dispatch media search subcommands."""
|
|
437
|
+
if not action or action == "--help":
|
|
438
|
+
usage()
|
|
439
|
+
|
|
440
|
+
if action == "search":
|
|
441
|
+
if not args:
|
|
442
|
+
print("Usage: media search <query> [--source <provider>]", file=sys.stderr)
|
|
443
|
+
sys.exit(1)
|
|
444
|
+
|
|
445
|
+
query = args[0]
|
|
446
|
+
source = ""
|
|
447
|
+
media_type = ""
|
|
448
|
+
orientation = ""
|
|
449
|
+
page = 1
|
|
450
|
+
per_page = 24
|
|
451
|
+
|
|
452
|
+
i = 1
|
|
453
|
+
while i < len(args):
|
|
454
|
+
if args[i] == "--source" and i + 1 < len(args):
|
|
455
|
+
source = args[i + 1]
|
|
456
|
+
i += 2
|
|
457
|
+
elif args[i] == "--type" and i + 1 < len(args):
|
|
458
|
+
media_type = args[i + 1]
|
|
459
|
+
i += 2
|
|
460
|
+
elif args[i] == "--orientation" and i + 1 < len(args):
|
|
461
|
+
orientation = args[i + 1]
|
|
462
|
+
i += 2
|
|
463
|
+
elif args[i] == "--page" and i + 1 < len(args):
|
|
464
|
+
page = int(args[i + 1])
|
|
465
|
+
i += 2
|
|
466
|
+
elif args[i] == "--per-page" and i + 1 < len(args):
|
|
467
|
+
per_page = int(args[i + 1])
|
|
468
|
+
i += 2
|
|
469
|
+
else:
|
|
470
|
+
i += 1
|
|
471
|
+
|
|
472
|
+
result = client.search(
|
|
473
|
+
query, source=source, media_type=media_type,
|
|
474
|
+
orientation=orientation, page=page, per_page=per_page,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
parts = [f"{len(result.results)} results"]
|
|
478
|
+
if result.provider_totals:
|
|
479
|
+
detail = ", ".join(f"{k}: {v}" for k, v in result.provider_totals.items())
|
|
480
|
+
parts.append(f"({detail})")
|
|
481
|
+
print(" ".join(parts))
|
|
482
|
+
|
|
483
|
+
if result.errors:
|
|
484
|
+
for provider, error in result.errors.items():
|
|
485
|
+
print(f" ⚠ {provider}: {error}", file=sys.stderr)
|
|
486
|
+
|
|
487
|
+
print()
|
|
488
|
+
print(format_output(result.results, fmt))
|
|
489
|
+
|
|
490
|
+
elif action == "providers":
|
|
491
|
+
providers = client.list_providers()
|
|
492
|
+
for p in providers:
|
|
493
|
+
key_marker = " (needs key)" if p.needs_key else ""
|
|
494
|
+
types_str = ", ".join(p.types)
|
|
495
|
+
print(f" {p.key:<15} {p.label:<15} [{types_str}]{key_marker}")
|
|
496
|
+
|
|
497
|
+
elif action == "image":
|
|
498
|
+
if len(args) < 2:
|
|
499
|
+
print("Usage: media image <id> --source <provider>", file=sys.stderr)
|
|
500
|
+
sys.exit(1)
|
|
501
|
+
result_id = args[0]
|
|
502
|
+
source = ""
|
|
503
|
+
i = 1
|
|
504
|
+
while i < len(args):
|
|
505
|
+
if args[i] == "--source" and i + 1 < len(args):
|
|
506
|
+
source = args[i + 1]
|
|
507
|
+
i += 2
|
|
508
|
+
else:
|
|
509
|
+
i += 1
|
|
510
|
+
if not source:
|
|
511
|
+
print("Usage: media image <id> --source <provider>", file=sys.stderr)
|
|
512
|
+
sys.exit(1)
|
|
513
|
+
result = client.get(result_id, source)
|
|
514
|
+
if result:
|
|
515
|
+
print(format_output(result, fmt))
|
|
516
|
+
else:
|
|
517
|
+
print(f"Not found: {result_id} from {source}", file=sys.stderr)
|
|
518
|
+
sys.exit(1)
|
|
519
|
+
|
|
520
|
+
else:
|
|
521
|
+
print(f"Unknown media action: {action!r}", file=sys.stderr)
|
|
522
|
+
print("Run 'magmascript media --help' for available actions.", file=sys.stderr)
|
|
523
|
+
sys.exit(1)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _dispatch_scores(action: str, args: list[str], client, fmt: str):
|
|
527
|
+
"""Dispatch Scores subcommands."""
|
|
528
|
+
if not action or action == "--help":
|
|
529
|
+
usage()
|
|
530
|
+
|
|
531
|
+
if action == "list":
|
|
532
|
+
results = client.list_scoreboards()
|
|
533
|
+
print(format_output(results, fmt))
|
|
534
|
+
|
|
535
|
+
elif action == "get":
|
|
536
|
+
if not args:
|
|
537
|
+
print("Usage: scores get <game> [limit]", file=sys.stderr)
|
|
538
|
+
sys.exit(1)
|
|
539
|
+
limit = int(args[1]) if len(args) > 1 else 20
|
|
540
|
+
results = client.get_scores(args[0], limit)
|
|
541
|
+
print(format_output(results, fmt))
|
|
542
|
+
|
|
543
|
+
elif action == "report":
|
|
544
|
+
report = client.report()
|
|
545
|
+
lines = [f"# Weekly High Scores — {report.generated_at}", ""]
|
|
546
|
+
lines.append("## Leaderboards")
|
|
547
|
+
lines.append("")
|
|
548
|
+
for board in report.scoreboards:
|
|
549
|
+
lines.append(f"### {board.game}")
|
|
550
|
+
lines.append("")
|
|
551
|
+
lines.append("| Rank | Player | Score |")
|
|
552
|
+
lines.append("|------|--------|-------|")
|
|
553
|
+
entries = client.get_scores(board.game_id, limit=5)
|
|
554
|
+
for e in entries:
|
|
555
|
+
parts = [str(e.score)]
|
|
556
|
+
if e.level:
|
|
557
|
+
parts.append(f"L{e.level}")
|
|
558
|
+
if e.difficulty:
|
|
559
|
+
parts.append(f"D{e.difficulty}")
|
|
560
|
+
if e.time:
|
|
561
|
+
parts.append(e.time)
|
|
562
|
+
if e.moves:
|
|
563
|
+
parts.append(f"{e.moves} moves")
|
|
564
|
+
if e.won is False:
|
|
565
|
+
parts.append("lost")
|
|
566
|
+
lines.append(f"| {e.rank} | {e.initials} | {' · '.join(parts)} |")
|
|
567
|
+
if board.entries == 0:
|
|
568
|
+
lines.append("| - | No scores yet | - |")
|
|
569
|
+
lines.append("")
|
|
570
|
+
|
|
571
|
+
lines.append("## Stats")
|
|
572
|
+
lines.append("")
|
|
573
|
+
lines.append(f"- **Games tracked**: {report.total_games}")
|
|
574
|
+
lines.append(f"- **Total scores**: {report.total_scores}")
|
|
575
|
+
if report.player_stats:
|
|
576
|
+
top = report.player_stats[0]
|
|
577
|
+
game_word = "game" if top.games_played == 1 else "games"
|
|
578
|
+
lines.append(f"- **Most active player**: {top.name} ({top.total_entries} scores across {top.games_played} {game_word})")
|
|
579
|
+
names = ", ".join(p.name for p in report.player_stats)
|
|
580
|
+
lines.append(f"- **Players**: {names}")
|
|
581
|
+
print("\n".join(lines))
|
|
582
|
+
|
|
583
|
+
else:
|
|
584
|
+
print(f"Unknown scores action: {action!r}", file=sys.stderr)
|
|
585
|
+
print("Run 'magmascript scores --help' for available actions.", file=sys.stderr)
|
|
586
|
+
sys.exit(1)
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def _dispatch_cache(action: str, args: list[str], fmt: str):
|
|
590
|
+
"""Dispatch cache subcommands."""
|
|
591
|
+
from magmascript.core.cache import get_cache
|
|
592
|
+
|
|
593
|
+
cache = get_cache()
|
|
594
|
+
|
|
595
|
+
if not action or action == "--help":
|
|
596
|
+
print("""Cache management.
|
|
597
|
+
|
|
598
|
+
Usage:
|
|
599
|
+
magmascript cache stats Show cache statistics
|
|
600
|
+
magmascript cache clear Clear all cache
|
|
601
|
+
magmascript cache clear --domain m Clear specific domain (media/scores/gh)
|
|
602
|
+
""")
|
|
603
|
+
sys.exit(0)
|
|
604
|
+
|
|
605
|
+
if action == "stats":
|
|
606
|
+
data = cache.file_stats()
|
|
607
|
+
print(format_output(data, fmt))
|
|
608
|
+
|
|
609
|
+
elif action == "clear":
|
|
610
|
+
domain = None
|
|
611
|
+
if "--domain" in args:
|
|
612
|
+
idx = args.index("--domain")
|
|
613
|
+
if idx + 1 < len(args):
|
|
614
|
+
domain = args[idx + 1]
|
|
615
|
+
else:
|
|
616
|
+
print("Usage: cache clear --domain <name>", file=sys.stderr)
|
|
617
|
+
sys.exit(1)
|
|
618
|
+
count = cache.clear(domain=domain)
|
|
619
|
+
label = f" domain '{domain}'" if domain else ""
|
|
620
|
+
print(f"Cleared {count} cache entries{label}.")
|
|
621
|
+
|
|
622
|
+
else:
|
|
623
|
+
print(f"Unknown cache action: {action!r}", file=sys.stderr)
|
|
624
|
+
sys.exit(1)
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
if __name__ == "__main__":
|
|
628
|
+
main()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Core framework for magmascript."""
|
|
2
|
+
|
|
3
|
+
from magmascript.core.cache import CacheStats, CacheStore, get_cache
|
|
4
|
+
from magmascript.core.config import Config, get_config, load_config, set_config
|
|
5
|
+
from magmascript.core.exceptions import (
|
|
6
|
+
APIError,
|
|
7
|
+
AuthError,
|
|
8
|
+
ConfigError,
|
|
9
|
+
MagmascriptError,
|
|
10
|
+
MCPError,
|
|
11
|
+
ProviderError,
|
|
12
|
+
RateLimitError,
|
|
13
|
+
SSHError,
|
|
14
|
+
)
|
|
15
|
+
from magmascript.core.registry import get_domain, list_domains, register_domain
|
|
16
|
+
from magmascript.core.rpc import RPCClient, RPCError, RPCResponse
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"APIError",
|
|
20
|
+
"AuthError",
|
|
21
|
+
"CacheStats",
|
|
22
|
+
"CacheStore",
|
|
23
|
+
"Config",
|
|
24
|
+
"ConfigError",
|
|
25
|
+
"MagmascriptError",
|
|
26
|
+
"MCPError",
|
|
27
|
+
"ProviderError",
|
|
28
|
+
"RPCClient",
|
|
29
|
+
"RPCError",
|
|
30
|
+
"RPCResponse",
|
|
31
|
+
"RateLimitError",
|
|
32
|
+
"SSHError",
|
|
33
|
+
"get_cache",
|
|
34
|
+
"get_config",
|
|
35
|
+
"get_domain",
|
|
36
|
+
"list_domains",
|
|
37
|
+
"load_config",
|
|
38
|
+
"register_domain",
|
|
39
|
+
"set_config",
|
|
40
|
+
]
|