@topy-ai/maggie 0.1.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.
Files changed (36) hide show
  1. package/bin/maggie.js +152 -0
  2. package/bundled-references/ai-native-blog-contract.md +310 -0
  3. package/bundled-references/blog-data-contract.md +146 -0
  4. package/bundled-references/blog-implementation.md +46 -0
  5. package/bundled-references/blog-operations-contract.md +68 -0
  6. package/bundled-references/browser-inspection.md +39 -0
  7. package/bundled-references/provider-adapter-contract.md +68 -0
  8. package/bundled-references/seo-technical-contract.md +75 -0
  9. package/bundled-skills/README.md +16 -0
  10. package/bundled-skills/maggie-blog-bootstrap/SKILL.md +243 -0
  11. package/bundled-skills/maggie-clone/SKILL.md +213 -0
  12. package/bundled-skills/maggie-deployment/SKILL.md +61 -0
  13. package/bundled-skills/maggie-deployment/agents/openai.yaml +4 -0
  14. package/bundled-skills/maggie-deployment/references/cloudflare.md +76 -0
  15. package/bundled-skills/maggie-deployment/references/provider-contract.md +32 -0
  16. package/bundled-skills/maggie-project-context/SKILL.md +38 -0
  17. package/bundled-skills/maggie-seo-geo/SKILL.md +53 -0
  18. package/bundled-skills/maggie-social-share/SKILL.md +48 -0
  19. package/bundled-tools/clis/maggie.py +748 -0
  20. package/bundled-tools/clis/maggie_clone.py +82 -0
  21. package/bundled-tools/clis/site_audit.py +99 -0
  22. package/bundled-tools/integrations/analytics.md +34 -0
  23. package/bundled-tools/integrations/maggie-api-pull.md +72 -0
  24. package/bundled-tools/integrations/maggie-project-context.md +62 -0
  25. package/bundled-tools/integrations/maggie-seo-audit.md +16 -0
  26. package/bundled-tools/integrations/maggie-skills-api.md +76 -0
  27. package/bundled-tools/integrations/maggie-social-share.md +23 -0
  28. package/bundled-tools/integrations/maggie-visibility.md +22 -0
  29. package/package.json +29 -0
  30. package/references/ai-native-blog-contract.md +310 -0
  31. package/references/blog-data-contract.md +146 -0
  32. package/references/blog-implementation.md +46 -0
  33. package/references/blog-operations-contract.md +68 -0
  34. package/references/browser-inspection.md +39 -0
  35. package/references/provider-adapter-contract.md +68 -0
  36. package/references/seo-technical-contract.md +75 -0
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env python3
2
+ """Plan collision-safe Maggie clone namespaces without touching a project."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import hashlib
8
+ import json
9
+ import re
10
+ from pathlib import Path
11
+ from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
12
+
13
+
14
+ def normalized(url: str) -> str:
15
+ value = urlsplit(url.strip())
16
+ if value.scheme not in {"http", "https"} or not value.netloc:
17
+ raise ValueError(f"invalid URL: {url}")
18
+ host = value.hostname.lower() if value.hostname else ""
19
+ port = value.port
20
+ default = (value.scheme == "http" and port == 80) or (value.scheme == "https" and port == 443)
21
+ authority = host if not port or default else f"{host}:{port}"
22
+ path = value.path or "/"
23
+ if path != "/":
24
+ path = "/" + "/".join(part for part in path.split("/") if part)
25
+ query = urlencode(sorted(parse_qsl(value.query, keep_blank_values=True)))
26
+ return urlunsplit((value.scheme, authority, path, query, value.fragment))
27
+
28
+
29
+ def key_part(value: str, fallback: str) -> str:
30
+ cleaned = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
31
+ return cleaned[:48] or fallback
32
+
33
+
34
+ def plan(urls: list[str], project: Path) -> list[dict[str, str]]:
35
+ result = []
36
+ seen: set[tuple[str, str]] = set()
37
+ for raw in urls:
38
+ url = normalized(raw)
39
+ parts = urlsplit(url)
40
+ origin = f"{parts.scheme}://{parts.netloc}"
41
+ path_state = parts.path + (f"?{parts.query}" if parts.query else "") + (f"#{parts.fragment}" if parts.fragment else "")
42
+ site_hash = hashlib.sha256(origin.encode()).hexdigest()[:8]
43
+ page_hash = hashlib.sha256(path_state.encode()).hexdigest()[:8]
44
+ identity = (origin, path_state)
45
+ if identity in seen:
46
+ raise ValueError(f"duplicate target after normalization: {raw}")
47
+ seen.add(identity)
48
+ site_key = f"{key_part(parts.netloc, 'site')}-{site_hash}"
49
+ segments = [part for part in parts.path.split("/") if part]
50
+ page_slug = "-".join(key_part(part, "page") for part in segments) if segments else "root"
51
+ page_key = f"{page_slug[:48]}-{page_hash}"
52
+ route = parts.path or "/"
53
+ result.append({
54
+ "source_url": url,
55
+ "origin": origin,
56
+ "destination_route": route,
57
+ "site_key": site_key,
58
+ "page_key": page_key,
59
+ "artifact_root": str(project / "docs/research" / site_key / page_key),
60
+ "screenshot_root": str(project / "docs/design-references" / site_key / page_key),
61
+ "component_root": str(project / "src/components/sites" / site_key / page_key),
62
+ "asset_root": str(project / "public/sites" / site_key / page_key),
63
+ })
64
+ return result
65
+
66
+
67
+ def main() -> int:
68
+ parser = argparse.ArgumentParser(description=__doc__)
69
+ sub = parser.add_subparsers(dest="command", required=True)
70
+ command = sub.add_parser("plan", help="emit a read-only clone output plan")
71
+ command.add_argument("urls", nargs="+", help="authorized target URLs")
72
+ command.add_argument("--project", type=Path, default=Path.cwd())
73
+ args = parser.parse_args()
74
+ try:
75
+ print(json.dumps({"project_root": str(args.project.resolve()), "targets": plan(args.urls, args.project.resolve())}, indent=2))
76
+ except ValueError as error:
77
+ parser.error(str(error))
78
+ return 0
79
+
80
+
81
+ if __name__ == "__main__":
82
+ raise SystemExit(main())
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env python3
2
+ """Small dependency-free SEO/GEO smoke audit for a public site."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import re
9
+ import sys
10
+ from html.parser import HTMLParser
11
+ from urllib.parse import urljoin, urlparse
12
+ from urllib.request import Request, urlopen
13
+
14
+
15
+ class PageParser(HTMLParser):
16
+ def __init__(self) -> None:
17
+ super().__init__()
18
+ self.title = ""
19
+ self.h1 = 0
20
+ self.canonical = ""
21
+ self.jsonld = 0
22
+ self.anchors = 0
23
+ self.in_title = False
24
+
25
+ def handle_starttag(self, tag, attrs):
26
+ data = dict(attrs)
27
+ if tag == "title":
28
+ self.in_title = True
29
+ elif tag == "h1":
30
+ self.h1 += 1
31
+ elif tag == "link" and data.get("rel", "").lower() == "canonical":
32
+ self.canonical = data.get("href", "")
33
+ elif tag == "script" and data.get("type") == "application/ld+json":
34
+ self.jsonld += 1
35
+ elif tag == "a" and data.get("href"):
36
+ self.anchors += 1
37
+
38
+ def handle_endtag(self, tag):
39
+ if tag == "title":
40
+ self.in_title = False
41
+
42
+ def handle_data(self, data):
43
+ if self.in_title:
44
+ self.title += data.strip()
45
+
46
+
47
+ def fetch(url: str) -> tuple[int, str, str]:
48
+ request = Request(url, headers={"User-Agent": "AI-CMO-Skills-Audit/0.1"})
49
+ with urlopen(request, timeout=15) as response:
50
+ body = response.read(2_000_000).decode("utf-8", "replace")
51
+ return response.status, response.headers.get_content_type(), body
52
+
53
+
54
+ def main() -> int:
55
+ parser = argparse.ArgumentParser()
56
+ parser.add_argument("url")
57
+ parser.add_argument("--json", action="store_true", dest="as_json")
58
+ args = parser.parse_args()
59
+ base = args.url.rstrip("/")
60
+ checks = {}
61
+ try:
62
+ status, content_type, html = fetch(base)
63
+ page = PageParser()
64
+ page.feed(html)
65
+ checks["homepage"] = {"status": status, "content_type": content_type}
66
+ checks["title"] = {"ok": bool(page.title), "value": page.title}
67
+ checks["h1"] = {"ok": page.h1 == 1, "count": page.h1}
68
+ checks["canonical"] = {"ok": bool(page.canonical), "value": page.canonical}
69
+ checks["article_jsonld"] = {"ok": page.jsonld > 0, "count": page.jsonld}
70
+ checks["crawlable_links"] = {"ok": page.anchors > 0, "count": page.anchors}
71
+ except Exception as exc:
72
+ checks["homepage"] = {"ok": False, "error": type(exc).__name__}
73
+
74
+ for name, path in (("robots", "/robots.txt"), ("sitemap", "/sitemap.xml")):
75
+ try:
76
+ status, content_type, body = fetch(urljoin(base + "/", path.lstrip("/")))
77
+ checks[name] = {
78
+ "ok": status == 200,
79
+ "status": status,
80
+ "content_type": content_type,
81
+ "mentions_sitemap": "sitemap" in body.lower() if name == "robots" else None,
82
+ "url_count": len(re.findall(r"<loc>.*?</loc>", body, re.I | re.S)) if name == "sitemap" else None,
83
+ }
84
+ except Exception as exc:
85
+ checks[name] = {"ok": False, "error": type(exc).__name__}
86
+
87
+ result = {"url": base, "checks": checks, "passed": all(v.get("ok", False) for v in checks.values())}
88
+ if args.as_json:
89
+ print(json.dumps(result, indent=2))
90
+ else:
91
+ for name, value in checks.items():
92
+ print(f"{'PASS' if value.get('ok') else 'FAIL'} {name}: {value}")
93
+ print("RESULT:", "PASS" if result["passed"] else "FAIL")
94
+ return 0 if result["passed"] else 1
95
+
96
+
97
+ if __name__ == "__main__":
98
+ sys.exit(main())
99
+
@@ -0,0 +1,34 @@
1
+ # GA4 and Google Search Console
2
+
3
+ ## GA4
4
+
5
+ Use the host framework's supported Google tag integration. The minimum
6
+ configuration is:
7
+
8
+ ```text
9
+ NEXT_PUBLIC_GA4_MEASUREMENT_ID=G-...
10
+ GA4_ENABLED=true
11
+ ```
12
+
13
+ For another framework, use equivalent names in `.env.example`. The integration
14
+ must be disabled when the measurement ID is absent or consent is not granted.
15
+ Do not send post body, API keys, or unnecessary personal data as event params.
16
+
17
+ Recommended events: `page_view` (automatic), `blog_post_view`, and an explicit
18
+ conversion event only when the host project defines one.
19
+
20
+ ## GSC
21
+
22
+ Prefer DNS verification for production. If HTML verification is needed, use a
23
+ public token environment variable and render the exact verification tag. OAuth
24
+ refresh tokens and service-account JSON stay server-side and out of the repo.
25
+
26
+ ```text
27
+ GSC_SITE_URL=https://example.com
28
+ GSC_VERIFICATION_TOKEN=replace-me
29
+ ```
30
+
31
+ GSC data is read-only in this starter. Any query, page, or indexing report must
32
+ show its date window and property so an agent does not confuse an empty result
33
+ with a failed connection.
34
+
@@ -0,0 +1,72 @@
1
+ # Maggie API-Pull Integration
2
+
3
+ The public edge is `https://api.cmo.so`. The API key is sent as:
4
+
5
+ ```http
6
+ Authorization: Bearer aicmo_...
7
+ ```
8
+
9
+ Never expose this key in browser JavaScript, client bundles, logs, screenshots,
10
+ or public repositories. Keep polling server-side or in a protected job.
11
+
12
+ ## Lifecycle
13
+
14
+ ```text
15
+ GET /whoami
16
+ -> POST /sitemap/match or /sitemap/auto-detect
17
+ -> GET /sitemap/matching-history
18
+ -> POST /rewrite/queue (optional explicit rewrite)
19
+ -> GET /rewrite/queue
20
+ -> GET /posts and GET /updates on a schedule
21
+ -> upsert local content and publish according to local approval policy
22
+ -> POST /content-tracking/report-state
23
+ -> GET /rewrite/history/{content_id}
24
+ ```
25
+
26
+ ## Project context synchronization
27
+
28
+ `GET /project-context` returns the sanitized, read-only context for the API
29
+ key's AI CMO project. The key is the scope; clients must not accept a project
30
+ id from a browser or public request.
31
+
32
+ The response includes brand, product/service positioning, audience, region,
33
+ brand voice, site URL, and the structured project CTA. It deliberately omits
34
+ team data, uploads, testimonials, market research, and internal workflow state.
35
+ Use the response `ETag` with `If-None-Match` for scheduled syncs, and write the
36
+ result to a local generated file rather than modifying source-of-truth files by
37
+ hand. Map `project.cta.default_url` and `project.cta.other_urls` into
38
+ `conversion.ctas.default` and `conversion.ctas.additional` while preserving
39
+ the original payload for forwards compatibility.
40
+
41
+ ## Endpoints
42
+
43
+ | Endpoint | Use | Key |
44
+ |---|---|---|
45
+ | `GET /whoami` | Resolve domain and capabilities | secret or publishable |
46
+ | `GET /project-context` | Sync sanitized project, brand voice and CTA context | secret |
47
+ | `GET /posts` | Pull one new post | secret |
48
+ | `GET /updates` | Pull one rewrite | secret |
49
+ | `GET /history` | Recover delivered posts | secret |
50
+ | `GET /rewrite-policy` | Read rewrite policy | secret |
51
+ | `POST /rewrite-policy` | Set auto/manual policy | secret |
52
+ | `POST /sitemap/match` | Match supplied sitemap | secret |
53
+ | `POST /sitemap/auto-detect` | Discover and match sitemap | secret |
54
+ | `GET /sitemap/matching-history` | Inspect prior runs | publishable |
55
+ | `POST /rewrite/queue` | Generate one rewrite for a matched asset | secret |
56
+ | `GET /rewrite/queue` | Inspect cached picks and pending delivery | publishable |
57
+ | `GET /rewrite/history/{content_id}` | Inspect rewrite delivery attempts | publishable |
58
+ | `POST /content-tracking/report-state` | Report local publication state | secret |
59
+ | `POST /content-tracking/report-state/batch` | Report bounded inventory batch | secret |
60
+
61
+ ## Adapter requirements
62
+
63
+ - Store remote `content_id`, `version`, `slug`, and `canonical_url` locally.
64
+ - Upsert new posts idempotently. A retry must not create a duplicate slug.
65
+ - On rewrite, update the existing record at the same canonical URL. Do not
66
+ replace the slug unless the API contract explicitly changes.
67
+ - Treat an empty `/posts` or `/updates` response as normal, not an error.
68
+ - Back off on 429/5xx and include an idempotency key where supported.
69
+ - Do not ack a local update until the new body is stored and the local publish
70
+ result is known.
71
+ - Use `report-state` for observed URL/status/hash so the tracking record can be
72
+ reconciled later.
@@ -0,0 +1,62 @@
1
+ # Maggie Project Context Adapter
2
+
3
+ This adapter connects a host project to the AI CMO source of truth without
4
+ requiring the host project to know the AI CMO database schema.
5
+
6
+ ## Request
7
+
8
+ ```http
9
+ GET https://api.cmo.so/project-context
10
+ Authorization: Bearer aicmo_...
11
+ If-None-Match: "previous-etag"
12
+ ```
13
+
14
+ The API key scopes the request to one domain, AI CMO workspace and Project. The
15
+ request must run server-side. A `304` means the generated local context remains
16
+ current.
17
+
18
+ ## Safe response usage
19
+
20
+ Use these response fields to generate or refresh local files:
21
+
22
+ ```text
23
+ project.brand/title/description
24
+ project.products_services/usp
25
+ project.target_problem/target_audience/target_region
26
+ project.keywords/industries
27
+ project.cta
28
+ brand_voice
29
+ site.domain/base_url
30
+ sync.updated_at
31
+ ```
32
+
33
+ Keep `context` intact for forward compatibility, but do not expose it directly
34
+ to browsers unless the host project has explicitly reviewed every field.
35
+
36
+ ## CTA normalization
37
+
38
+ The source CTA can contain `default_url` and `other_urls`, where each item may
39
+ contain `cta_url`, `url`, `href`, text, target keyword, priority, and additional
40
+ metadata. Preserve all metadata and normalize only the local view:
41
+
42
+ ```json
43
+ {
44
+ "conversion": {
45
+ "ctas": {
46
+ "default": { "cta_url": "https://example.com/start" },
47
+ "additional": []
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ Never invent a fallback CTA when the Project has no approved CTA. Ask for an
54
+ explicit destination or keep the content in preview mode.
55
+
56
+ ## Refresh policy
57
+
58
+ - manual sync before bootstrap or a content strategy change;
59
+ - scheduled sync with ETag revalidation after that;
60
+ - show changed fields before updating public copy or links;
61
+ - record `project_id`, `ai_cmo_id`, `updated_at`, ETag, timestamp and result;
62
+ - never commit `generated/ai-cmo-context.json` if it contains private context.
@@ -0,0 +1,16 @@
1
+ # Maggie SEO Audit Integration
2
+
3
+ Use the deterministic local audit first, then the paid/server-side audit when
4
+ the user has access to the full AI CMO feature.
5
+
6
+ ## Audit layers
7
+
8
+ 1. Local: HTTP status, robots, sitemap, canonical, metadata, headings, links,
9
+ JSON-LD, image alt text and server-rendered post paths.
10
+ 2. Provider: performance, accessibility, best practices and SEO diagnostics.
11
+ 3. AI readiness: extractability, author/freshness/evidence signals, crawler
12
+ access and public machine-readable context.
13
+
14
+ Provider runs should be asynchronous, cached, bounded by entitlement, and
15
+ reported with `job_id`, `audit_id`, `data_through`, and issue severity. A
16
+ provider outage must not be presented as a site failure.
@@ -0,0 +1,76 @@
1
+ # Maggie Skills API (versioned)
2
+
3
+ This is the additive API for the Skills workflow. It does not replace the
4
+ legacy API Pull contract in [maggie-api-pull.md](./maggie-api-pull.md).
5
+
6
+ The legacy guide remains authoritative for the existing root endpoints. New
7
+ Skills integrations should use this namespace for lifecycle operations; do not
8
+ rewrite existing `/posts`, `/updates`, or `/rewrite/queue` adapters.
9
+
10
+ Base URL:
11
+
12
+ ```text
13
+ https://api.cmo.so/api/v1/maggie
14
+ ```
15
+
16
+ Authentication is server-side only:
17
+
18
+ ```http
19
+ Authorization: Bearer aicmo_...
20
+ ```
21
+
22
+ The edge is only an API-key proxy. Data and mutations are handled by TOPY and
23
+ the shared MySQL database. Never put the key in browser code or a public build.
24
+
25
+ ## Contract discovery
26
+
27
+ Use `GET /api-version` to identify the additive contract and
28
+ `GET /capabilities` to feature-detect optional capabilities. The current
29
+ published OpenAPI contract is the source of truth for method details. Read-only
30
+ operations do not consume AI quota. Operations that may consume quota return
31
+ the `X-Quota-*` headers; callers must preserve and report them.
32
+
33
+ ## Endpoint map
34
+
35
+ | Area | Endpoints |
36
+ |---|---|
37
+ | Version and usage | `GET /api-version`, `GET /capabilities`, `GET /entitlements`, `GET /usage`, `GET /usage/events`, `POST /usage/events` |
38
+ | Pull runs | `GET/POST /pull/runs`, `GET /pull/runs/{record_id}`, `GET /pull/runs/{record_id}/items` |
39
+ | Content assets | `GET /content-assets`, `GET/POST /content-assets/backfill`, `GET /content-assets/{record_id}`, `GET/PATCH /content-assets/{record_id}/identity`, `GET /content-assets/{record_id}/versions` |
40
+ | Sitemap sources | `GET/POST /sitemap/sources`, `GET/PATCH/DELETE /sitemap/sources/{record_id}`, plus the same `GET/POST` and `GET/PATCH/DELETE` plural `/sitemaps/sources` routes |
41
+ | Sitemap runs | `GET/POST /sitemap/runs`, `GET /sitemap/runs/{record_id}`, `GET /sitemap/runs/{record_id}/cancel`, plus the same `GET/POST`, `GET`, and `GET` cancel plural `/sitemaps/runs` routes |
42
+ | Sitemap schedules | `GET/POST /sitemap/schedules`, `GET/POST /sitemaps/schedules` (the current contract has no item route for schedules) |
43
+ | Rewrite jobs | `GET/POST /rewrite/jobs`, `GET/POST /rewrite/jobs/batch`, `GET /rewrite/jobs/{record_id}`, `GET /rewrite/jobs/{record_id}/preview|approve|reject|cancel|retry`, `GET /rewrite/jobs/{record_id}/versions` |
44
+ | SEO | `GET/POST /seo/audits`, `GET /seo/audits/{record_id}`, `GET/POST /seo/insights` |
45
+ | Visibility | `GET/POST /visibility/runs`, `GET /visibility/runs/{record_id}`, `GET/POST /visibility/prompts`, `GET/POST /visibility/competitors` |
46
+ | Projects and sites | `GET /projects`, `GET /projects/{record_id}`, `GET /projects/{record_id}/context|ctas`, `GET /sites`, `GET /sites/{record_id}`, `GET /sites/{record_id}/context|settings`, `PATCH /sites/{record_id}/settings` |
47
+ | Reports | `GET/POST /reports`, `GET /reports/{record_id}`, `GET/POST /reports/{record_id}/deliver`, `GET /reports/{record_id}/deliveries` |
48
+ | Jobs | `GET/POST /scheduled-jobs`, `GET/POST /jobs`, `GET /jobs/{record_id}`, `GET/POST /jobs/{record_id}/run` |
49
+ | Social | `GET/POST /social/drafts`, `GET /social/drafts/{record_id}`, `GET /social/drafts/{record_id}/approve|reject|schedule|publish`, `GET/POST /social/accounts`, `GET/POST /social/calendars`, `GET/POST /social/schedules`, `GET/POST /social/metrics`, `GET/POST /social/publish`, `GET /social/publish/{record_id}` |
50
+ | Deployments | `GET /deployment/providers`, `GET/POST /deployment/validate`, `GET/POST /deployment/registrations`, `GET /deployment/registrations/{record_id}`, `GET /deployment/registrations/{record_id}/report`; plural routes: `GET /deployments/providers`, `GET/POST /deployments/validate`, `GET/POST /deployments`, `GET /deployments/{record_id}/report` |
51
+
52
+ `{record_id}` is the resource identifier returned by the API. Treat status and
53
+ response fields as forward-compatible; unknown fields must be preserved.
54
+
55
+ ## Workflow
56
+
57
+ ```text
58
+ api-version/capabilities
59
+ -> pull run or content-assets backfill
60
+ -> sitemap source/run
61
+ -> rewrite job preview
62
+ -> approve/reject/cancel
63
+ -> local publish and report-state (legacy endpoint)
64
+ -> usage/history/readback
65
+ ```
66
+
67
+ Use `Idempotency-Key` for every mutation that can be retried. Do not treat a
68
+ queued response as delivered or published. Keep the legacy `/posts`,
69
+ `/updates`, `/project-context`, `/sitemap/match`, `/rewrite/queue`, and
70
+ `/content-tracking/report-state` calls unchanged when using the established
71
+ API Pull adapter.
72
+
73
+ For legacy adapter discovery, `GET https://api.cmo.so/whoami` remains available
74
+ with the original response contract. It is intentionally separate from the
75
+ versioned Skills namespace; new clients should discover the versioned contract
76
+ with `/api-version` and `/capabilities`.
@@ -0,0 +1,23 @@
1
+ # Maggie Social Share Integration
2
+
3
+ Social Share consumes approved AI CMO content assets and produces channel
4
+ derivatives. It must retain `source_content_id`, canonical URL, Project CTA,
5
+ voice/tone, approval state, provider post ID, and publication attempts.
6
+
7
+ ## Provider contract
8
+
9
+ Each channel adapter declares OAuth scopes, supported formats, max lengths,
10
+ media requirements, publish/delete/republish support, retry classification and
11
+ idempotency behavior. Start with LinkedIn and Facebook; do not claim support for
12
+ a provider until its adapter and production credentials are verified.
13
+
14
+ ## State machine
15
+
16
+ ```text
17
+ draft -> approved -> scheduled -> publishing -> published
18
+ \-> cancelled
19
+ publishing -> failed -> retrying -> published | failed
20
+ ```
21
+
22
+ Publishing requires a paid entitlement, active connection and explicit
23
+ approval. A generated social draft is never proof that a provider post exists.
@@ -0,0 +1,22 @@
1
+ # Maggie Visibility Integration
2
+
3
+ This is the contract for AI SEO/GEO visibility work. The exact public route
4
+ availability is versioned in AI CMO's OpenAPI document; do not hard-code private
5
+ Geo service credentials in a host project.
6
+
7
+ ## Required behavior
8
+
9
+ - scope every request to the authenticated domain/project;
10
+ - distinguish quick prompt checks from recurring monitoring;
11
+ - enforce server-side feature quota and subscription entitlement;
12
+ - record query set, provider, run time, result status, citations and source URLs;
13
+ - report observed retrieved/cited/mentioned/recommended states separately;
14
+ - treat provider failures as retryable operational errors, not zero visibility;
15
+ - never claim ranking, citation or traffic improvement without measured data.
16
+
17
+ ## Host output
18
+
19
+ The skill should produce a preview/report containing query coverage, competitor
20
+ presence, citation/source gaps, technical blockers, and the next content action.
21
+ It should not directly rewrite or publish content until the content workflow's
22
+ approval state allows it.
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@topy-ai/maggie",
3
+ "version": "0.1.0",
4
+ "description": "Install and manage Maggie Skills for AI coding agents",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "maggie": "bin/maggie.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "references",
13
+ "bundled-skills",
14
+ "bundled-references",
15
+ "bundled-tools"
16
+ ],
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
20
+ "scripts": {
21
+ "prepack": "node ../../scripts/package-cli.mjs"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/TOPY-AI-LTD/ai-cmo-skills.git",
26
+ "directory": "packages/maggie-cli"
27
+ },
28
+ "homepage": "https://github.com/TOPY-AI-LTD/ai-cmo-skills"
29
+ }