@topy-ai/maggie 0.7.52 → 0.7.54

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.
Files changed (79) hide show
  1. package/README-zh-TW.md +28 -4
  2. package/README.md +93 -60
  3. package/bin/maggie.js +67 -12
  4. package/bundled-contracts/maggie-assistant-tools/booking-receptionist-example-v1.json +161 -0
  5. package/bundled-contracts/maggie-assistant-tools/tool-registry-v1.schema.json +268 -20
  6. package/bundled-contracts/maggie-deployment/bridge-host-example-v1.json +49 -7
  7. package/bundled-contracts/maggiedash/booking-email-templates-v1.json +181 -13
  8. package/bundled-contracts/maggiedash/booking-rules-v1.json +1699 -0
  9. package/bundled-contracts/maggiedash/stripe-booking-capabilities-v1.json +398 -39
  10. package/bundled-references/maggiedash-booking/PRD.md +22 -11
  11. package/bundled-references/maggiedash-booking/QA-TEST-PLAN.md +1 -1
  12. package/bundled-references/maggiedash-booking/RULES.md +167 -0
  13. package/bundled-references/maggiedash-booking/RUNTIME-OPERATIONS.md +2 -2
  14. package/bundled-references/maggiedash-booking/STRIPE-INTEGRATION.md +42 -4
  15. package/bundled-references/provider-adapter-contract.md +16 -3
  16. package/bundled-skills/catalog.json +1 -1
  17. package/bundled-skills/maggie-assistant-tools/SKILL.md +54 -3
  18. package/bundled-skills/maggie-assistant-tools/references/approvals-and-audit.md +27 -0
  19. package/bundled-skills/maggie-assistant-tools/references/booking-receptionist.md +45 -0
  20. package/bundled-skills/maggie-assistant-tools/references/bridge-host.md +54 -0
  21. package/bundled-skills/maggie-auth-reference/SKILL.md +18 -0
  22. package/bundled-skills/maggie-blog/SKILL.md +8 -0
  23. package/bundled-skills/maggie-booking/SKILL.md +66 -14
  24. package/bundled-skills/maggie-content-localization/SKILL.md +26 -0
  25. package/bundled-skills/maggie-dash/SKILL.md +28 -2
  26. package/bundled-skills/maggie-deployment/SKILL.md +48 -0
  27. package/bundled-skills/maggie-deployment/references/cloudflare.md +19 -0
  28. package/bundled-skills/maggie-deployment/references/vps.md +92 -0
  29. package/bundled-skills/maggie-design/SKILL.md +98 -3
  30. package/bundled-skills/maggie-design/references/app-ui-patterns.md +79 -0
  31. package/bundled-skills/maggie-feedback/SKILL.md +27 -2
  32. package/bundled-skills/maggie-marketplace/SKILL.md +12 -0
  33. package/bundled-skills/maggie-marketplace/references/directory-search.md +51 -0
  34. package/bundled-skills/maggie-marketplace/references/directory.mjs +112 -0
  35. package/bundled-skills/maggie-mobile-app/SKILL.md +65 -6
  36. package/bundled-skills/maggie-mobile-app/references/payments-and-push.md +78 -0
  37. package/bundled-skills/maggie-qa-workflow/SKILL.md +60 -0
  38. package/bundled-skills/maggie-seo-geo/SKILL.md +33 -0
  39. package/bundled-skills/maggie-service-booking/SKILL.md +28 -3
  40. package/bundled-skills/maggie-social-share/SKILL.md +56 -4
  41. package/bundled-skills/maggie-social-share/references/queue-and-connections.md +28 -0
  42. package/bundled-tools/clis/maggie_assistant_tools.py +84 -3
  43. package/bundled-tools/clis/maggie_booking.py +24 -12
  44. package/bundled-tools/clis/maggie_booking_connect_qa.py +136 -0
  45. package/bundled-tools/clis/maggie_booking_rules.py +476 -0
  46. package/bundled-tools/clis/maggie_bridge_host.py +77 -1
  47. package/bundled-tools/clis/maggie_dash.py +173 -3
  48. package/bundled-tools/clis/maggie_deployment_ops.py +524 -0
  49. package/bundled-tools/clis/maggie_design.py +15 -2
  50. package/bundled-tools/clis/maggie_design_bulk.py +290 -0
  51. package/bundled-tools/clis/maggie_design_lint.py +205 -3
  52. package/bundled-tools/clis/maggie_feedback.py +138 -28
  53. package/bundled-tools/clis/maggie_live_urls.py +138 -0
  54. package/bundled-tools/clis/maggie_localization.py +3 -0
  55. package/bundled-tools/clis/maggie_localization_keys.py +151 -0
  56. package/bundled-tools/clis/maggie_media_generate.py +174 -0
  57. package/bundled-tools/clis/maggie_mobile.py +219 -4
  58. package/bundled-tools/clis/maggie_product_release.py +49 -1
  59. package/bundled-tools/clis/maggie_qa_app_preflight.py +122 -4
  60. package/bundled-tools/clis/maggie_qa_fit.py +217 -0
  61. package/bundled-tools/clis/maggie_qa_workflow.py +5 -0
  62. package/bundled-tools/clis/maggie_schema_complexity.py +84 -0
  63. package/bundled-tools/clis/maggie_seo_routes.py +154 -0
  64. package/bundled-tools/clis/maggie_service_migration.py +272 -0
  65. package/bundled-tools/clis/maggie_social_queue.py +2 -0
  66. package/bundled-tools/clis/maggie_social_share.py +174 -7
  67. package/bundled-tools/integrations/maggie-api-pull.md +22 -0
  68. package/bundled-tools/runtime/assistant_approval.py +98 -0
  69. package/package.json +1 -1
  70. package/references/provider-adapter-contract.md +16 -3
  71. package/bundled-contracts/maggiedash/execution-board.json +0 -5463
  72. package/bundled-references/maggiedash-booking/CURRENT-STATE.md +0 -210
  73. package/bundled-references/maggiedash-booking/EXECUTION-BOARD.json +0 -5463
  74. package/bundled-references/maggiedash-booking/ORAWELLNESS-INTEGRATION-AUDIT.md +0 -270
  75. package/bundled-references/maggiedash-booking/PROGRESS.md +0 -279
  76. package/bundled-references/maggiedash-booking/README.md +0 -288
  77. package/bundled-references/maggiedash-booking/SKILLS-AND-CLI.md +0 -551
  78. package/bundled-references/maggiedash-booking/TASK-RUNBOOK.md +0 -107
  79. package/bundled-references/maggiedash-booking/TASKS.md +0 -178
@@ -252,7 +252,7 @@ def merge_astro_middleware(path: Path) -> dict[str, object]:
252
252
  return {"status": "updated", "reason": "inserted idempotent Maggie rewrite block"}
253
253
 
254
254
 
255
- def manifest_host_file_pairs(source: Path, manifest: dict[str, object], root: Path, framework: str | None) -> list[tuple[Path, Path, Path]]:
255
+ def manifest_host_file_pairs(source: Path, manifest: dict[str, object], root: Path, framework: str | None, required: bool = True) -> list[tuple[Path, Path, Path]]:
256
256
  """Return non-destructive host files for a declared framework adapter."""
257
257
  if not framework:
258
258
  return []
@@ -260,6 +260,8 @@ def manifest_host_file_pairs(source: Path, manifest: dict[str, object], root: Pa
260
260
  if not isinstance(bootstrap, dict):
261
261
  return []
262
262
  spec = bootstrap.get(framework)
263
+ if not isinstance(spec, dict) and not required:
264
+ return []
263
265
  if not isinstance(spec, dict):
264
266
  raise RuntimeError(
265
267
  f"MaggieDash distribution at {source} does not provide a {framework} host bootstrap; "
@@ -288,6 +290,128 @@ def manifest_host_file_pairs(source: Path, manifest: dict[str, object], root: Pa
288
290
  return pairs
289
291
 
290
292
 
293
+ CORE_MODULE = "core"
294
+
295
+
296
+ def module_names(manifest: dict[str, object]) -> list[str]:
297
+ modules = manifest.get("modules")
298
+ return sorted(modules) if isinstance(modules, dict) else []
299
+
300
+
301
+ def effective_manifest(manifest: dict[str, object], modules: list[str]) -> dict[str, object]:
302
+ """Core entries plus the root files and workspaces of the chosen modules."""
303
+ result = dict(manifest)
304
+ specs = manifest.get("modules") if isinstance(manifest.get("modules"), dict) else {}
305
+ root_files = list(manifest.get("rootFiles", []))
306
+ workspaces = list(manifest.get("workspaces", []))
307
+ for name in modules:
308
+ spec = specs.get(name) or {}
309
+ root_files.extend(spec.get("rootFiles", []))
310
+ workspaces.extend(spec.get("workspaces", []))
311
+ result["rootFiles"] = root_files
312
+ result["workspaces"] = workspaces
313
+ return result
314
+
315
+
316
+ def resolve_modules(manifest: dict[str, object], requested: list[str], previous: list[str]) -> list[str]:
317
+ available = module_names(manifest)
318
+ if requested and not available:
319
+ # A release without the module split installs everything already.
320
+ return []
321
+ unknown = sorted(set(requested) - set(available))
322
+ if unknown:
323
+ raise RuntimeError(f"unknown MaggieDash module(s): {', '.join(unknown)}; this release offers: {', '.join(available) or 'none'}")
324
+ # Re-running install keeps refreshing modules that were added before.
325
+ return sorted((set(requested) | set(previous)) & set(available))
326
+
327
+
328
+ def installed_state(root: Path) -> dict[str, object]:
329
+ try:
330
+ value = json.loads((root / ".maggie" / "dash-install.json").read_text(encoding="utf-8"))
331
+ except (OSError, json.JSONDecodeError):
332
+ return {}
333
+ return value if isinstance(value, dict) else {}
334
+
335
+
336
+ def package_dependencies(root: Path) -> set[str]:
337
+ try:
338
+ value = json.loads((root / "package.json").read_text(encoding="utf-8"))
339
+ except (OSError, json.JSONDecodeError):
340
+ return set()
341
+ return set(value.get("dependencies") or {}) | set(value.get("devDependencies") or {})
342
+
343
+
344
+ SERVICE_WORDS = re.compile(r"\b(book (?:now|an? (?:appointment|session|treatment|table))|appointments?|treatments?|reservations?|book online|opening hours)\b", re.I)
345
+ LOCALE_DIR = re.compile(r"^(?:[a-z]{2}(?:-[a-z]{2,4})?)$")
346
+
347
+
348
+ def analyse_host(root: Path, framework: str | None) -> dict[str, object]:
349
+ """What the host already has, read from files only (nothing is executed)."""
350
+ deps = package_dependencies(root)
351
+ auth = sorted(name for name in deps if name in {"firebase", "firebase-admin", "next-auth", "lucia", "better-auth", "@supabase/supabase-js", "@supabase/ssr", "auth-astro"} or name.startswith(("@clerk/", "@auth/")))
352
+ database = sorted(name for name in deps if name in {"pg", "postgres", "@neondatabase/serverless", "drizzle-orm", "prisma", "@prisma/client", "mysql2", "better-sqlite3", "@libsql/client", "kysely"})
353
+ for config in ("wrangler.json", "wrangler.jsonc", "wrangler.toml"):
354
+ path = root / config
355
+ if path.is_file() and "d1_databases" in path.read_text(encoding="utf-8", errors="replace"):
356
+ database.append("cloudflare-d1")
357
+ payments = sorted(name for name in deps if name in {"stripe", "@stripe/stripe-js", "@paypal/checkout-server-sdk", "@lemonsqueezy/lemonsqueezy.js"})
358
+ source = root / "src"
359
+ service_hits: list[str] = []
360
+ if source.is_dir():
361
+ for path in sorted(source.rglob("*")):
362
+ if len(service_hits) >= 5:
363
+ break
364
+ if path.is_file() and path.suffix in {".astro", ".md", ".mdx", ".tsx", ".jsx", ".ts", ".json", ".html", ".vue", ".svelte"} and "_maggie" not in path.parts and "maggie" not in path.parts:
365
+ try:
366
+ if SERVICE_WORDS.search(path.read_text(encoding="utf-8", errors="replace")[:200_000]):
367
+ service_hits.append(str(path.relative_to(root)))
368
+ except OSError:
369
+ continue
370
+ blog = any((root / candidate).exists() for candidate in ("src/content/blog", "src/content/posts", "src/pages/blog", "src/pages/blog.astro", "src/pages/posts", "content/blog", "app/blog"))
371
+ locales: set[str] = set()
372
+ for pages in (root / "src" / "pages", root / "app"):
373
+ if pages.is_dir():
374
+ locales.update(child.name for child in pages.iterdir() if child.is_dir() and LOCALE_DIR.match(child.name) and child.name not in {"api", "og"})
375
+ astro_config = next((path for path in root.glob("astro.config.*")), None)
376
+ if astro_config:
377
+ match = re.search(r"locales\s*:\s*\[([^\]]+)\]", astro_config.read_text(encoding="utf-8", errors="replace"))
378
+ if match:
379
+ locales.update(re.findall(r"[\"']([A-Za-z-]+)[\"']", match.group(1)))
380
+ return {
381
+ "framework": framework,
382
+ "auth": auth,
383
+ "database": sorted(set(database)),
384
+ "payments": payments,
385
+ "sellsServices": bool(service_hits),
386
+ "serviceEvidence": service_hits,
387
+ "blog": blog,
388
+ "locales": sorted(locales),
389
+ }
390
+
391
+
392
+ def recommendations(analysis: dict[str, object], available: list[str], installed: list[str]) -> list[dict[str, str]]:
393
+ result: list[dict[str, str]] = []
394
+ if "booking" in available and "booking" not in installed:
395
+ if analysis.get("sellsServices"):
396
+ notes = []
397
+ if not any(name in {"pg", "postgres", "@neondatabase/serverless"} for name in analysis.get("database", [])):
398
+ notes.append("it needs PostgreSQL, which this project does not use yet")
399
+ if analysis.get("auth"):
400
+ notes.append(f"the site already signs people in with {', '.join(analysis['auth'])}; MaggieDash keeps its own staff sign-in")
401
+ reason = "The site offers appointments or services (" + ", ".join(analysis.get("serviceEvidence", [])[:3]) + ")" + ("; " + "; ".join(notes) if notes else "")
402
+ result.append({"kind": "module", "module": "booking", "recommended": "yes", "reason": reason, "command": "maggie dash add booking --confirm"})
403
+ else:
404
+ result.append({"kind": "module", "module": "booking", "recommended": "no", "reason": "No appointments or bookable services found in src/; add it only if the business takes bookings.", "command": "maggie dash add booking --confirm"})
405
+ if analysis.get("blog"):
406
+ result.append({"kind": "skill", "skill": "maggie-blog", "recommended": "yes", "reason": "The site has a blog; manage posts from the dashboard Blog panel.", "command": "use the maggie-blog skill"})
407
+ result.append({"kind": "skill", "skill": "maggie-social-share", "recommended": "maybe", "reason": "Blog posts can be shared to social channels after publishing.", "command": "use the maggie-social-share skill"})
408
+ else:
409
+ result.append({"kind": "skill", "skill": "maggie-blog-bootstrap", "recommended": "maybe", "reason": "No blog found; add one only if the site will publish articles.", "command": "use the maggie-blog-bootstrap skill"})
410
+ if len(analysis.get("locales", [])) > 1:
411
+ result.append({"kind": "skill", "skill": "maggie-content-localization", "recommended": "yes", "reason": f"The site serves {', '.join(analysis['locales'])}; keep translations in the Languages panel.", "command": "use the maggie-content-localization skill"})
412
+ return result
413
+
414
+
291
415
  def command_install_diff(source: Path, manifest: dict[str, object], target: Path, compare_dir: Path | None, force: bool, host_pairs: list[tuple[Path, Path, Path]] | None = None, host_framework: str | None = None) -> dict[str, object]:
292
416
  pairs = manifest_file_pairs(source, manifest, target)
293
417
  root_pairs = manifest_root_file_pairs(source, manifest, target.parents[1])
@@ -386,7 +510,16 @@ def command_install(args: argparse.Namespace) -> int:
386
510
  raise RuntimeError("unsupported MaggieDash distribution schema")
387
511
  if manifest.get("target") != "_maggie/admin" and args.target == "_maggie/admin":
388
512
  raise RuntimeError("MaggieDash manifest target is not ./_maggie/admin")
513
+ previous = installed_state(root)
514
+ legacy = not module_names(manifest)
515
+ modules = resolve_modules(manifest, list(getattr(args, "with_modules", None) or []) + (module_names(manifest) if getattr(args, "all_modules", False) else []), [name for name in previous.get("modules", []) if name != CORE_MODULE])
516
+ module_specs = manifest.get("modules") if isinstance(manifest.get("modules"), dict) else {}
517
+ manifest = effective_manifest(manifest, modules)
389
518
  host_pairs = manifest_host_file_pairs(source, manifest, root, host_framework)
519
+ for name in modules:
520
+ host_pairs += manifest_host_file_pairs(source, module_specs[name], root, host_framework, required=False)
521
+ analysis = analyse_host(root, host_framework)
522
+ advice = recommendations(analysis, module_names({"modules": module_specs}), modules)
390
523
  compare_dir = None
391
524
  if args.existing_dir:
392
525
  compare_dir = Path(args.existing_dir).expanduser()
@@ -394,7 +527,9 @@ def command_install(args: argparse.Namespace) -> int:
394
527
  compare_dir = root / compare_dir
395
528
  compare_dir = compare_dir.resolve()
396
529
  if args.dry_run or args.diff:
397
- emit(command_install_diff(source, manifest, target, compare_dir, args.force, host_pairs, host_framework))
530
+ plan = command_install_diff(source, manifest, target, compare_dir, args.force, host_pairs, host_framework)
531
+ plan.update({"modules": [CORE_MODULE, *modules] if not legacy else ["all (release without modules)"], "availableModules": module_names({"modules": module_specs}), "host": analysis, "recommendations": advice})
532
+ emit(plan)
398
533
  return 0
399
534
  installed: list[str] = []
400
535
  preserved: list[str] = []
@@ -471,18 +606,39 @@ def command_install(args: argparse.Namespace) -> int:
471
606
  "hostPreserved": sorted(set(host_preserved)),
472
607
  "hostUpdated": sorted(set(host_updated)),
473
608
  "hostWarnings": sorted(set(host_warnings)),
609
+ "modules": [CORE_MODULE, *modules] if not legacy else ["legacy-all"],
474
610
  "updatedAt": utc_now(),
475
611
  }
476
612
  state_path = root / ".maggie" / "dash-install.json"
477
613
  state_path.parent.mkdir(parents=True, exist_ok=True)
478
614
  state_path.write_text(json.dumps(state, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
479
- emit({"status": "needs-attention" if host_warnings else "installed", "version": manifest.get("version"), "target": str(target), "hostFramework": host_framework, "hostInstalled": len(set(host_installed)), "hostPreserved": len(set(host_preserved)), "hostUpdated": len(set(host_updated)), "hostWarnings": host_warnings, "installed": len(set(installed)), "preserved": len(set(preserved)), "state": str(state_path)})
615
+ notices = ["this MaggieDash release has no module split, so its Booking workspace was installed with the core; use MaggieDash 0.2.14 or later to install the core first"] if legacy and manifest.get("workspaces") else []
616
+ emit({"status": "needs-attention" if host_warnings else "installed", "version": manifest.get("version"), "modules": state["modules"], "notices": notices, "recommendations": advice, "next": "Add a recommended module with `maggie dash add <module> --confirm`; nothing optional is installed without that step." if advice and not legacy else None, "target": str(target), "hostFramework": host_framework, "hostInstalled": len(set(host_installed)), "hostPreserved": len(set(host_preserved)), "hostUpdated": len(set(host_updated)), "hostWarnings": host_warnings, "installed": len(set(installed)), "preserved": len(set(preserved)), "state": str(state_path)})
480
617
  return 1 if host_warnings else 0
481
618
  finally:
482
619
  if temporary is not None:
483
620
  temporary.cleanup()
484
621
 
485
622
 
623
+ def command_add(args: argparse.Namespace) -> int:
624
+ """Install one optional module next to an existing (or new) core install."""
625
+ args.with_modules = list(args.modules)
626
+ args.all_modules = False
627
+ return command_install(args)
628
+
629
+
630
+ def command_recommend(args: argparse.Namespace) -> int:
631
+ root = project_root(args)
632
+ framework = detect_host_framework(root, args.host)
633
+ state = installed_state(root)
634
+ installed = [name for name in state.get("modules", []) if name != CORE_MODULE]
635
+ analysis = analyse_host(root, framework)
636
+ # The module list comes from the installed release when known; Booking is
637
+ # the optional code module MaggieDash ships today.
638
+ emit({"status": "recommendations", "installed": state.get("modules", []), "host": analysis, "recommendations": recommendations(analysis, ["booking"], installed)})
639
+ return 0
640
+
641
+
486
642
  def command_init(args: argparse.Namespace) -> int:
487
643
  require_confirm(args)
488
644
  root = project_root(args)
@@ -832,8 +988,22 @@ def parser() -> argparse.ArgumentParser:
832
988
  install.add_argument("--dry-run", action="store_true", help="show the install plan without writing files")
833
989
  install.add_argument("--diff", action="store_true", help="show the install plan and compare with an existing workspace")
834
990
  install.add_argument("--existing-dir", help="existing dashboard source directory to compare during --diff")
991
+ install.add_argument("--with", dest="with_modules", action="append", default=[], metavar="MODULE", help="also install an optional module (repeatable), e.g. --with booking")
992
+ install.add_argument("--all-modules", action="store_true", help="install every optional module (the pre-0.2.14 behaviour)")
835
993
  install.add_argument("--confirm", action="store_true")
836
994
  install.set_defaults(func=command_install)
995
+ add = sub.add_parser("add", help="add an optional MaggieDash module (e.g. booking) to an installed core")
996
+ add.add_argument("modules", nargs="+", metavar="MODULE")
997
+ for flag, options in (("--project", {"default": "."}), ("--source", {"help": "local checkout or Git URL; defaults to MaggieDash repository"}), ("--ref", {"default": "main"}), ("--target", {"default": "_maggie/admin"}), ("--existing-dir", {})):
998
+ add.add_argument(flag, **options)
999
+ add.add_argument("--host", choices=["auto", "astro", "none"], default="auto")
1000
+ for flag in ("--force", "--dry-run", "--diff", "--confirm"):
1001
+ add.add_argument(flag, action="store_true")
1002
+ add.set_defaults(func=command_add)
1003
+ recommend = sub.add_parser("recommend", help="analyse the host project and recommend optional modules and skills")
1004
+ recommend.add_argument("--project", default=".")
1005
+ recommend.add_argument("--host", choices=["auto", "astro", "none"], default="auto")
1006
+ recommend.set_defaults(func=command_recommend)
837
1007
  status = sub.add_parser("status", help="inspect project and documents")
838
1008
  status.add_argument("--project", default=".")
839
1009
  status.add_argument("--project-id")