@topy-ai/maggie 0.3.0 → 0.5.0

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.
package/README.md CHANGED
@@ -267,6 +267,24 @@ python3 tools/clis/maggie_design.py rebrand \
267
267
  | `maggie-auth-reference` | Generate and validate traditional email/password auth with secure server-side sessions |
268
268
  | `maggie-blog` | Run a provider-neutral blog lifecycle with stable identity, topics, feeds, settings, and rollback |
269
269
 
270
+ `maggie-design` can initialize native blog and service UI plans from a local,
271
+ read-only structural reference:
272
+
273
+ ```bash
274
+ maggie design reference-ui --project . --reference /path/to/reference \
275
+ --surface blog,service --confirm
276
+ maggie design init --project . --surface blog \
277
+ --reference-run .maggie/design/content-ui/reference-<id> --confirm
278
+ maggie design init --project . --surface service \
279
+ --reference-run .maggie/design/content-ui/reference-<id> --confirm
280
+ maggie design validate-ui --project . \
281
+ --plan .maggie/design/content-ui/blog/init-<id>/plan.json \
282
+ --rendered-dir .maggie/design/content-ui/blog/init-<id>/screenshots --confirm
283
+ ```
284
+
285
+ This reuses the host project's homepage shell and `DESIGN.md`; it does not
286
+ copy reference branding, source code, private data, or provider facts.
287
+
270
288
  The remaining installable skills are `maggie-blog-bootstrap`, `maggie-dash`,
271
289
  `maggie-clone`, `maggie-clone-to-template`, `maggie-marketplace`,
272
290
  `maggie-template`, `maggie-design`, `maggie-ops`, `maggie-deployment`,
package/bin/maggie.js CHANGED
@@ -85,6 +85,9 @@ Usage:
85
85
  maggie clone run <homepage-url> --run-id <id>
86
86
  maggie clone status --run-id <id>
87
87
  maggie design run <target-url> --clone-run <id>
88
+ maggie design reference-ui --project PATH --reference PATH --surface blog,service --confirm
89
+ maggie design init --project PATH --surface blog|service --reference-run PATH --confirm
90
+ maggie design validate-ui --project PATH --plan PATH --rendered-dir PATH --confirm
88
91
  maggie design author --project PATH --route /about --purpose TEXT --audience TEXT --confirm
89
92
  maggie auth reference --project PATH --confirm
90
93
  maggie auth check --project PATH [--production]
@@ -0,0 +1,6 @@
1
+ # Maggie Content UI Contracts
2
+
3
+ These contracts describe sanitized structural evidence captured from a local
4
+ UI reference and the native implementation plan consumed by `maggie-design`.
5
+ They intentionally exclude source-brand content, private data, credentials,
6
+ raw assets, and copied source code.
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "maggie-content-ui-plan.v1",
4
+ "type": "object",
5
+ "required": ["schemaVersion", "id", "workflow", "mode", "surface", "phase", "shell", "components", "routes", "dataAdapter", "publicWrite"],
6
+ "properties": {
7
+ "schemaVersion": { "const": "maggie-content-ui-plan.v1" },
8
+ "id": { "type": "string", "pattern": "^init-[a-f0-9]+$" },
9
+ "workflow": { "const": "maggie-design" },
10
+ "mode": { "const": "content-ui-init" },
11
+ "surface": { "enum": ["blog", "service"] },
12
+ "phase": { "const": "ready" },
13
+ "shell": { "type": "object", "required": ["sourceOfTruth", "reuse", "replaceAllowed"] },
14
+ "components": { "type": "array", "minItems": 1 },
15
+ "routes": { "type": "array", "minItems": 1 },
16
+ "dataAdapter": { "enum": ["maggie-blog", "maggie-service-booking"] },
17
+ "publicWrite": { "const": false }
18
+ }
19
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "maggie-content-ui-reference.v1",
4
+ "type": "object",
5
+ "required": ["schemaVersion", "id", "workflow", "mode", "surfaces", "sourcePolicy", "privateSourceCopied"],
6
+ "properties": {
7
+ "schemaVersion": { "const": "maggie-content-ui-reference.v1" },
8
+ "id": { "type": "string", "pattern": "^reference-[a-f0-9]+$" },
9
+ "workflow": { "const": "maggie-design" },
10
+ "mode": { "const": "reference-ui" },
11
+ "surfaces": { "type": "object", "minProperties": 1 },
12
+ "sourcePolicy": { "const": "read-only-structural-guideline" },
13
+ "privateSourceCopied": { "const": false }
14
+ }
15
+ }
@@ -32,3 +32,15 @@ published slug must not change during a rewrite. Search/sort views are not
32
32
  indexable; drafts never appear in public routes, RSS, or sitemap output.
33
33
  Provider keys remain server-side. Public publication and migrations always
34
34
  require explicit confirmation.
35
+
36
+ To initialize native front-end pages from the approved local UI guideline,
37
+ run `maggie-design`:
38
+
39
+ ```bash
40
+ maggie design init --project . --surface blog \
41
+ --reference-run .maggie/design/content-ui/reference-<id> \
42
+ --base-path /our-blogs --confirm
43
+ ```
44
+
45
+ The blog skill supplies route/data semantics; `maggie-design` supplies the
46
+ host-native components and responsive visual review.
@@ -90,6 +90,57 @@ unconfirmed writes. Generated copy and media must be marked authored rather
90
90
  than source-observed. Implementation must still capture responsive screenshots
91
91
  and run accessibility/build validation before publication.
92
92
 
93
+ ## Content UI initialization
94
+
95
+ Use content UI initialization when `maggie-blog` or
96
+ `maggie-service-booking` needs native front-end layouts based on a local UI
97
+ reference. The reference is structural evidence only. It must not replace the
98
+ project's `DESIGN.md`, homepage shell, brand, copy, assets, or provider facts.
99
+
100
+ First record a sanitized, read-only reference manifest:
101
+
102
+ ```bash
103
+ maggie design reference-ui \
104
+ --project . \
105
+ --reference /home/balalior/Dev/clients/spachevychase.org \
106
+ --surface blog,service --confirm
107
+ ```
108
+
109
+ Then initialize one surface at a time from that manifest:
110
+
111
+ ```bash
112
+ maggie design init --project . --surface blog \
113
+ --reference-run .maggie/design/content-ui/reference-<id> \
114
+ --base-path /our-blogs --confirm
115
+
116
+ maggie design init --project . --surface service \
117
+ --reference-run .maggie/design/content-ui/reference-<id> \
118
+ --route-pattern '/services/[slug]/' --confirm
119
+ ```
120
+
121
+ The initializer creates a reviewable component/route/responsive/provenance
122
+ plan under `.maggie/design/content-ui/`. It does not write public components
123
+ or publish routes. The implementation phase must call the matching data skill,
124
+ reuse the homepage shell, capture desktop/tablet/mobile screenshots, and pass
125
+ accessibility, metadata, build, and visual review gates before approval.
126
+
127
+ Validate the rendered plan before approval:
128
+
129
+ ```bash
130
+ maggie design validate-ui --project . \
131
+ --plan .maggie/design/content-ui/blog/init-<id>/plan.json \
132
+ --rendered-dir .maggie/design/content-ui/blog/init-<id>/screenshots \
133
+ --confirm
134
+ ```
135
+
136
+ This gate requires valid PNG evidence for desktop, tablet, and mobile and keeps
137
+ `publicWrite: false` until a separate approval step.
138
+
139
+ For blog UI, preserve archive, topic, post/slug, pagination, RSS, and sitemap
140
+ semantics from `maggie-blog`. For service UI, preserve provider identity,
141
+ variants, booking URLs, duplicate canonical/noindex decisions, and service
142
+ sitemap semantics from `maggie-service-booking`.
143
+
93
144
  ## Explicit homepage rebrand mode
94
145
 
95
146
  The homepage `review` mode only compares screenshots. It does not rebrand a
@@ -207,6 +207,19 @@ and `DESIGN.md`. Use the homepage header, footer, fonts, tokens, navigation,
207
207
  analytics boundary, accessibility behaviour, and responsive breakpoints.
208
208
  Only the service content region varies.
209
209
 
210
+ When initializing a new service UI from the approved reference guideline, use
211
+ the shared `maggie-design` initializer:
212
+
213
+ ```bash
214
+ maggie design init --project . --surface service \
215
+ --reference-run .maggie/design/content-ui/reference-<id> \
216
+ --route-pattern '/services/[slug]/' --confirm
217
+ ```
218
+
219
+ This creates a reviewable native component/route plan. It does not copy the
220
+ reference project's brand, source code, assets, or provider facts, and it does
221
+ not publish service pages without the existing service fact/copy gates.
222
+
210
223
  Each service page must expose the service title, category breadcrumbs, factual
211
224
  description, all active variants, duration, currency-formatted price, booking
212
225
  CTA, and payment CTA only if available. Preserve the provider booking URL as
@@ -34,6 +34,32 @@ DESIGN_HEADINGS = (
34
34
 
35
35
  REVIEW_VIEWPORTS = {"desktop": (1440, 900), "tablet": (768, 900), "mobile": (390, 844)}
36
36
 
37
+ REFERENCE_SURFACES = {
38
+ "blog": {
39
+ "files": [
40
+ "src/layouts/BlogLayout.astro", "src/components/blog/BlogArchive.astro",
41
+ "src/components/blog/PostCard.astro", "src/components/blog/Pagination.astro",
42
+ "src/components/blog/PostSidebar.astro", "src/pages/our-blogs/index.astro",
43
+ "src/pages/our-blogs/[slug].astro", "src/pages/our-blogs/topic/[slug].astro",
44
+ "src/pages/rss.xml.ts", "src/pages/sitemap.xml.ts",
45
+ ],
46
+ "components": ["BlogLayout", "BlogArchive", "PostCard", "Pagination", "PostSidebar", "ShareMenu"],
47
+ "routes": ["{base}/", "{base}/page/{n}/", "{base}/{slug}/", "{base}/topic/{slug}/", "{base}/topic/{slug}/page/{n}/", "/rss.xml", "/sitemap.xml"],
48
+ "patterns": ["compact image-backed archive hero", "URL-backed search/topic/sort controls", "responsive post grid", "sticky article sidebar", "related articles", "crawlable topic links"],
49
+ },
50
+ "service": {
51
+ "files": [
52
+ "src/pages/services/index.astro", "src/pages/services/[slug].astro",
53
+ "src/components/ServicePage.astro", "src/components/CategoryServiceHub.astro",
54
+ "src/components/SupportingServicePage.astro", "src/components/ServiceContextNarrative.astro",
55
+ "src/pages/sitemap-services.xml.ts", "src/pages/sitemap-service-categories.xml.ts",
56
+ ],
57
+ "components": ["ServiceIndex", "CategoryServiceHub", "ServicePage", "ServiceFacts", "RelatedServices", "ServiceFaq"],
58
+ "routes": ["/services/", "/services/{slug}/", "/service-categories/{slug}/", "/sitemap-services.xml", "/sitemap-service-categories.xml"],
59
+ "patterns": ["category breadcrumb hero", "variant facts and booking card", "service narrative with image", "related treatment grid", "FAQ accordion", "category navigation"],
60
+ },
61
+ }
62
+
37
63
 
38
64
  def rendered_asset_preflight(template: Path) -> list[str]:
39
65
  """Catch package CSS failures before visual comparison is trusted."""
@@ -56,6 +82,81 @@ def rendered_asset_preflight(template: Path) -> list[str]:
56
82
  return errors
57
83
 
58
84
 
85
+ def _surface_list(value: str) -> list[str]:
86
+ surfaces = [item.strip().lower() for item in value.split(",") if item.strip()]
87
+ invalid = sorted(set(surfaces) - set(REFERENCE_SURFACES))
88
+ if invalid or not surfaces:
89
+ raise ValueError("surface must contain blog and/or service")
90
+ return list(dict.fromkeys(surfaces))
91
+
92
+
93
+ def reference_ui(project: Path, reference: Path, surfaces: list[str], screenshots_dir: Path | None, confirm: bool) -> int:
94
+ """Record sanitized structural evidence from a read-only reference project."""
95
+ project = project.resolve(); reference = reference.resolve()
96
+ if not reference.is_dir(): raise ValueError(f"reference project not found: {reference}")
97
+ if not confirm: print("CONFIRMATION_REQUIRED: rerun with --confirm", file=sys.stderr); return 2
98
+ missing = {surface: [item for item in REFERENCE_SURFACES[surface]["files"] if not (reference / item).exists()] for surface in surfaces}
99
+ missing = {key: value for key, value in missing.items() if value}
100
+ if missing: raise ValueError("reference files missing: " + json.dumps(missing, ensure_ascii=False))
101
+ digest = hashlib.sha256((str(reference) + "\n" + ",".join(surfaces)).encode()).hexdigest()[:12]
102
+ output_dir = project / ".maggie" / "design" / "content-ui" / f"reference-{digest}"; output_dir.mkdir(parents=True, exist_ok=True)
103
+ evidence = {}
104
+ for surface in surfaces:
105
+ config = REFERENCE_SURFACES[surface]
106
+ evidence[surface] = {"sourceFiles": config["files"], "components": config["components"], "routes": config["routes"], "patterns": config["patterns"], "observations": {pattern: True for pattern in config["patterns"]}}
107
+ screenshots = {name: "pending" for name in REVIEW_VIEWPORTS}
108
+ if screenshots_dir:
109
+ screenshots = {name: str((screenshots_dir / f"{name}.png").resolve()) if (screenshots_dir / f"{name}.png").is_file() else "missing" for name in REVIEW_VIEWPORTS}
110
+ manifest = {"schemaVersion": "maggie-content-ui-reference.v1", "id": f"reference-{digest}", "workflow": "maggie-design", "mode": "reference-ui", "referenceProject": reference.name, "surfaces": evidence, "screenshots": screenshots, "sourcePolicy": "read-only-structural-guideline", "privateSourceCopied": False, "createdAt": datetime.now(timezone.utc).isoformat()}
111
+ path = output_dir / "reference-manifest.json"; path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
112
+ print(json.dumps({"manifest": str(path), "id": manifest["id"], "surfaces": surfaces, "screenshotStatus": screenshots}, indent=2, ensure_ascii=False)); return 0
113
+
114
+
115
+ def init_content_ui(project: Path, surface: str, reference_run: Path, base_path: str, route_pattern: str, confirm: bool) -> int:
116
+ """Create the reviewable plan that Maggie Design will implement natively."""
117
+ project = project.resolve(); surface = surface.strip().lower()
118
+ if surface not in REFERENCE_SURFACES: raise ValueError("surface must be blog or service")
119
+ if not confirm: print("CONFIRMATION_REQUIRED: rerun with --confirm", file=sys.stderr); return 2
120
+ contract = require_design_contract(project)
121
+ manifest_path = reference_run.resolve()
122
+ if manifest_path.is_dir(): manifest_path /= "reference-manifest.json"
123
+ if not manifest_path.exists(): raise ValueError(f"reference manifest required: {manifest_path}")
124
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
125
+ if surface not in manifest.get("surfaces", {}): raise ValueError(f"reference manifest does not contain surface: {surface}")
126
+ digest = hashlib.sha256((str(project) + "\n" + surface + "\n" + str(manifest_path)).encode()).hexdigest()[:12]
127
+ output_dir = project / ".maggie" / "design" / "content-ui" / surface / f"init-{digest}"; output_dir.mkdir(parents=True, exist_ok=True)
128
+ config = REFERENCE_SURFACES[surface]
129
+ routes = config["routes"]
130
+ if surface == "blog": routes = [route.replace("{base}", base_path.rstrip("/") or "/our-blogs") for route in routes]
131
+ elif route_pattern: routes = [route_pattern, "/sitemap-services.xml", "/sitemap-service-categories.xml"]
132
+ plan = {"schemaVersion": "maggie-content-ui-plan.v1", "id": f"init-{digest}", "workflow": "maggie-design", "mode": "content-ui-init", "surface": surface, "phase": "ready", "referenceManifest": str(manifest_path), "designContract": contract, "shell": {"sourceOfTruth": "existing homepage shell", "reuse": ["header", "footer", "tokens", "typography", "icons", "breakpoints", "accessibility", "analytics"], "replaceAllowed": False}, "components": config["components"], "routes": routes, "dataAdapter": "maggie-blog" if surface == "blog" else "maggie-service-booking", "provenance": {"referenceObserved": True, "projectPreserved": True, "agentAuthored": True, "providerFact": surface == "service"}, "requiredSteps": ["inspect-host-shell", "create-native-components", "bind-data-adapter", "capture-desktop-tablet-mobile", "accessibility-check", "metadata-and-route-check", "build-check", "explicit-approval"], "publicWrite": False, "createdAt": datetime.now(timezone.utc).isoformat()}
133
+ files = {"reference-manifest.json": manifest, "shell-reuse.json": plan["shell"], "component-plan.json": {"components": config["components"], "patterns": config["patterns"]}, "route-plan.json": {"routes": routes, "dataAdapter": plan["dataAdapter"]}, "responsive-plan.json": {"viewports": REVIEW_VIEWPORTS, "mobileStickySidebar": False if surface == "blog" else None}, "provenance.json": plan["provenance"], "validation.json": {"status": "pending", "required": plan["requiredSteps"]}}
134
+ for filename, value in files.items(): (output_dir / filename).write_text(json.dumps(value, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
135
+ (output_dir / "screenshots").mkdir(exist_ok=True)
136
+ (output_dir / "plan.json").write_text(json.dumps(plan, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
137
+ print(json.dumps({"plan": str(output_dir / "plan.json"), "surface": surface, "phase": "ready", "publicWrite": False}, indent=2)); return 0
138
+
139
+
140
+ def validate_content_ui(project: Path, plan_path: Path, rendered_dir: Path | None, confirm: bool) -> int:
141
+ """Validate a generated content UI plan before host publication."""
142
+ project = project.resolve(); plan_path = plan_path.resolve()
143
+ if not plan_path.exists(): raise ValueError(f"content UI plan not found: {plan_path}")
144
+ plan = json.loads(plan_path.read_text(encoding="utf-8")); errors = []
145
+ if plan.get("workflow") != "maggie-design" or plan.get("mode") != "content-ui-init": errors.append("invalid design workflow plan")
146
+ if plan.get("publicWrite") is not False: errors.append("publicWrite must remain false before approval")
147
+ try: require_design_contract(project)
148
+ except ValueError as error: errors.append(str(error))
149
+ screenshot_status = {}
150
+ for name in REVIEW_VIEWPORTS:
151
+ path = (rendered_dir / f"{name}.png") if rendered_dir else plan_path.parent / "screenshots" / f"{name}.png"
152
+ valid = path.is_file() and path.read_bytes()[:8] == b"\x89PNG\r\n\x1a\n"
153
+ screenshot_status[name] = {"path": str(path), "validPng": valid}
154
+ if not valid: errors.append(f"missing valid {name} screenshot")
155
+ result = {"schemaVersion": "maggie-content-ui-validation.v1", "plan": str(plan_path), "surface": plan.get("surface"), "screenshots": screenshot_status, "checks": {"shellReuse": plan.get("shell", {}).get("replaceAllowed") is False, "publicWriteBlocked": plan.get("publicWrite") is False, "designContract": not any("DESIGN.md" in error for error in errors)}, "status": "passed" if not errors else "failed", "errors": errors, "validatedAt": datetime.now(timezone.utc).isoformat()}
156
+ if not confirm: print(json.dumps(result, indent=2, ensure_ascii=False)); return 0 if not errors else 1
157
+ output = plan_path.parent / "validation.json"; output.write_text(json.dumps(result, indent=2, ensure_ascii=False) + "\n", encoding="utf-8"); print(json.dumps(result, indent=2, ensure_ascii=False)); return 0 if not errors else 1
158
+
159
+
59
160
  def rebrand_template(args: argparse.Namespace) -> int:
60
161
  """Apply an explicit brand identity to a packaged homepage template."""
61
162
  template = args.template.resolve()
@@ -479,6 +580,42 @@ def author_job(project: Path, route: str, purpose: str, audience: str, brief_fil
479
580
 
480
581
 
481
582
  def main() -> int:
583
+ if len(sys.argv) > 1 and sys.argv[1] == "reference-ui":
584
+ command = argparse.ArgumentParser(description="Record sanitized blog/service UI evidence from a read-only reference project.")
585
+ command.add_argument("--project", type=Path, default=Path.cwd())
586
+ command.add_argument("--reference", type=Path, required=True)
587
+ command.add_argument("--surface", default="blog,service")
588
+ command.add_argument("--screenshots-dir", type=Path)
589
+ command.add_argument("--confirm", action="store_true")
590
+ args = command.parse_args(sys.argv[2:])
591
+ try:
592
+ return reference_ui(args.project, args.reference, _surface_list(args.surface), args.screenshots_dir, args.confirm)
593
+ except (OSError, ValueError, json.JSONDecodeError) as error:
594
+ print(f"BLOCKED: maggie-design reference-ui: {error}", file=sys.stderr); return 1
595
+ if len(sys.argv) > 1 and sys.argv[1] == "init":
596
+ command = argparse.ArgumentParser(description="Initialize a native blog or service UI plan from a sanitized reference manifest.")
597
+ command.add_argument("--project", type=Path, default=Path.cwd())
598
+ command.add_argument("--surface", required=True, choices=["blog", "service"])
599
+ command.add_argument("--reference-run", type=Path, required=True)
600
+ command.add_argument("--base-path", default="/our-blogs")
601
+ command.add_argument("--route-pattern", default="/services/[slug]/")
602
+ command.add_argument("--confirm", action="store_true")
603
+ args = command.parse_args(sys.argv[2:])
604
+ try:
605
+ return init_content_ui(args.project, args.surface, args.reference_run, args.base_path, args.route_pattern, args.confirm)
606
+ except (OSError, ValueError, json.JSONDecodeError) as error:
607
+ print(f"BLOCKED: maggie-design init: {error}", file=sys.stderr); return 1
608
+ if len(sys.argv) > 1 and sys.argv[1] == "validate-ui":
609
+ command = argparse.ArgumentParser(description="Validate content UI screenshots and design/public-write gates.")
610
+ command.add_argument("--project", type=Path, default=Path.cwd())
611
+ command.add_argument("--plan", type=Path, required=True)
612
+ command.add_argument("--rendered-dir", type=Path)
613
+ command.add_argument("--confirm", action="store_true")
614
+ args = command.parse_args(sys.argv[2:])
615
+ try:
616
+ return validate_content_ui(args.project, args.plan, args.rendered_dir, args.confirm)
617
+ except (OSError, ValueError, json.JSONDecodeError) as error:
618
+ print(f"BLOCKED: maggie-design validate-ui: {error}", file=sys.stderr); return 1
482
619
  if len(sys.argv) > 1 and sys.argv[1] == "rebrand":
483
620
  rebrand = argparse.ArgumentParser(description="Apply an explicit brand identity to a packaged marketplace template.")
484
621
  rebrand.add_argument("--template", type=Path, required=True)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topy-ai/maggie",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Install and manage Maggie Skills for AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "module",