@topy-ai/maggie 0.7.1 → 0.7.2

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
@@ -66,8 +66,9 @@ Maggie in this order:
66
66
  ```bash
67
67
  python3 tools/clis/maggie.py analyze . --json --save
68
68
  python3 tools/clis/maggie.py bootstrap interview .
69
- python3 tools/clis/maggie_dash.py init --project . --confirm
70
- python3 tools/clis/maggie_dash.py migrate --project . --confirm
69
+ maggie dash install --project . --confirm
70
+ maggie dash init --project . --confirm
71
+ maggie dash migrate --project . --confirm
71
72
  ```
72
73
 
73
74
  The normal daily loop is:
@@ -88,7 +89,7 @@ The CLI provides the installer plus durable workflow commands:
88
89
  maggie init | install | update | remove | list | doctor
89
90
  maggie cleanup --project . [--confirm]
90
91
  maggie bootstrap interview | phase ...
91
- maggie dash init | status | migrate | cms ...
92
+ maggie dash install | init | status | migrate | cms ...
92
93
  maggie dash transition ... # explicit content approval transition
93
94
  maggie dash variant ... # service variant create/review/preview/publish
94
95
  maggie clone ... # authorized homepage capture
@@ -190,8 +191,8 @@ artifact schemas.
190
191
  Recommended upgrade sequence for the current release:
191
192
 
192
193
  ```bash
193
- npx @topy-ai/maggie@0.7.1 update --project . --force
194
- npx @topy-ai/maggie@0.7.1 cleanup --project .
194
+ npx @topy-ai/maggie@0.7.2 update --project . --force
195
+ npx @topy-ai/maggie@0.7.2 cleanup --project .
195
196
  ```
196
197
 
197
198
  Maintainers should pass npm credentials through the repository helper, never
@@ -201,7 +202,8 @@ as a command-line argument:
201
202
  node scripts/publish-npm.mjs --maggie-env-file ../.env
202
203
  ```
203
204
 
204
- The 0.7.1 workflow adds audited MaggieDash CMS operations (`cms revisions`,
205
+ The 0.7.2 workflow adds the installable MaggieDash admin distribution and
206
+ audited CMS operations (`cms revisions`,
205
207
  `trash`, `restore`, `schedule`, `duplicate`, `redirect`, and signed
206
208
  `preview`), import-authoritative service matching, shared translation indexes,
207
209
  sanitized seed manifests, lockfile/analytics traffic checks, semantic sitemap
@@ -215,11 +217,20 @@ For a new project, establish the local content and approval foundation before
215
217
  connecting providers or publishing:
216
218
 
217
219
  ```bash
220
+ maggie dash install --project . --confirm
218
221
  maggie dash init --project . --confirm
219
222
  maggie dash status --project .
220
223
  maggie doctor --project .
221
224
  ```
222
225
 
226
+ `maggie dash install` installs the first-party private MaggieDash dashboard
227
+ source into `./_maggie/admin`, preserving local files unless `--force` is used.
228
+ It records the resolved source revision in `.maggie/dash-install.json`. During
229
+ bootstrap this is enabled by default; use `--dash-source /path/to/MaggieDash`
230
+ for local development or a configured private Git URL. The host project keeps
231
+ ownership of routes, email/password auth, database, provider credentials, and
232
+ the `/api/maggie/*` adapter.
233
+
223
234
  Content remains draft-first and external writes remain explicit. Use the
224
235
  project-local memory workflow for confirmed preferences and reusable lessons;
225
236
  feedback drafts are never promoted to active memory automatically.
package/bin/maggie.js CHANGED
@@ -65,7 +65,7 @@ Usage:
65
65
  maggie list
66
66
  maggie doctor [--project PATH]
67
67
  maggie bootstrap interview [project]
68
- maggie dash init|status|migrate|transition|variant|cms --project PATH [options]
68
+ maggie dash init|install|status|migrate|transition|variant|cms --project PATH [options]
69
69
  maggie dash status --project PATH
70
70
  maggie dash migrate --project PATH --confirm
71
71
  maggie content FILE --source PROVIDER --project PATH --confirm
@@ -5,8 +5,30 @@ description: Manage the MaggieDash project foundation, local content store, and
5
5
 
6
6
  # MaggieDash
7
7
 
8
- Use the provider-neutral MaggieDash contracts and CLI for project setup and
9
- content operations.
8
+ Use the provider-neutral MaggieDash contracts and CLI for project setup,
9
+ installation of the first-party admin workspace, and content operations.
10
+
11
+ ## Install the admin workspace
12
+
13
+ During bootstrap, the recommended decision is to install MaggieDash into
14
+ `./_maggie/admin`. The dashboard source lives in the private first-party
15
+ `TOPY-AI-LTD/MaggieDash` repository during the MVP phase; Git/gh credentials
16
+ must already be configured for the project owner.
17
+
18
+ ```bash
19
+ maggie dash install --project . --confirm
20
+ ```
21
+
22
+ The command is idempotent, preserves existing files unless `--force` is
23
+ explicitly supplied, and records the resolved revision in
24
+ `.maggie/dash-install.json`. It never accepts or stores a GitHub token as a
25
+ CLI argument. For local development or a pinned checkout, use
26
+ `--source /path/to/MaggieDash` or `--source URL --ref REF`.
27
+
28
+ The installed dashboard owns the React workspace UI. The host project still
29
+ owns the framework route, email/password session middleware, database, media
30
+ storage, provider credentials, and `/api/maggie/*` adapter endpoints. Read the
31
+ MaggieDash host adapter contract before adding a new framework adapter.
10
32
 
11
33
  ## Workflow
12
34
 
@@ -11,3 +11,14 @@ marketplace repository.
11
11
  Dashboard routes should adopt `dashboard-ui-contract.json` and validate their
12
12
  actual host components with `maggie dash ui validate`. The contract provides
13
13
  the shared workspace chrome and rejects components that declare dead props.
14
+
15
+ The first-party dashboard distribution is installed separately into
16
+ `./_maggie/admin`:
17
+
18
+ ```bash
19
+ maggie dash install --project . --confirm
20
+ ```
21
+
22
+ It records the source revision at `.maggie/dash-install.json`; host routes,
23
+ email/password authentication, and `/api/maggie/*` adapters remain owned by the
24
+ website project.
@@ -8,6 +8,7 @@ import hashlib
8
8
  import json
9
9
  import os
10
10
  import re
11
+ import subprocess
11
12
  import sys
12
13
  import time
13
14
  from datetime import datetime, timezone
@@ -561,6 +562,11 @@ def command_bootstrap_phase(args: argparse.Namespace) -> int:
561
562
  if analysis.get("maggiedash", {}).get("status") not in {"Detected", "Planned"}:
562
563
  print("GATE_BLOCKED: MaggieDash project contract is unavailable.", file=sys.stderr)
563
564
  return 2
565
+ bootstrap = load_state(root) or {}
566
+ decisions = bootstrap.get("decisions", {})
567
+ if decisions.get("maggiedash_admin") and not (root / STATE_DIR / "dash-install.json").exists():
568
+ print("GATE_BLOCKED: MaggieDash admin is enabled but not installed; run `maggie dash install --project . --confirm`.", file=sys.stderr)
569
+ return 2
564
570
  phase["status"] = "passed"
565
571
  phase["validation"] = args.validation
566
572
  phase["validated_at"] = utc_now()
@@ -589,7 +595,7 @@ BOOTSTRAP_QUESTIONS = [
589
595
  ("content_source", "Where should posts be stored?", ["preserve", "local-content", "database", "api-pull"]),
590
596
  ("posts_per_page", "How many posts should appear on each blog page?", ["9", "12", "6", "custom"]),
591
597
  ("grid_columns", "How many columns should the default post grid use?", ["3", "2", "4", "custom"]),
592
- ("ops_dashboard", "Should the project add the private Maggie Ops dashboard after bootstrap?", ["disabled", "enabled"]),
598
+ ("maggiedash_admin", "Should bootstrap install the private MaggieDash admin workspace into ./_maggie/admin?", ["enabled", "disabled"]),
593
599
  ]
594
600
 
595
601
 
@@ -604,7 +610,7 @@ def question_default(key: str, analysis: dict) -> str:
604
610
  detected = analysis.get(key, {}).get("value") if isinstance(analysis.get(key), dict) else None
605
611
  if key == "posts_per_page": return "9"
606
612
  if key == "grid_columns": return "3"
607
- if key == "ops_dashboard": return "disabled"
613
+ if key == "maggiedash_admin": return "enabled"
608
614
  return detected or {"ui_system": "preserve-or-tailwind", "icon_set": "phosphor", "font": "preserve-or-system", "database": "preserve-or-sqlite"}.get(key, "detected")
609
615
 
610
616
 
@@ -641,6 +647,8 @@ def command_bootstrap_interview(args: argparse.Namespace) -> int:
641
647
  raise RuntimeError(f"invalid answers file: {args.answers_file}") from error
642
648
  if not isinstance(loaded, dict): raise RuntimeError("answers file must contain a JSON object")
643
649
  answers.update({str(key): str(value) for key, value in loaded.items()})
650
+ if "maggiedash_admin" not in answers and "ops_dashboard" in answers:
651
+ answers["maggiedash_admin"] = answers["ops_dashboard"]
644
652
  for key, question, choices in BOOTSTRAP_QUESTIONS:
645
653
  value = answers.get(key, question_default(key, analysis))
646
654
  if not args.non_interactive and key not in answers:
@@ -665,8 +673,10 @@ def command_bootstrap_interview(args: argparse.Namespace) -> int:
665
673
  project=str(root), confirm=["foundation", "experience", "data", "publishing"],
666
674
  framework=answers["framework"], language=answers["language"], content_language=answers["content_language"],
667
675
  ui_system=answers["ui_system"], icon_set=answers["icon_set"], font=answers["font"], database=answers["database"], content_source=answers["content_source"],
668
- posts_per_page=int(answers["posts_per_page"]), grid_columns=int(answers["grid_columns"]), ops_dashboard=answers["ops_dashboard"] == "enabled",
676
+ posts_per_page=int(answers["posts_per_page"]), grid_columns=int(answers["grid_columns"]),
677
+ ops_dashboard=answers["maggiedash_admin"] == "enabled", maggiedash_admin=answers["maggiedash_admin"] == "enabled",
669
678
  site_domain=answers["site_domain"], api_key_present=answers["api_key_present"], foundation_source=answers["foundation_source"], maggiedash_path=None,
679
+ dash_source=getattr(args, "dash_source", None),
670
680
  )
671
681
  return command_complete(complete_args)
672
682
 
@@ -685,6 +695,17 @@ def command_complete(args: argparse.Namespace) -> int:
685
695
  if foundation_analysis.get("maggiedash", {}).get("status") != "Detected":
686
696
  print("FOUNDATION_REQUIRED: use-existing-maggiedash was selected but no MaggieDash contract was detected.", file=sys.stderr)
687
697
  return 2
698
+ install_admin = bool(getattr(args, "maggiedash_admin", False))
699
+ if install_admin:
700
+ root.mkdir(parents=True, exist_ok=True)
701
+ command = [sys.executable, str(Path(__file__).with_name("maggie_dash.py")), "install", "--project", str(root), "--confirm"]
702
+ dash_source = getattr(args, "dash_source", None)
703
+ if dash_source:
704
+ command.extend(["--source", dash_source])
705
+ result = subprocess.run(command, cwd=root, check=False)
706
+ if result.returncode:
707
+ print("MAGGIEDASH_INSTALL_FAILED: bootstrap was not completed; resolve the private MaggieDash source and retry.", file=sys.stderr)
708
+ return result.returncode
688
709
  state = {
689
710
  "schema_version": "1.1",
690
711
  "status": "completed",
@@ -707,6 +728,7 @@ def command_complete(args: argparse.Namespace) -> int:
707
728
  "posts_per_page": args.posts_per_page,
708
729
  "grid_columns": args.grid_columns,
709
730
  "ops_dashboard": args.ops_dashboard,
731
+ "maggiedash_admin": install_admin,
710
732
  },
711
733
  }
712
734
  path = state_path(root)
@@ -714,7 +736,7 @@ def command_complete(args: argparse.Namespace) -> int:
714
736
  path.write_text(json.dumps(state, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
715
737
  contracts = {
716
738
  "project.json": {"schema_version": "1.0", "framework": args.framework, "language": args.language, "database": args.database, "content_source": args.content_source, "site_domain": getattr(args, "site_domain", "none"), "context_source": state["decisions"]["context_source"], "foundation_source": foundation_source},
717
- "maggiedash.json": {"schema_version": "1.0", "source": foundation_source, "path": getattr(args, "maggiedash_path", None), "scaffold_command": "maggie dash init --project ." if foundation_source == "create-maggiedash" else None, "capabilities": ["content", "admin", "auth", "media", "plugins", "api", "mcp"], "status": "detected" if foundation_source == "use-existing-maggiedash" else "planned" if foundation_source == "create-maggiedash" else "not-selected"},
739
+ "maggiedash.json": {"schema_version": "1.0", "source": foundation_source, "path": getattr(args, "maggiedash_path", None), "scaffold_command": "maggie dash init --project ." if foundation_source == "create-maggiedash" else None, "admin_install": {"enabled": install_admin, "target": "./_maggie/admin", "state": ".maggie/dash-install.json" if install_admin else None}, "capabilities": ["content", "admin", "auth", "media", "plugins", "api", "mcp"], "status": "detected" if foundation_source == "use-existing-maggiedash" else "planned" if foundation_source == "create-maggiedash" else "not-selected"},
718
740
  "decisions.json": state["decisions"],
719
741
  "schema.json": {"post_required": ["id", "slug", "title", "content", "status", "canonicalUrl", "publishedAt", "updatedAt", "author"], "public_status": "published", "posts_per_page": args.posts_per_page, "grid_columns": args.grid_columns, "grid_default": f"{args.grid_columns}x{args.posts_per_page // args.grid_columns}"},
720
742
  "routes.json": {"public": ["/about", "/blog", "/blog/page/[page]", "/blog/[slug]", "/topics", "/topics/[slug]", "/authors/[slug]", "/sitemap.xml", "/robots.txt"], "ops": ["/ops", "/ops/posts", "/ops/posts/new", "/ops/posts/[id]/edit", "/ops/posts/[id]/preview", "/ops/topics", "/ops/sitemap", "/ops/reports", "/ops/settings/site", "/ops/settings/integrations", "/ops/operations", "/ops/wordpress", "/api/ops/summary", "/api/ops/posts", "/api/ops/topics", "/api/ops/bulk", "/api/ops/agency", "/api/ops/entities", "/api/ops/migrations/import", "/api/ops/migrations/[id]/resume", "/api/ops/wordpress/migration-plan", "/api/ops/pull/project-context", "/api/ops/pull/sync", "/api/ops/pull/sync-updates", "/api/ops/sitemap/matching-history", "/api/ops/sitemap/match", "/api/ops/sitemap/auto-detect", "/api/ops/rewrite/queue", "/api/ops/rewrite/history", "/api/ops/rewrite/policy", "/api/ops/content-tracking/report-state", "/api/ops/content-tracking/report-state/batch", "/api/ops/reports", "/api/ops/playbooks/[id]/run"]},
@@ -903,6 +925,7 @@ def parser() -> argparse.ArgumentParser:
903
925
  interview.add_argument("--answers-file", help="JSON answers for headless/CI use")
904
926
  interview.add_argument("--non-interactive", action="store_true", help="do not prompt; requires --answers-file and --confirm")
905
927
  interview.add_argument("--confirm", action="store_true", help="apply the reviewed proposal")
928
+ interview.add_argument("--dash-source", help="local checkout or Git URL for the MaggieDash admin source")
906
929
  interview.set_defaults(func=command_bootstrap_interview)
907
930
  phase = bootstrap_sub.add_parser("phase", help="start/pass/fail one ordered workflow phase")
908
931
  phase.add_argument("action", choices=["status", "start", "pass", "fail"])
@@ -915,11 +938,13 @@ def parser() -> argparse.ArgumentParser:
915
938
  complete.add_argument("--api-key-present", choices=["yes", "no"], default="no")
916
939
  complete.add_argument("--foundation-source", choices=["use-existing-maggiedash", "create-maggiedash", "preserve-existing-project"], default="preserve-existing-project")
917
940
  complete.add_argument("--maggiedash-path", default=None, help="path to an existing MaggieDash project contract")
941
+ complete.add_argument("--dash-source", help="local checkout or Git URL for the MaggieDash admin source")
918
942
  for name, default in (("framework", "detected"), ("language", "detected"), ("content-language", "confirmed"), ("ui-system", "preserve-or-tailwind"), ("icon-set", "phosphor"), ("font", "preserve-or-system"), ("database", "preserve-or-sqlite"), ("content-source", "confirmed")):
919
943
  complete.add_argument("--" + name, dest=name.replace("-", "_"), default=default)
920
944
  complete.add_argument("--posts-per-page", type=int, default=9)
921
945
  complete.add_argument("--grid-columns", type=int, default=3)
922
946
  complete.add_argument("--ops-dashboard", action=argparse.BooleanOptionalAction, default=False)
947
+ complete.add_argument("--maggiedash-admin", action=argparse.BooleanOptionalAction, default=False)
923
948
  complete.set_defaults(func=command_complete)
924
949
  generate = sub.add_parser("generate", help="generate stable contract or fixture files")
925
950
  generate.add_argument("target", choices=["contract", "fixture", "post", "topic", "author", "migration", "seo", "ops-page"])
@@ -11,7 +11,12 @@ from __future__ import annotations
11
11
 
12
12
  import argparse
13
13
  import json
14
+ import os
15
+ import shutil
16
+ import subprocess
14
17
  import sys
18
+ import tempfile
19
+ from datetime import datetime, timezone
15
20
  from pathlib import Path
16
21
 
17
22
  sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "runtime"))
@@ -38,6 +43,118 @@ def require_confirm(args: argparse.Namespace) -> None:
38
43
  raise SystemExit(2)
39
44
 
40
45
 
46
+ DEFAULT_DASH_SOURCE = "https://github.com/TOPY-AI-LTD/MaggieDash.git"
47
+
48
+
49
+ def utc_now() -> str:
50
+ return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
51
+
52
+
53
+ def git_revision(path: Path) -> str | None:
54
+ try:
55
+ result = subprocess.run(["git", "-C", str(path), "rev-parse", "HEAD"], capture_output=True, text=True, check=True)
56
+ except (OSError, subprocess.CalledProcessError):
57
+ return None
58
+ return result.stdout.strip() or None
59
+
60
+
61
+ def copy_dashboard_tree(source: Path, target: Path, force: bool) -> tuple[list[str], list[str]]:
62
+ installed: list[str] = []
63
+ preserved: list[str] = []
64
+ target.mkdir(parents=True, exist_ok=True)
65
+ for path in sorted(source.rglob("*")):
66
+ relative = path.relative_to(source)
67
+ destination = target / relative
68
+ if path.is_dir():
69
+ destination.mkdir(parents=True, exist_ok=True)
70
+ continue
71
+ if destination.exists() and not force:
72
+ preserved.append(str(destination))
73
+ continue
74
+ destination.parent.mkdir(parents=True, exist_ok=True)
75
+ shutil.copy2(path, destination)
76
+ installed.append(str(destination))
77
+ return installed, preserved
78
+
79
+
80
+ def command_install(args: argparse.Namespace) -> int:
81
+ require_confirm(args)
82
+ root = project_root(args)
83
+ source_value = args.source or os.environ.get("MAGGIE_DASH_SOURCE") or DEFAULT_DASH_SOURCE
84
+ ref = args.ref
85
+ target = root / args.target
86
+ temporary: tempfile.TemporaryDirectory[str] | None = None
87
+ source = Path(source_value).expanduser().resolve() if not source_value.startswith(("https://", "http://", "git@")) else None
88
+ try:
89
+ if source is None:
90
+ temporary = tempfile.TemporaryDirectory(prefix="maggiedash-install-")
91
+ clone = ["git", "clone", "--depth", "1"]
92
+ if ref:
93
+ clone.extend(["--branch", ref])
94
+ clone.extend([source_value, temporary.name])
95
+ result = subprocess.run(clone, capture_output=True, text=True)
96
+ if result.returncode:
97
+ detail = (result.stderr or result.stdout).strip().splitlines()[-1:]
98
+ raise RuntimeError("MaggieDash source could not be cloned" + (f": {detail[0]}" if detail else ""))
99
+ source = Path(temporary.name)
100
+ if not source.is_dir():
101
+ raise RuntimeError(f"MaggieDash source directory not found: {source}")
102
+ manifest_path = source / "maggiedash.json"
103
+ if not manifest_path.exists():
104
+ raise RuntimeError("MaggieDash source has no maggiedash.json manifest")
105
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
106
+ if manifest.get("schemaVersion") != "maggiedash-distribution.v1":
107
+ raise RuntimeError("unsupported MaggieDash distribution schema")
108
+ if manifest.get("target") != "_maggie/admin" and args.target == "_maggie/admin":
109
+ raise RuntimeError("MaggieDash manifest target is not ./_maggie/admin")
110
+ installed: list[str] = []
111
+ preserved: list[str] = []
112
+ for item in manifest.get("files", []):
113
+ relative = Path(item)
114
+ if relative.is_absolute() or ".." in relative.parts:
115
+ raise RuntimeError(f"unsafe MaggieDash manifest path: {item}")
116
+ source_item = source / relative
117
+ if not source_item.exists():
118
+ raise RuntimeError(f"MaggieDash manifest file is missing: {item}")
119
+ if relative.parts and relative.parts[0] == "admin":
120
+ destination = target / Path(*relative.parts[1:])
121
+ else:
122
+ destination = target / relative
123
+ if source_item.is_dir():
124
+ current_installed, current_preserved = copy_dashboard_tree(source_item, destination, args.force)
125
+ else:
126
+ current_installed = []
127
+ current_preserved = []
128
+ if destination.exists() and not args.force:
129
+ current_preserved = [str(destination)]
130
+ else:
131
+ destination.parent.mkdir(parents=True, exist_ok=True)
132
+ shutil.copy2(source_item, destination)
133
+ current_installed = [str(destination)]
134
+ installed.extend(current_installed)
135
+ preserved.extend(current_preserved)
136
+ state = {
137
+ "schemaVersion": "maggiedash-install.v1",
138
+ "status": "installed",
139
+ "source": source_value,
140
+ "ref": ref,
141
+ "revision": git_revision(source),
142
+ "version": manifest.get("version"),
143
+ "target": str(target),
144
+ "installed": sorted(set(installed)),
145
+ "preserved": sorted(set(preserved)),
146
+ "updatedAt": utc_now(),
147
+ }
148
+ state_path = root / ".maggie" / "dash-install.json"
149
+ state_path.parent.mkdir(parents=True, exist_ok=True)
150
+ state_path.write_text(json.dumps(state, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
151
+ emit({"status": "installed", "version": manifest.get("version"), "target": str(target), "installed": len(set(installed)), "preserved": len(set(preserved)), "state": str(state_path)})
152
+ return 0
153
+ finally:
154
+ if temporary is not None:
155
+ temporary.cleanup()
156
+
157
+
41
158
  def command_init(args: argparse.Namespace) -> int:
42
159
  require_confirm(args)
43
160
  root = project_root(args)
@@ -171,6 +288,14 @@ def parser() -> argparse.ArgumentParser:
171
288
  init.add_argument("--locale", default="en-GB")
172
289
  init.add_argument("--confirm", action="store_true")
173
290
  init.set_defaults(func=command_init)
291
+ install = sub.add_parser("install", help="install the MaggieDash admin workspace")
292
+ install.add_argument("--project", default=".")
293
+ install.add_argument("--source", help="local checkout or Git URL; defaults to MaggieDash repository")
294
+ install.add_argument("--ref", default="main", help="Git ref when installing from a remote source")
295
+ install.add_argument("--target", default="_maggie/admin", help="installation target relative to the project")
296
+ install.add_argument("--force", action="store_true", help="replace existing dashboard files")
297
+ install.add_argument("--confirm", action="store_true")
298
+ install.set_defaults(func=command_install)
174
299
  status = sub.add_parser("status", help="inspect project and documents")
175
300
  status.add_argument("--project", default=".")
176
301
  status.add_argument("--project-id", default="local-project")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topy-ai/maggie",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Install and manage Maggie Skills for AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "module",