@adgine/geo-cli 1.2.8
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 +93 -0
- package/bin/adgine-geo.js +224 -0
- package/package.json +38 -0
- package/skills/.env.example +11 -0
- package/skills/README.md +247 -0
- package/skills/SKILL.md +117 -0
- package/skills/VERSION +1 -0
- package/skills/adgine-geo-aiagent/SKILL.md +200 -0
- package/skills/adgine-geo-aiagent/scripts/_client.py +341 -0
- package/skills/adgine-geo-aiagent/scripts/bot_traffic.py +278 -0
- package/skills/adgine-geo-aiagent/scripts/human_traffic.py +357 -0
- package/skills/adgine-geo-aiagent/scripts/page_analytics.py +317 -0
- package/skills/adgine-geo-aiagent/scripts/page_detail.py +273 -0
- package/skills/adgine-geo-analytics/API.md +89 -0
- package/skills/adgine-geo-analytics/SKILL.md +191 -0
- package/skills/adgine-geo-analytics/scripts/_client.py +341 -0
- package/skills/adgine-geo-analytics/scripts/get_overview.py +129 -0
- package/skills/adgine-geo-billing/SKILL.md +118 -0
- package/skills/adgine-geo-billing/scripts/_client.py +341 -0
- package/skills/adgine-geo-billing/scripts/get_credits.py +51 -0
- package/skills/adgine-geo-billing/scripts/get_credits_pricing.py +52 -0
- package/skills/adgine-geo-billing/scripts/get_subscription.py +48 -0
- package/skills/adgine-geo-billing/scripts/list_plans.py +66 -0
- package/skills/adgine-geo-brand/SKILL.md +193 -0
- package/skills/adgine-geo-brand/WORKFLOW.md +66 -0
- package/skills/adgine-geo-brand/scripts/_client.py +341 -0
- package/skills/adgine-geo-brand/scripts/generate_brand.py +95 -0
- package/skills/adgine-geo-brand/scripts/get_brand.py +67 -0
- package/skills/adgine-geo-brand/scripts/list_jobs.py +153 -0
- package/skills/adgine-geo-brand/scripts/update_brand.py +61 -0
- package/skills/adgine-geo-citation/SKILL.md +220 -0
- package/skills/adgine-geo-citation/scripts/_client.py +341 -0
- package/skills/adgine-geo-citation/scripts/create_tests.py +62 -0
- package/skills/adgine-geo-citation/scripts/get_aggregate.py +108 -0
- package/skills/adgine-geo-citation/scripts/get_results.py +179 -0
- package/skills/adgine-geo-content/SKILL.md +224 -0
- package/skills/adgine-geo-content/WORKFLOW.md +100 -0
- package/skills/adgine-geo-content/scripts/_client.py +341 -0
- package/skills/adgine-geo-content/scripts/generate_article.py +108 -0
- package/skills/adgine-geo-content/scripts/generate_outline.py +113 -0
- package/skills/adgine-geo-content/scripts/generate_titles.py +51 -0
- package/skills/adgine-geo-content/scripts/list_content.py +65 -0
- package/skills/adgine-geo-content/scripts/manage_content.py +90 -0
- package/skills/adgine-geo-content/scripts/manage_jobs.py +184 -0
- package/skills/adgine-geo-dashboard/SKILL.md +181 -0
- package/skills/adgine-geo-dashboard/scripts/_client.py +341 -0
- package/skills/adgine-geo-dashboard/scripts/check_integrations.py +139 -0
- package/skills/adgine-geo-dashboard/scripts/get_overview.py +155 -0
- package/skills/adgine-geo-dashboard/scripts/get_visibility_overview.py +93 -0
- package/skills/adgine-geo-docs/SKILL.md +60 -0
- package/skills/adgine-geo-domains/SKILL.md +223 -0
- package/skills/adgine-geo-domains/scripts/_client.py +341 -0
- package/skills/adgine-geo-domains/scripts/get_domain.py +75 -0
- package/skills/adgine-geo-domains/scripts/list_domains.py +66 -0
- package/skills/adgine-geo-domains/scripts/search_domains.py +85 -0
- package/skills/adgine-geo-integrations/SKILL.md +188 -0
- package/skills/adgine-geo-integrations/scripts/_client.py +341 -0
- package/skills/adgine-geo-integrations/scripts/cloudflare_connect.py +155 -0
- package/skills/adgine-geo-integrations/scripts/cloudflare_worker.py +212 -0
- package/skills/adgine-geo-integrations/scripts/ga4_data.py +192 -0
- package/skills/adgine-geo-integrations/scripts/ga4_setup.py +120 -0
- package/skills/adgine-geo-opportunities/SKILL.md +162 -0
- package/skills/adgine-geo-opportunities/scripts/_client.py +341 -0
- package/skills/adgine-geo-opportunities/scripts/get_opportunity.py +90 -0
- package/skills/adgine-geo-opportunities/scripts/list_opportunities.py +92 -0
- package/skills/adgine-geo-performance/SKILL.md +177 -0
- package/skills/adgine-geo-performance/scripts/_client.py +341 -0
- package/skills/adgine-geo-performance/scripts/analyze_page.py +100 -0
- package/skills/adgine-geo-projects/SKILL.md +279 -0
- package/skills/adgine-geo-projects/scripts/_client.py +341 -0
- package/skills/adgine-geo-projects/scripts/check_auth.py +45 -0
- package/skills/adgine-geo-projects/scripts/list_projects.py +47 -0
- package/skills/adgine-geo-projects/scripts/manage_competitors.py +122 -0
- package/skills/adgine-geo-projects/scripts/manage_project.py +88 -0
- package/skills/adgine-geo-saas/SKILL.md +144 -0
- package/skills/adgine-geo-saas/scripts/_client.py +341 -0
- package/skills/adgine-geo-saas/scripts/check_domain.py +43 -0
- package/skills/adgine-geo-saas/scripts/create_website.py +56 -0
- package/skills/adgine-geo-saas/scripts/get_task.py +94 -0
- package/skills/adgine-geo-site-audit/README.md +281 -0
- package/skills/adgine-geo-site-audit/SKILL.md +449 -0
- package/skills/adgine-geo-site-audit/requirements.txt +5 -0
- package/skills/adgine-geo-site-audit/scripts/_client.py +341 -0
- package/skills/adgine-geo-site-audit/scripts/_geo_notice.py +55 -0
- package/skills/adgine-geo-site-audit/scripts/export_skill_package.py +197 -0
- package/skills/adgine-geo-site-audit/scripts/geo_collect.py +2443 -0
- package/skills/adgine-geo-site-audit/scripts/geo_score.py +490 -0
- package/skills/adgine-geo-site-audit/scripts/geo_timing.py +399 -0
- package/skills/adgine-geo-site-audit/scripts/geo_visibility.py +718 -0
- package/skills/adgine-geo-site-audit/scripts/render_report_pdf.py +1502 -0
- package/skills/adgine-geo-topics/SKILL.md +232 -0
- package/skills/adgine-geo-topics/scripts/_client.py +341 -0
- package/skills/adgine-geo-topics/scripts/generate_prompts.py +140 -0
- package/skills/adgine-geo-topics/scripts/manage_prompts.py +153 -0
- package/skills/adgine-geo-topics/scripts/manage_topics.py +145 -0
- package/skills/adgine-geo-visibility/SKILL.md +168 -0
- package/skills/adgine-geo-visibility/scripts/_client.py +341 -0
- package/skills/adgine-geo-visibility/scripts/get_execution.py +151 -0
- package/skills/adgine-geo-visibility/scripts/get_matrix.py +111 -0
- package/skills/adgine-geo-visibility/scripts/get_prompt_metrics.py +104 -0
- package/skills/adgine-geo-visibility/scripts/get_topic_metrics.py +201 -0
- package/skills/adgine-geo-visibility/scripts/get_visibility.py +166 -0
- package/skills/adgine-geo-wordpress/SKILL.md +151 -0
- package/skills/adgine-geo-wordpress/scripts/_client.py +341 -0
- package/skills/adgine-geo-wordpress/scripts/list_publishable.py +62 -0
- package/skills/adgine-geo-wordpress/scripts/manage_credentials.py +139 -0
- package/skills/adgine-geo-wordpress/scripts/manage_publishes.py +108 -0
- package/skills/adgine-geo-wordpress/scripts/publish.py +79 -0
- package/skills/scripts/check_version.py +290 -0
- package/skills/setup.py +248 -0
- package/src/config.js +126 -0
- package/src/doctor.js +137 -0
- package/src/install.js +90 -0
- package/src/manifest.js +99 -0
- package/src/runner.js +159 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""List GEO website projects for the authenticated user.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python3 scripts/list_projects.py [--limit N] [--json]
|
|
6
|
+
"""
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
import argparse
|
|
10
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
11
|
+
from _client import get_api_config, api_get, extract_data, print_json # _notice printed on import
|
|
12
|
+
|
|
13
|
+
parser = argparse.ArgumentParser(description="List GEO projects")
|
|
14
|
+
parser.add_argument("--limit", type=int, default=50, help="Max results (default: 50)")
|
|
15
|
+
parser.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
16
|
+
args = parser.parse_args()
|
|
17
|
+
|
|
18
|
+
key, base = get_api_config()
|
|
19
|
+
result = api_get("/api/projects", key, base, params={"limit": args.limit})
|
|
20
|
+
data = extract_data(result)
|
|
21
|
+
|
|
22
|
+
if args.json:
|
|
23
|
+
print_json(data)
|
|
24
|
+
sys.exit(0)
|
|
25
|
+
|
|
26
|
+
items = data.get("items", []) if isinstance(data, dict) else data
|
|
27
|
+
total = data.get("total", len(items)) if isinstance(data, dict) else len(items)
|
|
28
|
+
|
|
29
|
+
if not items:
|
|
30
|
+
print("No projects found.")
|
|
31
|
+
print("Create your first project at: https://app.geoplatform.ai")
|
|
32
|
+
sys.exit(0)
|
|
33
|
+
|
|
34
|
+
print(f"Found {total} project(s):\n")
|
|
35
|
+
print(f" {'ID':<36} {'Name':<30} {'Domain':<30} {'Brand'}")
|
|
36
|
+
print(f" {'-'*36} {'-'*30} {'-'*30} {'-'*5}")
|
|
37
|
+
|
|
38
|
+
for p in items:
|
|
39
|
+
pid = p.get("id", "")
|
|
40
|
+
name = (p.get("name") or p.get("url") or "")[:30]
|
|
41
|
+
domain = (p.get("domain") or "")[:30]
|
|
42
|
+
brand = "✓" if p.get("has_brand_profile") else "—"
|
|
43
|
+
print(f" {pid:<36} {name:<30} {domain:<30} {brand}")
|
|
44
|
+
|
|
45
|
+
print()
|
|
46
|
+
print("To set the active project:")
|
|
47
|
+
print(" export GEO_PROJECT_ID=<id>")
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Manage competitors for a GEO project.
|
|
3
|
+
|
|
4
|
+
Subcommands:
|
|
5
|
+
list — list all competitors
|
|
6
|
+
add --name <name> [--domain <url>] [--aliases a,b,c] [--source manual]
|
|
7
|
+
remove --competitor-id <id> --yes — DESTRUCTIVE
|
|
8
|
+
|
|
9
|
+
Usage examples:
|
|
10
|
+
python3 scripts/manage_competitors.py list
|
|
11
|
+
python3 scripts/manage_competitors.py add --name "Acme" --domain acme.com \\
|
|
12
|
+
--aliases "Acme Inc,Acme Corp"
|
|
13
|
+
python3 scripts/manage_competitors.py remove --competitor-id <id> --yes
|
|
14
|
+
"""
|
|
15
|
+
import sys
|
|
16
|
+
import os
|
|
17
|
+
import argparse
|
|
18
|
+
|
|
19
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
20
|
+
from _client import (
|
|
21
|
+
get_api_config, get_project_id,
|
|
22
|
+
api_get, api_post, api_delete,
|
|
23
|
+
extract_data, print_json, truncate,
|
|
24
|
+
pad,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def cmd_list(args, key, base, pid):
|
|
29
|
+
result = api_get(f"/api/projects/{pid}/competitors", key, base)
|
|
30
|
+
data = extract_data(result)
|
|
31
|
+
items = data if isinstance(data, list) else (data or {}).get("competitors", [])
|
|
32
|
+
|
|
33
|
+
if args.json:
|
|
34
|
+
print_json(items)
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
print(f"Competitors for project {pid}")
|
|
38
|
+
print()
|
|
39
|
+
if not items:
|
|
40
|
+
print("No competitors yet. Use `add` to create one.")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
print("```")
|
|
44
|
+
print("┌──────────────────────────────────────┬────────────────────┬────────────────────┐")
|
|
45
|
+
print("│ ID │ Name │ Domain │")
|
|
46
|
+
print("├──────────────────────────────────────┼────────────────────┼────────────────────┤")
|
|
47
|
+
for c in items:
|
|
48
|
+
cid = truncate(c.get("id") or c.get("competitor_id") or c.get("brand_id"), 36)
|
|
49
|
+
name = truncate(c.get("name"), 18)
|
|
50
|
+
domain = truncate(c.get("domain") or "--", 18)
|
|
51
|
+
print(f"│ {pad(cid, 36)} │ {pad(name, 18)} │ {pad(domain, 18)} │")
|
|
52
|
+
print("└──────────────────────────────────────┴────────────────────┴────────────────────┘")
|
|
53
|
+
print("```")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def cmd_add(args, key, base, pid):
|
|
57
|
+
body = {"name": args.name, "source": args.source}
|
|
58
|
+
if args.domain:
|
|
59
|
+
body["domain"] = args.domain
|
|
60
|
+
if args.aliases:
|
|
61
|
+
body["aliases"] = [a.strip() for a in args.aliases.split(",") if a.strip()]
|
|
62
|
+
result = api_post(f"/api/projects/{pid}/competitors", key, base, body=body)
|
|
63
|
+
data = extract_data(result)
|
|
64
|
+
if args.json:
|
|
65
|
+
print_json(data)
|
|
66
|
+
return
|
|
67
|
+
print(f"Added competitor: {args.name}")
|
|
68
|
+
if isinstance(data, dict):
|
|
69
|
+
cid = data.get("id") or data.get("competitor_id") or data.get("brand_id")
|
|
70
|
+
if cid:
|
|
71
|
+
print(f" ID: {cid}")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def cmd_remove(args, key, base, pid):
|
|
75
|
+
if not args.yes:
|
|
76
|
+
print(f"About to remove competitor {args.competitor_id} from project {pid}.")
|
|
77
|
+
print("Re-run with --yes to confirm.")
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
result = api_delete(
|
|
80
|
+
f"/api/projects/{pid}/competitors/{args.competitor_id}",
|
|
81
|
+
key, base,
|
|
82
|
+
)
|
|
83
|
+
data = extract_data(result)
|
|
84
|
+
if args.json:
|
|
85
|
+
print_json(data if data is not None else {"ok": True})
|
|
86
|
+
return
|
|
87
|
+
print(f"Removed competitor: {args.competitor_id}")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def main():
|
|
91
|
+
parser = argparse.ArgumentParser(description="Manage GEO project competitors")
|
|
92
|
+
parser.add_argument("--project-id", help="Project ID (or set GEO_PROJECT_ID env var)")
|
|
93
|
+
parser.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
94
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
95
|
+
|
|
96
|
+
sub.add_parser("list", help="List all competitors")
|
|
97
|
+
|
|
98
|
+
p_add = sub.add_parser("add", help="Add a competitor")
|
|
99
|
+
p_add.add_argument("--name", required=True, help="Competitor brand name")
|
|
100
|
+
p_add.add_argument("--domain", help="Competitor domain")
|
|
101
|
+
p_add.add_argument("--aliases", help="Comma-separated alias list")
|
|
102
|
+
p_add.add_argument("--source", default="manual",
|
|
103
|
+
choices=["manual", "brand_profile", "ai_discovery"])
|
|
104
|
+
|
|
105
|
+
p_rm = sub.add_parser("remove", help="Remove a competitor (DESTRUCTIVE)")
|
|
106
|
+
p_rm.add_argument("--competitor-id", required=True)
|
|
107
|
+
p_rm.add_argument("--yes", action="store_true", help="Confirm deletion")
|
|
108
|
+
|
|
109
|
+
args = parser.parse_args()
|
|
110
|
+
key, base = get_api_config()
|
|
111
|
+
pid = get_project_id(args.project_id)
|
|
112
|
+
|
|
113
|
+
if args.command == "list":
|
|
114
|
+
cmd_list(args, key, base, pid)
|
|
115
|
+
elif args.command == "add":
|
|
116
|
+
cmd_add(args, key, base, pid)
|
|
117
|
+
elif args.command == "remove":
|
|
118
|
+
cmd_remove(args, key, base, pid)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
main()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Get, create, update, or delete a GEO project.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python3 scripts/manage_project.py get --project-id <id> [--json]
|
|
6
|
+
python3 scripts/manage_project.py create --url https://example.com [--description "text"]
|
|
7
|
+
python3 scripts/manage_project.py update --project-id <id> [--name "Name"] [--url "url"] [--description "text"]
|
|
8
|
+
python3 scripts/manage_project.py delete --project-id <id>
|
|
9
|
+
"""
|
|
10
|
+
import sys
|
|
11
|
+
import os
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
15
|
+
from _client import get_api_config, get_project_id, api_get, api_post, api_put, api_delete, extract_data, print_json
|
|
16
|
+
|
|
17
|
+
parser = argparse.ArgumentParser(description="Manage a GEO project")
|
|
18
|
+
parser.add_argument("action", choices=["get", "create", "update", "delete"])
|
|
19
|
+
parser.add_argument("--project-id", help="Project ID (or set GEO_PROJECT_ID env var)")
|
|
20
|
+
parser.add_argument("--url", help="Website URL (required for create)")
|
|
21
|
+
parser.add_argument("--name", help="Project name")
|
|
22
|
+
parser.add_argument("--description", help="Project description")
|
|
23
|
+
parser.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
24
|
+
args = parser.parse_args()
|
|
25
|
+
|
|
26
|
+
key, base = get_api_config()
|
|
27
|
+
|
|
28
|
+
# ── GET ──────────────────────────────────────────────────────────────────────
|
|
29
|
+
if args.action == "get":
|
|
30
|
+
pid = get_project_id(args.project_id)
|
|
31
|
+
result = api_get(f"/api/projects/{pid}", key, base)
|
|
32
|
+
p = extract_data(result)
|
|
33
|
+
if args.json:
|
|
34
|
+
print_json(p)
|
|
35
|
+
sys.exit(0)
|
|
36
|
+
print(f"Project: {p.get('name') or p.get('url')}")
|
|
37
|
+
print(f" ID : {p.get('id')}")
|
|
38
|
+
print(f" URL : {p.get('url')}")
|
|
39
|
+
print(f" Domain : {p.get('domain') or '—'}")
|
|
40
|
+
print(f" Description : {p.get('description') or '—'}")
|
|
41
|
+
print(f" Brand ready : {'Yes' if p.get('has_brand_profile') else 'No'}")
|
|
42
|
+
print(f" Created : {p.get('created_at', '')[:10]}")
|
|
43
|
+
|
|
44
|
+
# ── CREATE ───────────────────────────────────────────────────────────────────
|
|
45
|
+
elif args.action == "create":
|
|
46
|
+
if not args.url:
|
|
47
|
+
print("ERROR: --url is required for create")
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
body = {"url": args.url}
|
|
50
|
+
if args.description:
|
|
51
|
+
body["description"] = args.description
|
|
52
|
+
result = api_post("/api/projects", key, base, body)
|
|
53
|
+
p = extract_data(result)
|
|
54
|
+
if args.json:
|
|
55
|
+
print_json(p)
|
|
56
|
+
sys.exit(0)
|
|
57
|
+
print(f"✓ Project created")
|
|
58
|
+
print(f" ID : {p.get('id')}")
|
|
59
|
+
print(f" Name : {p.get('name') or p.get('url')}")
|
|
60
|
+
print(f" Domain : {p.get('domain') or '—'}")
|
|
61
|
+
print()
|
|
62
|
+
print(f"To set as active: export GEO_PROJECT_ID={p.get('id')}")
|
|
63
|
+
|
|
64
|
+
# ── UPDATE ───────────────────────────────────────────────────────────────────
|
|
65
|
+
elif args.action == "update":
|
|
66
|
+
pid = get_project_id(args.project_id)
|
|
67
|
+
body = {}
|
|
68
|
+
if args.name: body["name"] = args.name
|
|
69
|
+
if args.url: body["url"] = args.url
|
|
70
|
+
if args.description: body["description"] = args.description
|
|
71
|
+
if not body:
|
|
72
|
+
print("ERROR: Provide at least one of --name, --url, --description")
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
result = api_put(f"/api/projects/{pid}", key, base, body)
|
|
75
|
+
p = extract_data(result)
|
|
76
|
+
if args.json:
|
|
77
|
+
print_json(p)
|
|
78
|
+
sys.exit(0)
|
|
79
|
+
print(f"✓ Project updated: {p.get('name') or p.get('url')}")
|
|
80
|
+
|
|
81
|
+
# ── DELETE ───────────────────────────────────────────────────────────────────
|
|
82
|
+
elif args.action == "delete":
|
|
83
|
+
pid = get_project_id(args.project_id)
|
|
84
|
+
api_delete(f"/api/projects/{pid}", key, base)
|
|
85
|
+
print(f"✓ Project {pid} deleted")
|
|
86
|
+
if os.environ.get("GEO_PROJECT_ID") == pid:
|
|
87
|
+
print(" NOTE: Unset GEO_PROJECT_ID since the deleted project was active.")
|
|
88
|
+
print(" Run: unset GEO_PROJECT_ID")
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adgine/geo-saas
|
|
3
|
+
description: Creates and tracks GEO SaaS-hosted websites — checks if a subdomain is available, kicks off an async website deployment with brand details, and polls deployment task status. Use when the user wants to launch a new SaaS website on the GEO platform (创建 SaaS 网站, 新建网站, 部署网站, launch website, create SaaS site), check whether a subdomain is taken (检查域名, subdomain availability, 二级域名), or check the progress of a website deployment task (部署状态, deployment status, task progress).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GEO SaaS
|
|
7
|
+
|
|
8
|
+
Three-script flow for spinning up a SaaS-hosted website on the GEO platform:
|
|
9
|
+
|
|
10
|
+
1. **Check** that the desired subdomain is available.
|
|
11
|
+
2. **Create** the website (returns a task_id; deployment runs asynchronously).
|
|
12
|
+
3. **Poll** the task until it reaches a terminal state.
|
|
13
|
+
|
|
14
|
+
## Output rules — IDs (apply to every reply)
|
|
15
|
+
|
|
16
|
+
These rules apply to **every list, table, and confirmation message** in this skill. Their goal: keep user-facing output friendly while preserving the IDs the agent needs internally.
|
|
17
|
+
|
|
18
|
+
1. **Lists & tables — never show raw UUIDs in cells.** Use a 1-based `#` index column instead. Keep a private mental mapping of `#N → actual UUID` so that follow-up commands like *"delete #3"*, *"run citation test on #1 #2"*, *"show details of the 2nd one"* resolve to the right entity.
|
|
19
|
+
- Index numbers restart from 1 in each new list — they are not stable across calls.
|
|
20
|
+
- If the user references *"the topic about X"* / *"that Poki vs CrazyGames prompt"*, match by visible content (name / title / domain / prompt text), not by ID.
|
|
21
|
+
|
|
22
|
+
2. **Single-item operations — prefer a human name over an ID.**
|
|
23
|
+
- ✅ *"Project **Poki vs Competitors** deleted."*
|
|
24
|
+
- ✅ *"Topic **Brand mentions in 2024** updated — name → 'Brand mentions 2025'."*
|
|
25
|
+
- ❌ *"Project `a4305b57-1c79-4cec-a17c-16eb1d959ea6` deleted."*
|
|
26
|
+
- If the entity has **no human-readable name** (e.g. an anonymous prompt or a job), use a short 8-character prefix: *"Prompt `2a2a8f4f…` deleted."* Never paste the full UUID.
|
|
27
|
+
|
|
28
|
+
3. **Always exception: `--json` mode.** When the user passes `--json` to a script or explicitly asks for raw JSON / debug output, print the script output verbatim — do not strip IDs.
|
|
29
|
+
|
|
30
|
+
4. **Internally, the agent still uses full UUIDs** for every API call (`--project-id`, `--topic-id`, `--prompt-id`, etc.). The display rules only affect what is shown back to the user.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Step 1: Make sure GEO_API_KEY is configured
|
|
35
|
+
|
|
36
|
+
Scripts auto-load `GEO_API_KEY` from `<skills-root>/.env` on import — **no `export` needed, no shell restart needed**. To check the configuration, run any script (it prints the exact `.env` path if the key is missing).
|
|
37
|
+
|
|
38
|
+
- ✅ Key already in `<skills-root>/.env` → proceed.
|
|
39
|
+
- ❌ Key missing, or user just gave you a new key → go to the **adgine-geo-projects** skill, **Step 0**, which runs `python3 <skills-root>/setup.py <KEY>` to write the key into the correct `.env` file. **Never** write the key to `~/.zshrc`, `~/.bashrc`, Hermes global config, or any user-secrets store.
|
|
40
|
+
|
|
41
|
+
> ⚠️ **IMPORTANT:** In all shell/exec commands, always reference the key as `$GEO_API_KEY` (the environment variable). Never hardcode the literal value.
|
|
42
|
+
|
|
43
|
+
## Scripts
|
|
44
|
+
|
|
45
|
+
### 1) Check subdomain availability
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
python3 scripts/check_domain.py --subdomain mysite [--json]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2) Create the website (async)
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python3 scripts/create_website.py --subdomain mysite \
|
|
55
|
+
--brand-name "My Site" \
|
|
56
|
+
--industry "SaaS" \
|
|
57
|
+
--description "An AI-first content platform" \
|
|
58
|
+
--language English \
|
|
59
|
+
[--json]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Returns a `task_id`. Pass it to the next step.
|
|
63
|
+
|
|
64
|
+
### 3) Track the deployment task
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
python3 scripts/get_task.py --task-id <id> [--poll] [--json]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Add `--poll` to block until the task reaches `Completed` or `Failed`.
|
|
71
|
+
|
|
72
|
+
> ⏳ **Expected duration: 5–15 minutes.** The script polls automatically (interval 10 s, timeout 20 min). Do NOT cancel early — website deployment involves provisioning infrastructure.
|
|
73
|
+
|
|
74
|
+
## Recommended flow
|
|
75
|
+
|
|
76
|
+
> 🌐 **Step 1.** Check `mysite` is available.
|
|
77
|
+
>
|
|
78
|
+
> 🚀 **Step 2.** Start deployment, capture `task_id`.
|
|
79
|
+
>
|
|
80
|
+
> ⏳ **Step 3.** Poll status (use `--poll`).
|
|
81
|
+
>
|
|
82
|
+
> ✅ When the task reports `Completed`, the script prints the site URL **and** the WordPress admin login URL, username, and password automatically.
|
|
83
|
+
|
|
84
|
+
## Output Format
|
|
85
|
+
|
|
86
|
+
ASCII tables only. Status vocabulary:
|
|
87
|
+
`Pending` / `Generating` / `Completed` / `Failed`.
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
┌────────────────────┬──────────────────────────────┐
|
|
91
|
+
│ Field │ Value │
|
|
92
|
+
├────────────────────┼──────────────────────────────┤
|
|
93
|
+
│ status │ Generating │
|
|
94
|
+
│ progress │ 60% │
|
|
95
|
+
│ subdomain │ mysite │
|
|
96
|
+
└────────────────────┴──────────────────────────────┘
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Completed state — always show WordPress credentials
|
|
100
|
+
|
|
101
|
+
When `status` is `Completed` **and** the response includes `wp_username` / `wp_password`, the script automatically appends a second table with the WordPress login details:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
🎉 Your website is live! WordPress login details:
|
|
105
|
+
|
|
106
|
+
┌────────────────────┬──────────────────────────────┐
|
|
107
|
+
│ WordPress │ Info │
|
|
108
|
+
├────────────────────┼──────────────────────────────┤
|
|
109
|
+
│ login URL │ https://mysite.adgine.net/wp-login.php │
|
|
110
|
+
│ username │ admin │
|
|
111
|
+
│ password │ xxxxxxxx │
|
|
112
|
+
└────────────────────┴──────────────────────────────┘
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- The login URL is derived from the `domain` field in the task response: `https://<domain>/wp-login.php`. The `domain` is the full subdomain (e.g. `gamecalc.adgine.net`); `adgine.net` and `/wp-login.php` are always fixed.
|
|
116
|
+
- **Never omit** the credentials table when status is Completed — this is the primary way the user learns how to access their new site.
|
|
117
|
+
|
|
118
|
+
## Related endpoints
|
|
119
|
+
|
|
120
|
+
| Method | Path |
|
|
121
|
+
|---|---|
|
|
122
|
+
| GET | `/api/saas/domain/check` |
|
|
123
|
+
| POST | `/api/saas/websites` |
|
|
124
|
+
| GET | `/api/saas/task/{task_id}` |
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Post-task recommendations
|
|
129
|
+
|
|
130
|
+
After SaaS website deployment, guide the user to set up their GEO workflow:
|
|
131
|
+
|
|
132
|
+
| Scenario | → use skill (agent-internal) |
|
|
133
|
+
|---|---|
|
|
134
|
+
| Deployment completed | 确认项目已创建,设置 `GEO_PROJECT_ID` *(→ adgine-geo-projects)*|
|
|
135
|
+
| Site is live | 为新网站生成品牌画像 *(→ adgine-geo-brand)*|
|
|
136
|
+
| Site is live | 创建主题和 AI 搜索提示词 *(→ adgine-geo-topics)*|
|
|
137
|
+
| Deployment failed | 检查错误信息,修正后重试 *(→ adgine-geo-saas)*|
|
|
138
|
+
| WordPress credentials received | 保存凭证后连接 WordPress 站点 *(→ adgine-geo-wordpress)* |
|
|
139
|
+
|
|
140
|
+
**⚠️ Output rule:** Do NOT write skill names (e.g. `adgine-geo-xxx`) in user-facing suggestions. Each suggestion must be phrased as a natural-language prompt the user can copy and send directly to the agent.
|
|
141
|
+
|
|
142
|
+
> 💡 **建议下一步:**
|
|
143
|
+
> 1. **[行动标题]** — *"[可直接发送给 AI 的自然语言提示词]"*
|
|
144
|
+
> 2. **[行动标题]** — *"[可直接发送给 AI 的自然语言提示词]"*
|