haxaml-ui 0.6.7__tar.gz

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.
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.4
2
+ Name: haxaml-ui
3
+ Version: 0.6.7
4
+ Summary: Local read-only dashboard package for Haxaml.
5
+ Author: Hax
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/haxsysgit/haxaml
8
+ Project-URL: Repository, https://github.com/haxsysgit/haxaml
9
+ Keywords: haxaml,dashboard,ui,developer-tools
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Web Environment
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Build Tools
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: haxaml<0.7.0,>=0.6.7
20
+ Requires-Dist: jinja2>=3.1
21
+ Requires-Dist: starlette>=0.37
22
+ Requires-Dist: uvicorn>=0.30
23
+
24
+ # haxaml-ui
25
+
26
+ Read-only local dashboard package for Haxaml.
27
+
28
+ This package is the dashboard distribution itself.
29
+
30
+ The human launcher remains:
31
+
32
+ ```bash
33
+ haxaml dashboard
34
+ ```
35
+
36
+ `haxaml[ui]` is only the convenience install selector for the core package.
37
+
38
+ Install directly:
39
+
40
+ ```bash
41
+ pip install haxaml-ui
42
+ ```
43
+
44
+ Or install through the core extra:
45
+
46
+ ```bash
47
+ pip install "haxaml[ui]"
48
+ ```
49
+
50
+ Direct package entrypoint is also available:
51
+
52
+ ```bash
53
+ haxaml-dashboard
54
+ ```
@@ -0,0 +1,31 @@
1
+ # haxaml-ui
2
+
3
+ Read-only local dashboard package for Haxaml.
4
+
5
+ This package is the dashboard distribution itself.
6
+
7
+ The human launcher remains:
8
+
9
+ ```bash
10
+ haxaml dashboard
11
+ ```
12
+
13
+ `haxaml[ui]` is only the convenience install selector for the core package.
14
+
15
+ Install directly:
16
+
17
+ ```bash
18
+ pip install haxaml-ui
19
+ ```
20
+
21
+ Or install through the core extra:
22
+
23
+ ```bash
24
+ pip install "haxaml[ui]"
25
+ ```
26
+
27
+ Direct package entrypoint is also available:
28
+
29
+ ```bash
30
+ haxaml-dashboard
31
+ ```
@@ -0,0 +1,6 @@
1
+ """UI package for the Haxaml local dashboard."""
2
+
3
+ from haxaml_ui.dashboard import create_dashboard_app, run_dashboard_server
4
+
5
+
6
+ __all__ = ["create_dashboard_app", "run_dashboard_server"]
@@ -0,0 +1,517 @@
1
+ """Read-only local dashboard for FRAME projects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from html import escape
6
+ from pathlib import Path
7
+ import webbrowser
8
+
9
+ import uvicorn
10
+ import yaml
11
+ from jinja2 import DictLoader, Environment, select_autoescape
12
+ from starlette.applications import Starlette
13
+ from starlette.requests import Request
14
+ from starlette.responses import HTMLResponse, Response
15
+ from starlette.routing import Route
16
+
17
+ from haxaml.frame_model import FrameModel
18
+ from haxaml.map_policy import evaluate_map_complexity, format_map_complexity_summary, map_complexity_issues
19
+ from haxaml.mcp.lifecycle_helpers import _expect_sync_state
20
+ from haxaml.paths import detect_project_root, frame_path
21
+ from haxaml.reconcile import reconcile_derivation
22
+ from haxaml.runner import ExecutionRunner
23
+ from haxaml.runtime_cache import runtime_cache
24
+ from haxaml.state_manager import StateManager
25
+ from haxaml.validator import frame_consistency_report, semantic_validate
26
+
27
+
28
+ DEFAULT_DASHBOARD_HOST = "127.0.0.1"
29
+ DEFAULT_DASHBOARD_PORT = 8421
30
+ FRAME_PAGE_ORDER = ["facts", "rules", "acts", "expect", "map"]
31
+
32
+
33
+ TEMPLATES = {
34
+ "base.html": """
35
+ <!doctype html>
36
+ <html lang="en">
37
+ <head>
38
+ <meta charset="utf-8">
39
+ <meta name="viewport" content="width=device-width, initial-scale=1">
40
+ <title>{{ title }}</title>
41
+ <link rel="stylesheet" href="/static/app.css">
42
+ </head>
43
+ <body>
44
+ <header class="site-header">
45
+ <div>
46
+ <p class="eyebrow">Haxaml Dashboard</p>
47
+ <h1>{{ heading }}</h1>
48
+ <p class="meta">{{ project_dir }}</p>
49
+ </div>
50
+ <div class="pill-row">
51
+ <span class="pill">{{ "Read-only" if read_only else "Mutable" }}</span>
52
+ {% if project_name %}<span class="pill accent">{{ project_name }}</span>{% endif %}
53
+ </div>
54
+ </header>
55
+ <nav class="nav">
56
+ <a href="/">Overview</a>
57
+ {% for item in frame_nav %}
58
+ <a href="/frame/{{ item }}">{{ item }}</a>
59
+ {% endfor %}
60
+ <a href="/archive">archive</a>
61
+ </nav>
62
+ <main class="page">
63
+ {{ body | safe }}
64
+ </main>
65
+ </body>
66
+ </html>
67
+ """,
68
+ }
69
+
70
+
71
+ CSS = """
72
+ :root {
73
+ --bg: #f4f0e8;
74
+ --panel: #fffaf2;
75
+ --ink: #1d1b19;
76
+ --muted: #6a645c;
77
+ --line: #d8cfc1;
78
+ --accent: #0d6b57;
79
+ --accent-soft: #d9f0ea;
80
+ --warn: #9b4d19;
81
+ --warn-soft: #f7e1cf;
82
+ --mono: "JetBrains Mono", "SFMono-Regular", monospace;
83
+ --sans: "IBM Plex Sans", "Segoe UI", sans-serif;
84
+ }
85
+ body {
86
+ margin: 0;
87
+ background:
88
+ radial-gradient(circle at top left, rgba(13, 107, 87, 0.12), transparent 34%),
89
+ linear-gradient(180deg, #efe7da 0%, var(--bg) 36%, #f8f5ef 100%);
90
+ color: var(--ink);
91
+ font-family: var(--sans);
92
+ }
93
+ .site-header, .nav, .page {
94
+ max-width: 1100px;
95
+ margin: 0 auto;
96
+ padding-left: 1rem;
97
+ padding-right: 1rem;
98
+ }
99
+ .site-header {
100
+ display: flex;
101
+ justify-content: space-between;
102
+ gap: 1rem;
103
+ padding-top: 2rem;
104
+ }
105
+ .eyebrow {
106
+ margin: 0 0 .35rem;
107
+ letter-spacing: .08em;
108
+ text-transform: uppercase;
109
+ color: var(--muted);
110
+ font-size: .8rem;
111
+ }
112
+ h1, h2, h3 { margin: 0; }
113
+ .meta, .subtle { color: var(--muted); }
114
+ .pill-row {
115
+ display: flex;
116
+ gap: .5rem;
117
+ align-items: start;
118
+ flex-wrap: wrap;
119
+ }
120
+ .pill {
121
+ border: 1px solid var(--line);
122
+ background: var(--panel);
123
+ border-radius: 999px;
124
+ padding: .45rem .8rem;
125
+ font-size: .9rem;
126
+ }
127
+ .pill.accent {
128
+ border-color: var(--accent);
129
+ background: var(--accent-soft);
130
+ }
131
+ .nav {
132
+ display: flex;
133
+ gap: .6rem;
134
+ flex-wrap: wrap;
135
+ padding-top: 1rem;
136
+ padding-bottom: 1rem;
137
+ }
138
+ .nav a, .button-link {
139
+ color: var(--ink);
140
+ text-decoration: none;
141
+ border: 1px solid var(--line);
142
+ background: rgba(255, 255, 255, .68);
143
+ padding: .55rem .8rem;
144
+ border-radius: 999px;
145
+ }
146
+ .page {
147
+ padding-bottom: 2rem;
148
+ }
149
+ .grid {
150
+ display: grid;
151
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
152
+ gap: 1rem;
153
+ }
154
+ .card, .panel, details {
155
+ background: rgba(255, 250, 242, .88);
156
+ border: 1px solid var(--line);
157
+ border-radius: 18px;
158
+ padding: 1rem;
159
+ box-shadow: 0 10px 30px rgba(54, 43, 29, 0.04);
160
+ }
161
+ .card h3, .panel h2 { margin-bottom: .4rem; }
162
+ .plain-list {
163
+ margin: .6rem 0 0;
164
+ padding-left: 1rem;
165
+ }
166
+ .plain-list li {
167
+ margin: .3rem 0;
168
+ }
169
+ .warning {
170
+ border-color: var(--warn);
171
+ background: var(--warn-soft);
172
+ }
173
+ pre, code {
174
+ font-family: var(--mono);
175
+ }
176
+ pre {
177
+ overflow-x: auto;
178
+ white-space: pre-wrap;
179
+ background: #f7f3ec;
180
+ padding: 1rem;
181
+ border-radius: 12px;
182
+ border: 1px solid #e5dccf;
183
+ }
184
+ .stack {
185
+ display: flex;
186
+ flex-direction: column;
187
+ gap: 1rem;
188
+ }
189
+ .toolbar {
190
+ display: flex;
191
+ flex-wrap: wrap;
192
+ gap: .6rem;
193
+ margin-bottom: 1rem;
194
+ }
195
+ .section-summary {
196
+ font-weight: 600;
197
+ }
198
+ @media (max-width: 720px) {
199
+ .site-header {
200
+ flex-direction: column;
201
+ }
202
+ }
203
+ """
204
+
205
+
206
+ _TEMPLATE_ENV = Environment(
207
+ loader=DictLoader(TEMPLATES),
208
+ autoescape=select_autoescape(["html", "xml"]),
209
+ )
210
+
211
+
212
+ def resolve_dashboard_project_dir(project_dir: str = ".") -> Path:
213
+ resolved = detect_project_root(project_dir)
214
+ if resolved is None:
215
+ raise FileNotFoundError(f"No .haxaml directory found from {Path(project_dir).resolve()}")
216
+ return resolved
217
+
218
+
219
+ def dashboard_url(host: str, port: int) -> str:
220
+ return f"http://{host}:{port}/"
221
+
222
+
223
+ def run_dashboard_server(
224
+ *,
225
+ project_dir: str,
226
+ host: str = DEFAULT_DASHBOARD_HOST,
227
+ port: int = DEFAULT_DASHBOARD_PORT,
228
+ open_browser: bool = True,
229
+ read_only: bool = True,
230
+ ) -> str:
231
+ app = create_dashboard_app(project_dir=project_dir, read_only=read_only)
232
+ url = dashboard_url(host, port)
233
+ if open_browser:
234
+ webbrowser.open(url)
235
+ uvicorn.run(app, host=host, port=port, log_level="warning")
236
+ return url
237
+
238
+
239
+ def main() -> None:
240
+ run_dashboard_server(project_dir=".")
241
+
242
+
243
+ def create_dashboard_app(*, project_dir: str, read_only: bool = True) -> Starlette:
244
+ root = resolve_dashboard_project_dir(project_dir)
245
+ app = Starlette(
246
+ debug=False,
247
+ routes=[
248
+ Route("/", _overview_page),
249
+ Route("/frame/{frame_name}", _frame_page),
250
+ Route("/archive", _archive_page),
251
+ Route("/archive/{kind}/{record_id}", _archive_detail_page),
252
+ Route("/static/app.css", _css_asset),
253
+ ],
254
+ )
255
+ app.state.project_dir = str(root)
256
+ app.state.read_only = bool(read_only)
257
+ return app
258
+
259
+
260
+ def _render_page(
261
+ request: Request,
262
+ *,
263
+ title: str,
264
+ heading: str,
265
+ body: str,
266
+ project_name: str = "",
267
+ ) -> HTMLResponse:
268
+ template = _TEMPLATE_ENV.get_template("base.html")
269
+ return HTMLResponse(
270
+ template.render(
271
+ title=title,
272
+ heading=heading,
273
+ body=body,
274
+ project_dir=request.app.state.project_dir,
275
+ project_name=project_name,
276
+ read_only=request.app.state.read_only,
277
+ frame_nav=FRAME_PAGE_ORDER,
278
+ )
279
+ )
280
+
281
+
282
+ async def _css_asset(_: Request) -> Response:
283
+ return Response(CSS, media_type="text/css")
284
+
285
+
286
+ def _render_card(title: str, value: str, detail: str = "", *, warning: bool = False) -> str:
287
+ detail_html = f"<p class='subtle'>{escape(detail)}</p>" if detail else ""
288
+ klass = "card warning" if warning else "card"
289
+ return f"<section class='{klass}'><h3>{escape(title)}</h3><p>{escape(value)}</p>{detail_html}</section>"
290
+
291
+
292
+ def _overview_body(project_dir: str) -> tuple[str, str]:
293
+ frame = FrameModel.load(project_dir)
294
+ project_name = str(((frame.facts or {}).get("identity") or {}).get("name", "")).strip()
295
+ runner = ExecutionRunner(project_dir)
296
+ health = runner.get_project_health()
297
+ stats = StateManager(str(frame_path(project_dir, "acts.yaml"))).get_stats() if frame.has_acts() else {}
298
+ archive_index = runtime_cache().get_archive_index(project_dir)
299
+ reconcile = reconcile_derivation(project_dir)
300
+ semantic = semantic_validate(frame)
301
+ consistency = frame_consistency_report(frame)
302
+ map_assessment = evaluate_map_complexity(project_dir)
303
+ map_errors, map_warnings = map_complexity_issues(map_assessment)
304
+ sync_state = _expect_sync_state(frame.acts or {})
305
+ cards = [
306
+ _render_card("Ready", "yes" if health.get("ready") else "no", "Validation and lifecycle readiness", warning=not health.get("ready")),
307
+ _render_card("Context Tokens", str(health.get("context_tokens", 0)), "Current full-context size"),
308
+ _render_card("Archive", f"{len(archive_index.index)} indexed records", "Shallow index loaded from archive"),
309
+ _render_card("Map Policy", format_map_complexity_summary(map_assessment), "Complexity and map expectation", warning=bool(map_errors)),
310
+ _render_card("Runs", str(stats.get("total_runs", 0)), "Hot plus archived"),
311
+ _render_card("Active Task", str(stats.get("active_task", "none")), "Current human-facing state"),
312
+ ]
313
+ warnings: list[str] = []
314
+ warnings.extend(str(item) for item in health.get("errors", []))
315
+ warnings.extend(str(item) for item in semantic.blocking)
316
+ warnings.extend(str(item) for item in semantic.warnings)
317
+ warnings.extend(str(item.get("message", "")) for item in consistency.get("findings", []))
318
+ warnings.extend(str(item) for item in map_warnings)
319
+ if sync_state.get("required"):
320
+ warnings.append(
321
+ f"expect.yaml sync pending for run {sync_state.get('pending_run_id') or 'unknown'}."
322
+ )
323
+ warnings = [item for item in warnings if item]
324
+ recent_decisions = (frame.acts or {}).get("decisions", []) if isinstance(frame.acts, dict) else []
325
+ recent_runs = archive_index.index[-5:] if archive_index.index else []
326
+ body = [
327
+ "<section class='grid'>",
328
+ *cards,
329
+ "</section>",
330
+ "<section class='panel stack'>",
331
+ "<div><h2>Signals</h2><p class='subtle'>Overview-first and read-only. Use drilldown pages for full YAML.</p></div>",
332
+ "<ul class='plain-list'>",
333
+ f"<li>Project: {escape(project_name or '(unnamed)')}</li>",
334
+ f"<li>Phase: {escape(str(stats.get('current_phase', 'unknown')))}</li>",
335
+ f"<li>Archive mode: {escape(str(stats.get('archive_mode', 'manual')))}</li>",
336
+ f"<li>Reconcile: {escape(str(reconcile.get('human_summary', 'No reconcile summary')))}</li>",
337
+ "</ul>",
338
+ "</section>",
339
+ ]
340
+ if warnings:
341
+ body.extend(
342
+ [
343
+ "<section class='panel warning'>",
344
+ "<h2>Lifecycle And Drift Warnings</h2>",
345
+ "<ul class='plain-list'>",
346
+ *[f"<li>{escape(item)}</li>" for item in warnings[:12]],
347
+ "</ul>",
348
+ "</section>",
349
+ ]
350
+ )
351
+ body.extend(
352
+ [
353
+ "<section class='grid'>",
354
+ "<div class='panel'><h2>Recent Decisions</h2><ul class='plain-list'>",
355
+ *[
356
+ f"<li>{escape(str(item.get('decision', '')))}"
357
+ f"{' — ' + escape(str(item.get('reasoning', ''))) if item.get('reasoning') else ''}</li>"
358
+ for item in recent_decisions[-5:]
359
+ if isinstance(item, dict)
360
+ ],
361
+ "</ul></div>",
362
+ "<div class='panel'><h2>Archive Summary</h2><ul class='plain-list'>",
363
+ *[
364
+ f"<li><a href='/archive/{escape(str(item.get('kind', '')))}"
365
+ f"/{escape(str(item.get('id', '')))}'>{escape(str(item.get('id', '')))}</a> "
366
+ f"{escape(str(item.get('summary', '')))}</li>"
367
+ for item in recent_runs
368
+ ],
369
+ "</ul></div>",
370
+ "</section>",
371
+ ]
372
+ )
373
+ return "".join(body), project_name
374
+
375
+
376
+ async def _overview_page(request: Request) -> HTMLResponse:
377
+ body, project_name = _overview_body(request.app.state.project_dir)
378
+ return _render_page(
379
+ request,
380
+ title="Haxaml Dashboard",
381
+ heading="Overview",
382
+ body=body,
383
+ project_name=project_name,
384
+ )
385
+
386
+
387
+ def _frame_body(project_dir: str, frame_name: str, q: str = "", view: str = "human") -> tuple[str, str]:
388
+ frame = FrameModel.load(project_dir)
389
+ data = frame.frame_file(frame_name)
390
+ path = frame_path(project_dir, f"{frame_name}.yaml")
391
+ project_name = str((((frame.facts or {}).get("identity")) or {}).get("name", "")).strip()
392
+ filter_text = q.strip().lower()
393
+ if view == "raw":
394
+ raw = yaml.dump(data or {}, default_flow_style=False, sort_keys=False)
395
+ return (
396
+ "<section class='toolbar'>"
397
+ f"<a class='button-link' href='/frame/{frame_name}?view=human'>Human view</a>"
398
+ f"<span class='pill'>{escape(str(path))}</span></section>"
399
+ f"<pre>{escape(raw)}</pre>",
400
+ project_name,
401
+ )
402
+
403
+ blocks: list[str] = [
404
+ "<section class='toolbar'>"
405
+ f"<a class='button-link' href='/frame/{frame_name}?view=raw'>Raw YAML</a>"
406
+ f"<span class='pill'>{escape(str(path))}</span>"
407
+ "</section>"
408
+ ]
409
+ if not isinstance(data, dict):
410
+ blocks.append(f"<section class='panel warning'><h2>{escape(frame_name)}</h2><p>File is missing.</p></section>")
411
+ return "".join(blocks), project_name
412
+
413
+ for key, value in data.items():
414
+ preview = yaml.dump(value, default_flow_style=False, sort_keys=False).strip()
415
+ haystack = f"{key}\n{preview}".lower()
416
+ if filter_text and filter_text not in haystack:
417
+ continue
418
+ blocks.append(
419
+ "<details open>"
420
+ f"<summary class='section-summary'>{escape(str(key))}</summary>"
421
+ f"<pre>{escape(preview)}</pre>"
422
+ "</details>"
423
+ )
424
+ if len(blocks) == 1:
425
+ blocks.append("<section class='panel'><p>No sections matched this filter.</p></section>")
426
+ return "".join(blocks), project_name
427
+
428
+
429
+ async def _frame_page(request: Request) -> HTMLResponse:
430
+ frame_name = request.path_params["frame_name"]
431
+ if frame_name not in FRAME_PAGE_ORDER:
432
+ return HTMLResponse("Not found", status_code=404)
433
+ body, project_name = _frame_body(
434
+ request.app.state.project_dir,
435
+ frame_name,
436
+ q=str(request.query_params.get("q", "")),
437
+ view=str(request.query_params.get("view", "human")),
438
+ )
439
+ return _render_page(
440
+ request,
441
+ title=f"FRAME: {frame_name}",
442
+ heading=f"{frame_name}.yaml",
443
+ body=body,
444
+ project_name=project_name,
445
+ )
446
+
447
+
448
+ def _archive_body(project_dir: str, q: str = "") -> tuple[str, str]:
449
+ frame = FrameModel.load(project_dir)
450
+ project_name = str((((frame.facts or {}).get("identity")) or {}).get("name", "")).strip()
451
+ archive_index = runtime_cache().get_archive_index(project_dir)
452
+ if not archive_index.exists:
453
+ return "<section class='panel'><h2>Archive</h2><p>No archive file found.</p></section>", project_name
454
+ filter_text = q.strip().lower()
455
+ items = archive_index.index
456
+ if filter_text:
457
+ items = [
458
+ item for item in items
459
+ if filter_text in yaml.dump(item, default_flow_style=False, sort_keys=False).lower()
460
+ ]
461
+ counts = archive_index.metadata.get("counts", {})
462
+ body = [
463
+ "<section class='panel'>",
464
+ "<h2>Archive Overview</h2>",
465
+ "<ul class='plain-list'>",
466
+ f"<li>Runs: {int(counts.get('runs', 0) or 0)}</li>",
467
+ f"<li>Sessions: {int(counts.get('sessions', 0) or 0)}</li>",
468
+ f"<li>Verifications: {int(counts.get('verifications', 0) or 0)}</li>",
469
+ "</ul>",
470
+ "</section>",
471
+ "<section class='panel'><h2>Indexed Records</h2><ul class='plain-list'>",
472
+ ]
473
+ for item in reversed(items[-25:]):
474
+ kind = str(item.get("kind", "")).strip()
475
+ record_id = str(item.get("id", "")).strip()
476
+ body.append(
477
+ f"<li><a href='/archive/{escape(kind)}/{escape(record_id)}'>{escape(record_id or kind)}</a> "
478
+ f"{escape(str(item.get('summary', '')))}</li>"
479
+ )
480
+ body.append("</ul></section>")
481
+ return "".join(body), project_name
482
+
483
+
484
+ async def _archive_page(request: Request) -> HTMLResponse:
485
+ body, project_name = _archive_body(
486
+ request.app.state.project_dir,
487
+ q=str(request.query_params.get("q", "")),
488
+ )
489
+ return _render_page(
490
+ request,
491
+ title="Archive",
492
+ heading="Archive",
493
+ body=body,
494
+ project_name=project_name,
495
+ )
496
+
497
+
498
+ async def _archive_detail_page(request: Request) -> HTMLResponse:
499
+ kind = str(request.path_params["kind"])
500
+ record_id = str(request.path_params["record_id"])
501
+ record = runtime_cache().load_archive_record_details(request.app.state.project_dir, kind, record_id)
502
+ if record is None:
503
+ return HTMLResponse("Not found", status_code=404)
504
+ frame = FrameModel.load(request.app.state.project_dir)
505
+ project_name = str((((frame.facts or {}).get("identity")) or {}).get("name", "")).strip()
506
+ body = (
507
+ f"<section class='toolbar'><a class='button-link' href='/archive'>Back to archive</a></section>"
508
+ f"<section class='panel'><h2>{escape(kind)}:{escape(record_id)}</h2>"
509
+ f"<pre>{escape(yaml.dump(record, default_flow_style=False, sort_keys=False))}</pre></section>"
510
+ )
511
+ return _render_page(
512
+ request,
513
+ title=f"Archive {kind}:{record_id}",
514
+ heading=f"Archive {kind}:{record_id}",
515
+ body=body,
516
+ project_name=project_name,
517
+ )
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.4
2
+ Name: haxaml-ui
3
+ Version: 0.6.7
4
+ Summary: Local read-only dashboard package for Haxaml.
5
+ Author: Hax
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/haxsysgit/haxaml
8
+ Project-URL: Repository, https://github.com/haxsysgit/haxaml
9
+ Keywords: haxaml,dashboard,ui,developer-tools
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Web Environment
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Build Tools
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: haxaml<0.7.0,>=0.6.7
20
+ Requires-Dist: jinja2>=3.1
21
+ Requires-Dist: starlette>=0.37
22
+ Requires-Dist: uvicorn>=0.30
23
+
24
+ # haxaml-ui
25
+
26
+ Read-only local dashboard package for Haxaml.
27
+
28
+ This package is the dashboard distribution itself.
29
+
30
+ The human launcher remains:
31
+
32
+ ```bash
33
+ haxaml dashboard
34
+ ```
35
+
36
+ `haxaml[ui]` is only the convenience install selector for the core package.
37
+
38
+ Install directly:
39
+
40
+ ```bash
41
+ pip install haxaml-ui
42
+ ```
43
+
44
+ Or install through the core extra:
45
+
46
+ ```bash
47
+ pip install "haxaml[ui]"
48
+ ```
49
+
50
+ Direct package entrypoint is also available:
51
+
52
+ ```bash
53
+ haxaml-dashboard
54
+ ```
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ haxaml_ui/__init__.py
4
+ haxaml_ui/dashboard.py
5
+ haxaml_ui.egg-info/PKG-INFO
6
+ haxaml_ui.egg-info/SOURCES.txt
7
+ haxaml_ui.egg-info/dependency_links.txt
8
+ haxaml_ui.egg-info/entry_points.txt
9
+ haxaml_ui.egg-info/requires.txt
10
+ haxaml_ui.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ haxaml-dashboard = haxaml_ui.dashboard:main
@@ -0,0 +1,4 @@
1
+ haxaml<0.7.0,>=0.6.7
2
+ jinja2>=3.1
3
+ starlette>=0.37
4
+ uvicorn>=0.30
@@ -0,0 +1 @@
1
+ haxaml_ui
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "haxaml-ui"
7
+ version = "0.6.7"
8
+ description = "Local read-only dashboard package for Haxaml."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "Hax" }
14
+ ]
15
+ keywords = [
16
+ "haxaml",
17
+ "dashboard",
18
+ "ui",
19
+ "developer-tools"
20
+ ]
21
+ classifiers = [
22
+ "Development Status :: 4 - Beta",
23
+ "Environment :: Web Environment",
24
+ "Intended Audience :: Developers",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Topic :: Software Development :: Build Tools"
29
+ ]
30
+ dependencies = [
31
+ "haxaml>=0.6.7,<0.7.0",
32
+ "jinja2>=3.1",
33
+ "starlette>=0.37",
34
+ "uvicorn>=0.30"
35
+ ]
36
+
37
+ [project.scripts]
38
+ haxaml-dashboard = "haxaml_ui.dashboard:main"
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/haxsysgit/haxaml"
42
+ Repository = "https://github.com/haxsysgit/haxaml"
43
+
44
+ [tool.setuptools.packages.find]
45
+ include = ["haxaml_ui*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+