missioncache-dashboard 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.
- missioncache_dashboard/__init__.py +3 -0
- missioncache_dashboard/assets/logo_icon.png +0 -0
- missioncache_dashboard/assets/orbit_logo_black.png +0 -0
- missioncache_dashboard/assets/orbit_logo_white.png +0 -0
- missioncache_dashboard/cli.py +349 -0
- missioncache_dashboard/index.html +8020 -0
- missioncache_dashboard/lib/__init__.py +5 -0
- missioncache_dashboard/lib/analytics_db.py +3737 -0
- missioncache_dashboard/lib/config.py +166 -0
- missioncache_dashboard/lib/jsonl_parser.py +510 -0
- missioncache_dashboard/server.py +2822 -0
- missioncache_dashboard/statusline.py +1773 -0
- missioncache_dashboard-1.0.0.dist-info/METADATA +89 -0
- missioncache_dashboard-1.0.0.dist-info/RECORD +16 -0
- missioncache_dashboard-1.0.0.dist-info/WHEEL +4 -0
- missioncache_dashboard-1.0.0.dist-info/entry_points.txt +3 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""MissionCache Dashboard CLI.
|
|
2
|
+
|
|
3
|
+
Entry point for the `missioncache-dashboard` console script. Subcommands:
|
|
4
|
+
|
|
5
|
+
missioncache-dashboard serve Run the dashboard (default).
|
|
6
|
+
missioncache-dashboard install-service Register as launchd / systemd service.
|
|
7
|
+
missioncache-dashboard uninstall-service Remove the service.
|
|
8
|
+
missioncache-dashboard reinstall-service Uninstall + install (Python path fix).
|
|
9
|
+
missioncache-dashboard status Show installed / running state.
|
|
10
|
+
|
|
11
|
+
Platform support: macOS (launchd) and Linux (systemd --user). Windows
|
|
12
|
+
prints manual instructions and exits 0 - Task Scheduler support is
|
|
13
|
+
deferred.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import os
|
|
20
|
+
import shutil
|
|
21
|
+
import socket
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
LAUNCHD_LABEL = "com.missioncache.dashboard"
|
|
27
|
+
SYSTEMD_UNIT = "missioncache-dashboard.service"
|
|
28
|
+
DEFAULT_PORT = 8787
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# =============================================================================
|
|
32
|
+
# Paths
|
|
33
|
+
# =============================================================================
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def launchd_plist_path() -> Path:
|
|
37
|
+
return Path.home() / "Library" / "LaunchAgents" / f"{LAUNCHD_LABEL}.plist"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def systemd_unit_path() -> Path:
|
|
41
|
+
return Path.home() / ".config" / "systemd" / "user" / SYSTEMD_UNIT
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def log_dir() -> Path:
|
|
45
|
+
return Path.home() / ".claude" / "logs"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# =============================================================================
|
|
49
|
+
# Templates (pure, testable)
|
|
50
|
+
# =============================================================================
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def render_plist(binary_path: str, port: int) -> str:
|
|
54
|
+
"""Render the launchd plist pointing at the pip-installed binary."""
|
|
55
|
+
logs = log_dir()
|
|
56
|
+
env_block = ""
|
|
57
|
+
if port != DEFAULT_PORT:
|
|
58
|
+
env_block = (
|
|
59
|
+
" <key>EnvironmentVariables</key>\n"
|
|
60
|
+
" <dict>\n"
|
|
61
|
+
f" <key>MISSIONCACHE_DASHBOARD_PORT</key>\n"
|
|
62
|
+
f" <string>{port}</string>\n"
|
|
63
|
+
" </dict>\n"
|
|
64
|
+
)
|
|
65
|
+
return (
|
|
66
|
+
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
|
67
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n'
|
|
68
|
+
'<plist version="1.0">\n'
|
|
69
|
+
"<dict>\n"
|
|
70
|
+
" <key>Label</key>\n"
|
|
71
|
+
f" <string>{LAUNCHD_LABEL}</string>\n"
|
|
72
|
+
" <key>ProgramArguments</key>\n"
|
|
73
|
+
" <array>\n"
|
|
74
|
+
f" <string>{binary_path}</string>\n"
|
|
75
|
+
" <string>serve</string>\n"
|
|
76
|
+
" </array>\n"
|
|
77
|
+
" <key>RunAtLoad</key>\n"
|
|
78
|
+
" <true/>\n"
|
|
79
|
+
" <key>KeepAlive</key>\n"
|
|
80
|
+
" <true/>\n"
|
|
81
|
+
" <key>StandardOutPath</key>\n"
|
|
82
|
+
f" <string>{logs / 'missioncache-dashboard-stdout.log'}</string>\n"
|
|
83
|
+
" <key>StandardErrorPath</key>\n"
|
|
84
|
+
f" <string>{logs / 'missioncache-dashboard-stderr.log'}</string>\n"
|
|
85
|
+
f"{env_block}"
|
|
86
|
+
"</dict>\n"
|
|
87
|
+
"</plist>\n"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def render_systemd_unit(binary_path: str, port: int) -> str:
|
|
92
|
+
"""Render the systemd user unit pointing at the pip-installed binary."""
|
|
93
|
+
env_line = f"Environment=MISSIONCACHE_DASHBOARD_PORT={port}\n" if port != DEFAULT_PORT else ""
|
|
94
|
+
return (
|
|
95
|
+
"[Unit]\n"
|
|
96
|
+
"Description=MissionCache Dashboard\n"
|
|
97
|
+
"After=network.target\n"
|
|
98
|
+
"\n"
|
|
99
|
+
"[Service]\n"
|
|
100
|
+
"Type=simple\n"
|
|
101
|
+
f"{env_line}"
|
|
102
|
+
f"ExecStart={binary_path} serve\n"
|
|
103
|
+
"Restart=always\n"
|
|
104
|
+
"RestartSec=5\n"
|
|
105
|
+
"\n"
|
|
106
|
+
"[Install]\n"
|
|
107
|
+
"WantedBy=default.target\n"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# =============================================================================
|
|
112
|
+
# Port probing
|
|
113
|
+
# =============================================================================
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def port_in_use(port: int) -> bool:
|
|
117
|
+
"""Return True if TCP port is bound on 127.0.0.1."""
|
|
118
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
119
|
+
sock.settimeout(0.5)
|
|
120
|
+
try:
|
|
121
|
+
sock.bind(("127.0.0.1", port))
|
|
122
|
+
except OSError:
|
|
123
|
+
return True
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def resolve_port(requested: int) -> int:
|
|
128
|
+
"""Return a free port, prompting if the requested one is taken."""
|
|
129
|
+
if not port_in_use(requested):
|
|
130
|
+
return requested
|
|
131
|
+
print(f" Port {requested} is already in use.")
|
|
132
|
+
while True:
|
|
133
|
+
raw = input(" Enter a different port (or blank to abort): ").strip()
|
|
134
|
+
if not raw:
|
|
135
|
+
raise SystemExit("Aborted.")
|
|
136
|
+
try:
|
|
137
|
+
alt = int(raw)
|
|
138
|
+
except ValueError:
|
|
139
|
+
print(" Not a number, try again.")
|
|
140
|
+
continue
|
|
141
|
+
if port_in_use(alt):
|
|
142
|
+
print(f" Port {alt} is also in use.")
|
|
143
|
+
continue
|
|
144
|
+
return alt
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# =============================================================================
|
|
148
|
+
# Binary resolution
|
|
149
|
+
# =============================================================================
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def resolve_binary() -> str:
|
|
153
|
+
"""Return the absolute path of the installed `missioncache-dashboard` script."""
|
|
154
|
+
found = shutil.which("missioncache-dashboard")
|
|
155
|
+
if not found:
|
|
156
|
+
raise SystemExit(
|
|
157
|
+
"Could not find `missioncache-dashboard` on PATH. This command must be "
|
|
158
|
+
"run from the same environment where `missioncache-dashboard` is pip-"
|
|
159
|
+
"installed (pipx, uv tool, or a venv)."
|
|
160
|
+
)
|
|
161
|
+
return found
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# =============================================================================
|
|
165
|
+
# Platform install/uninstall
|
|
166
|
+
# =============================================================================
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def install_launchd(port: int) -> None:
|
|
170
|
+
binary = resolve_binary()
|
|
171
|
+
plist = launchd_plist_path()
|
|
172
|
+
plist.parent.mkdir(parents=True, exist_ok=True)
|
|
173
|
+
log_dir().mkdir(parents=True, exist_ok=True)
|
|
174
|
+
|
|
175
|
+
if plist.exists():
|
|
176
|
+
print(f" Replacing existing service definition at {plist}")
|
|
177
|
+
subprocess.run(["launchctl", "unload", str(plist)], check=False)
|
|
178
|
+
|
|
179
|
+
plist.write_text(render_plist(binary, port))
|
|
180
|
+
subprocess.run(["launchctl", "load", str(plist)], check=True)
|
|
181
|
+
print(f" launchd service loaded: {LAUNCHD_LABEL}")
|
|
182
|
+
print(f" Logs: {log_dir()}/missioncache-dashboard-{{stdout,stderr}}.log")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def uninstall_launchd() -> None:
|
|
186
|
+
plist = launchd_plist_path()
|
|
187
|
+
if not plist.exists():
|
|
188
|
+
print(" launchd service not installed, nothing to do.")
|
|
189
|
+
return
|
|
190
|
+
subprocess.run(["launchctl", "unload", str(plist)], check=False)
|
|
191
|
+
plist.unlink()
|
|
192
|
+
print(f" Removed {plist}")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def install_systemd(port: int) -> None:
|
|
196
|
+
binary = resolve_binary()
|
|
197
|
+
unit = systemd_unit_path()
|
|
198
|
+
unit.parent.mkdir(parents=True, exist_ok=True)
|
|
199
|
+
unit.write_text(render_systemd_unit(binary, port))
|
|
200
|
+
subprocess.run(["systemctl", "--user", "daemon-reload"], check=True)
|
|
201
|
+
subprocess.run(["systemctl", "--user", "enable", "--now", SYSTEMD_UNIT], check=True)
|
|
202
|
+
print(f" systemd --user unit enabled: {SYSTEMD_UNIT}")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def uninstall_systemd() -> None:
|
|
206
|
+
unit = systemd_unit_path()
|
|
207
|
+
if not unit.exists():
|
|
208
|
+
print(" systemd user unit not installed, nothing to do.")
|
|
209
|
+
return
|
|
210
|
+
subprocess.run(["systemctl", "--user", "disable", "--now", SYSTEMD_UNIT], check=False)
|
|
211
|
+
unit.unlink()
|
|
212
|
+
subprocess.run(["systemctl", "--user", "daemon-reload"], check=False)
|
|
213
|
+
print(f" Removed {unit}")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# =============================================================================
|
|
217
|
+
# Subcommand handlers
|
|
218
|
+
# =============================================================================
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def cmd_serve(_args: argparse.Namespace) -> int:
|
|
222
|
+
"""Run the dashboard via uvicorn. Reads MISSIONCACHE_DASHBOARD_PORT env var."""
|
|
223
|
+
import uvicorn # local import: keeps `missioncache-dashboard --help` fast
|
|
224
|
+
|
|
225
|
+
port = int(os.environ.get("MISSIONCACHE_DASHBOARD_PORT", str(DEFAULT_PORT)))
|
|
226
|
+
uvicorn.run("missioncache_dashboard.server:app", host="127.0.0.1", port=port)
|
|
227
|
+
return 0
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def cmd_install_service(_args: argparse.Namespace) -> int:
|
|
231
|
+
port = int(os.environ.get("MISSIONCACHE_DASHBOARD_PORT", str(DEFAULT_PORT)))
|
|
232
|
+
port = resolve_port(port)
|
|
233
|
+
|
|
234
|
+
if sys.platform == "darwin":
|
|
235
|
+
install_launchd(port)
|
|
236
|
+
elif sys.platform.startswith("linux"):
|
|
237
|
+
install_systemd(port)
|
|
238
|
+
elif sys.platform == "win32":
|
|
239
|
+
print(
|
|
240
|
+
"Windows service registration is not yet supported.\n"
|
|
241
|
+
"Run 'missioncache-dashboard serve' manually, or add your own Task "
|
|
242
|
+
"Scheduler entry. See docs/installation.md#windows."
|
|
243
|
+
)
|
|
244
|
+
return 0
|
|
245
|
+
else:
|
|
246
|
+
print(f"Unsupported platform: {sys.platform}", file=sys.stderr)
|
|
247
|
+
return 1
|
|
248
|
+
return 0
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def cmd_uninstall_service(_args: argparse.Namespace) -> int:
|
|
252
|
+
if sys.platform == "darwin":
|
|
253
|
+
uninstall_launchd()
|
|
254
|
+
elif sys.platform.startswith("linux"):
|
|
255
|
+
uninstall_systemd()
|
|
256
|
+
elif sys.platform == "win32":
|
|
257
|
+
print("Windows service was never auto-registered; nothing to uninstall.")
|
|
258
|
+
return 0
|
|
259
|
+
else:
|
|
260
|
+
print(f"Unsupported platform: {sys.platform}", file=sys.stderr)
|
|
261
|
+
return 1
|
|
262
|
+
return 0
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def cmd_reinstall_service(args: argparse.Namespace) -> int:
|
|
266
|
+
rc = cmd_uninstall_service(args)
|
|
267
|
+
if rc != 0:
|
|
268
|
+
return rc
|
|
269
|
+
return cmd_install_service(args)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def cmd_status(_args: argparse.Namespace) -> int:
|
|
273
|
+
"""Report installed and running state."""
|
|
274
|
+
if sys.platform == "darwin":
|
|
275
|
+
installed = launchd_plist_path().exists()
|
|
276
|
+
running = False
|
|
277
|
+
if installed:
|
|
278
|
+
result = subprocess.run(
|
|
279
|
+
["launchctl", "list", LAUNCHD_LABEL],
|
|
280
|
+
capture_output=True,
|
|
281
|
+
text=True,
|
|
282
|
+
check=False,
|
|
283
|
+
)
|
|
284
|
+
running = result.returncode == 0
|
|
285
|
+
print(f" Installed: {installed}")
|
|
286
|
+
print(f" Running: {running}")
|
|
287
|
+
elif sys.platform.startswith("linux"):
|
|
288
|
+
installed = systemd_unit_path().exists()
|
|
289
|
+
running = False
|
|
290
|
+
if installed:
|
|
291
|
+
result = subprocess.run(
|
|
292
|
+
["systemctl", "--user", "is-active", SYSTEMD_UNIT],
|
|
293
|
+
capture_output=True,
|
|
294
|
+
text=True,
|
|
295
|
+
check=False,
|
|
296
|
+
)
|
|
297
|
+
running = result.stdout.strip() == "active"
|
|
298
|
+
print(f" Installed: {installed}")
|
|
299
|
+
print(f" Running: {running}")
|
|
300
|
+
elif sys.platform == "win32":
|
|
301
|
+
print(" Windows: not supported.")
|
|
302
|
+
else:
|
|
303
|
+
print(f" Unsupported platform: {sys.platform}")
|
|
304
|
+
return 0
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# =============================================================================
|
|
308
|
+
# argparse wiring
|
|
309
|
+
# =============================================================================
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
313
|
+
parser = argparse.ArgumentParser(
|
|
314
|
+
prog="missioncache-dashboard",
|
|
315
|
+
description="MissionCache Dashboard - task analytics and autonomous execution monitoring.",
|
|
316
|
+
)
|
|
317
|
+
sub = parser.add_subparsers(dest="command")
|
|
318
|
+
|
|
319
|
+
p_serve = sub.add_parser("serve", help="Run the dashboard (default)")
|
|
320
|
+
p_serve.set_defaults(func=cmd_serve)
|
|
321
|
+
|
|
322
|
+
p_install = sub.add_parser("install-service", help="Register the dashboard as a background service")
|
|
323
|
+
p_install.set_defaults(func=cmd_install_service)
|
|
324
|
+
|
|
325
|
+
p_uninstall = sub.add_parser("uninstall-service", help="Remove the background service")
|
|
326
|
+
p_uninstall.set_defaults(func=cmd_uninstall_service)
|
|
327
|
+
|
|
328
|
+
p_reinstall = sub.add_parser(
|
|
329
|
+
"reinstall-service",
|
|
330
|
+
help="Uninstall + install the service (Python path change recovery)",
|
|
331
|
+
)
|
|
332
|
+
p_reinstall.set_defaults(func=cmd_reinstall_service)
|
|
333
|
+
|
|
334
|
+
p_status = sub.add_parser("status", help="Show service installed and running state")
|
|
335
|
+
p_status.set_defaults(func=cmd_status)
|
|
336
|
+
|
|
337
|
+
return parser
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def main(argv: list[str] | None = None) -> int:
|
|
341
|
+
parser = build_parser()
|
|
342
|
+
args = parser.parse_args(argv)
|
|
343
|
+
if not getattr(args, "func", None):
|
|
344
|
+
return cmd_serve(args)
|
|
345
|
+
return args.func(args)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
if __name__ == "__main__":
|
|
349
|
+
raise SystemExit(main())
|