@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,67 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""View the brand cognition profile for a GEO project.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python3 scripts/get_brand.py [--project-id <id>] [--json]
|
|
6
|
+
"""
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
import argparse
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
12
|
+
from _client import get_api_config, get_project_id, api_get, extract_data, print_json
|
|
13
|
+
|
|
14
|
+
parser = argparse.ArgumentParser(description="View GEO brand cognition profile")
|
|
15
|
+
parser.add_argument("--project-id", help="Project ID (or set GEO_PROJECT_ID env var)")
|
|
16
|
+
parser.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
17
|
+
args = parser.parse_args()
|
|
18
|
+
|
|
19
|
+
key, base = get_api_config()
|
|
20
|
+
pid = get_project_id(args.project_id)
|
|
21
|
+
|
|
22
|
+
result = api_get(f"/api/projects/{pid}/brand", key, base)
|
|
23
|
+
data = extract_data(result)
|
|
24
|
+
|
|
25
|
+
if args.json:
|
|
26
|
+
print_json(data)
|
|
27
|
+
sys.exit(0)
|
|
28
|
+
|
|
29
|
+
status = data.get("status", "none")
|
|
30
|
+
profile = data.get("profile") or {}
|
|
31
|
+
|
|
32
|
+
print(f"Brand Profile (project: {pid})")
|
|
33
|
+
print(f"Status : {status}")
|
|
34
|
+
|
|
35
|
+
if status == "none":
|
|
36
|
+
print("\nNo brand profile yet.")
|
|
37
|
+
print(" Run: python3 scripts/generate_brand.py --project-id " + pid)
|
|
38
|
+
sys.exit(0)
|
|
39
|
+
|
|
40
|
+
if status == "generating":
|
|
41
|
+
job_id = data.get("job_id", "")
|
|
42
|
+
print(f"Job ID : {job_id}")
|
|
43
|
+
print("\nGeneration is in progress. Run generate_brand.py to wait for completion.")
|
|
44
|
+
sys.exit(0)
|
|
45
|
+
|
|
46
|
+
FIELDS = [
|
|
47
|
+
("brand_introduction", "Brand Introduction"),
|
|
48
|
+
("ideal_customer", "Ideal Customer"),
|
|
49
|
+
("competitors", "Competitors"),
|
|
50
|
+
("brand_perspective", "Brand Perspective"),
|
|
51
|
+
("author_persona", "Author Persona"),
|
|
52
|
+
("voice_and_tone", "Voice & Tone"),
|
|
53
|
+
("writing_rules", "Writing Rules"),
|
|
54
|
+
("cta_text", "CTA Text"),
|
|
55
|
+
("cta_landing_page", "CTA URL"),
|
|
56
|
+
("language", "Language"),
|
|
57
|
+
("region", "Region"),
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
print()
|
|
61
|
+
for field_key, label in FIELDS:
|
|
62
|
+
value = profile.get(field_key, "")
|
|
63
|
+
if value:
|
|
64
|
+
display = value[:300] + "\n [truncated]" if len(value) > 300 else value
|
|
65
|
+
print(f"[{label}]")
|
|
66
|
+
print(f" {display}")
|
|
67
|
+
print()
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""List, inspect or manually start brand generation jobs.
|
|
3
|
+
|
|
4
|
+
Subcommands:
|
|
5
|
+
list — list brand jobs (latest first)
|
|
6
|
+
get --job-id <id> — show one job's full status
|
|
7
|
+
start --job-id <id> — manually trigger a job that was created with auto_start=false
|
|
8
|
+
|
|
9
|
+
Usage examples:
|
|
10
|
+
python3 scripts/list_jobs.py list
|
|
11
|
+
python3 scripts/list_jobs.py get --job-id <id>
|
|
12
|
+
python3 scripts/list_jobs.py start --job-id <id>
|
|
13
|
+
"""
|
|
14
|
+
import sys
|
|
15
|
+
import os
|
|
16
|
+
import argparse
|
|
17
|
+
|
|
18
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
19
|
+
from _client import (
|
|
20
|
+
get_api_config, get_project_id,
|
|
21
|
+
api_get, api_post,
|
|
22
|
+
extract_data, print_json, truncate,
|
|
23
|
+
pad,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _norm_status(s):
|
|
28
|
+
s = (s or "").lower()
|
|
29
|
+
if s in ("completed", "complete", "success", "done"):
|
|
30
|
+
return "Completed"
|
|
31
|
+
if s in ("failed", "error"):
|
|
32
|
+
return "Failed"
|
|
33
|
+
if s in ("running", "generating", "in_progress"):
|
|
34
|
+
return "Generating"
|
|
35
|
+
if s in ("pending", "queued", "created"):
|
|
36
|
+
return "Pending"
|
|
37
|
+
return s.title() if s else "--"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def cmd_list(args, key, base, pid):
|
|
41
|
+
result = api_get(f"/api/projects/{pid}/brand/jobs", key, base,
|
|
42
|
+
params={"page": args.page, "limit": args.limit})
|
|
43
|
+
data = extract_data(result)
|
|
44
|
+
items = data if isinstance(data, list) else (data or {}).get("jobs") or (data or {}).get("items", [])
|
|
45
|
+
|
|
46
|
+
if args.json:
|
|
47
|
+
print_json(items)
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
print(f"Brand jobs for project {pid}")
|
|
51
|
+
print()
|
|
52
|
+
if not items:
|
|
53
|
+
print("No brand generation jobs yet.")
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
print("```")
|
|
57
|
+
print("┌──────────────────────────────────────┬──────────────┬──────────────────────┐")
|
|
58
|
+
print("│ Job ID │ Status │ Created at │")
|
|
59
|
+
print("├──────────────────────────────────────┼──────────────┼──────────────────────┤")
|
|
60
|
+
for j in items:
|
|
61
|
+
jid = truncate(j.get("id") or j.get("job_id"), 36)
|
|
62
|
+
st = _norm_status(j.get("status"))
|
|
63
|
+
created = truncate(j.get("created_at") or j.get("started_at") or "--", 20)
|
|
64
|
+
print(f"│ {pad(jid, 36)} │ {pad(st, 12)} │ {pad(created, 20)} │")
|
|
65
|
+
print("└──────────────────────────────────────┴──────────────┴──────────────────────┘")
|
|
66
|
+
print("```")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def cmd_get(args, key, base, pid):
|
|
70
|
+
result = api_get(f"/api/projects/{pid}/brand/jobs/{args.job_id}", key, base)
|
|
71
|
+
data = extract_data(result) or {}
|
|
72
|
+
|
|
73
|
+
if args.json:
|
|
74
|
+
print_json(data)
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
print(f"Brand job: {args.job_id}")
|
|
78
|
+
print()
|
|
79
|
+
print("```")
|
|
80
|
+
print("┌────────────────────┬──────────────────────────────────────┐")
|
|
81
|
+
print("│ Field │ Value │")
|
|
82
|
+
print("├────────────────────┼──────────────────────────────────────┤")
|
|
83
|
+
for k in ("status", "current_phase", "progress", "created_at",
|
|
84
|
+
"started_at", "completed_at", "error_message", "url",
|
|
85
|
+
"language", "region"):
|
|
86
|
+
if k in data:
|
|
87
|
+
val = data.get(k)
|
|
88
|
+
if k == "status":
|
|
89
|
+
val = _norm_status(val)
|
|
90
|
+
print(f"│ {pad(k, 18)} │ {pad(truncate(val, 36), 36)} │")
|
|
91
|
+
print("└────────────────────┴──────────────────────────────────────┘")
|
|
92
|
+
print("```")
|
|
93
|
+
|
|
94
|
+
phases = data.get("phases") or []
|
|
95
|
+
if phases:
|
|
96
|
+
print()
|
|
97
|
+
print("Phases")
|
|
98
|
+
print("```")
|
|
99
|
+
print("┌────────────────────────────────┬──────────────┐")
|
|
100
|
+
print("│ Phase │ Status │")
|
|
101
|
+
print("├────────────────────────────────┼──────────────┤")
|
|
102
|
+
for p in phases:
|
|
103
|
+
name = truncate(p.get("name") or p.get("phase"), 30)
|
|
104
|
+
st = _norm_status(p.get("status"))
|
|
105
|
+
print(f"│ {pad(name, 30)} │ {pad(st, 12)} │")
|
|
106
|
+
print("└────────────────────────────────┴──────────────┘")
|
|
107
|
+
print("```")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def cmd_start(args, key, base, pid):
|
|
111
|
+
result = api_post(
|
|
112
|
+
f"/api/projects/{pid}/brand/jobs/{args.job_id}/start",
|
|
113
|
+
key, base,
|
|
114
|
+
)
|
|
115
|
+
data = extract_data(result)
|
|
116
|
+
if args.json:
|
|
117
|
+
print_json(data if data is not None else {"ok": True})
|
|
118
|
+
return
|
|
119
|
+
print(f"Started brand job: {args.job_id}")
|
|
120
|
+
if isinstance(data, dict) and data.get("status"):
|
|
121
|
+
print(f" Status: {_norm_status(data.get('status'))}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def main():
|
|
125
|
+
parser = argparse.ArgumentParser(description="Manage brand generation jobs")
|
|
126
|
+
parser.add_argument("--project-id", help="Project ID (or set GEO_PROJECT_ID env var)")
|
|
127
|
+
parser.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
128
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
129
|
+
|
|
130
|
+
p_list = sub.add_parser("list", help="List brand jobs")
|
|
131
|
+
p_list.add_argument("--page", type=int, default=1)
|
|
132
|
+
p_list.add_argument("--limit", type=int, default=20)
|
|
133
|
+
|
|
134
|
+
p_get = sub.add_parser("get", help="Get one job's details")
|
|
135
|
+
p_get.add_argument("--job-id", required=True)
|
|
136
|
+
|
|
137
|
+
p_start = sub.add_parser("start", help="Manually start a queued job")
|
|
138
|
+
p_start.add_argument("--job-id", required=True)
|
|
139
|
+
|
|
140
|
+
args = parser.parse_args()
|
|
141
|
+
key, base = get_api_config()
|
|
142
|
+
pid = get_project_id(args.project_id)
|
|
143
|
+
|
|
144
|
+
if args.command == "list":
|
|
145
|
+
cmd_list(args, key, base, pid)
|
|
146
|
+
elif args.command == "get":
|
|
147
|
+
cmd_get(args, key, base, pid)
|
|
148
|
+
elif args.command == "start":
|
|
149
|
+
cmd_start(args, key, base, pid)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == "__main__":
|
|
153
|
+
main()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Update a specific field in the GEO brand cognition profile.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python3 scripts/update_brand.py [--project-id <id>] --field <field> --value "<text>"
|
|
6
|
+
|
|
7
|
+
Examples:
|
|
8
|
+
python3 scripts/update_brand.py --field voice_and_tone --value "Friendly, expert, data-driven"
|
|
9
|
+
python3 scripts/update_brand.py --field competitors --value "Semrush, Ahrefs, Moz"
|
|
10
|
+
python3 scripts/update_brand.py --field language --value "Chinese (Simplified)" --region CN
|
|
11
|
+
"""
|
|
12
|
+
import sys
|
|
13
|
+
import os
|
|
14
|
+
import argparse
|
|
15
|
+
|
|
16
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
17
|
+
from _client import get_api_config, get_project_id, api_patch, extract_data, print_json
|
|
18
|
+
|
|
19
|
+
UPDATABLE_FIELDS = [
|
|
20
|
+
"brand_introduction",
|
|
21
|
+
"ideal_customer",
|
|
22
|
+
"competitors",
|
|
23
|
+
"brand_perspective",
|
|
24
|
+
"author_persona",
|
|
25
|
+
"voice_and_tone",
|
|
26
|
+
"writing_rules",
|
|
27
|
+
"cta_text",
|
|
28
|
+
"cta_landing_page",
|
|
29
|
+
"writing_sample_url",
|
|
30
|
+
"writing_sample_title",
|
|
31
|
+
"writing_sample_body",
|
|
32
|
+
"writing_sample_outline",
|
|
33
|
+
"language",
|
|
34
|
+
"region",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
parser = argparse.ArgumentParser(description="Update a GEO brand profile field")
|
|
38
|
+
parser.add_argument("--project-id", help="Project ID (or set GEO_PROJECT_ID env var)")
|
|
39
|
+
parser.add_argument("--field", required=True, choices=UPDATABLE_FIELDS,
|
|
40
|
+
help="Name of the brand field to update")
|
|
41
|
+
parser.add_argument("--value", required=True,
|
|
42
|
+
help="New value for the field")
|
|
43
|
+
parser.add_argument("--json", action="store_true", help="Output the updated profile as raw JSON")
|
|
44
|
+
args = parser.parse_args()
|
|
45
|
+
|
|
46
|
+
key, base = get_api_config()
|
|
47
|
+
pid = get_project_id(args.project_id)
|
|
48
|
+
|
|
49
|
+
body = {args.field: args.value}
|
|
50
|
+
result = api_patch(f"/api/projects/{pid}/brand", key, base, body)
|
|
51
|
+
data = extract_data(result)
|
|
52
|
+
|
|
53
|
+
if args.json:
|
|
54
|
+
print_json(data)
|
|
55
|
+
sys.exit(0)
|
|
56
|
+
|
|
57
|
+
print(f"✓ Updated [{args.field}] for project {pid}")
|
|
58
|
+
profile = data.get("profile") or data
|
|
59
|
+
updated_value = profile.get(args.field, args.value)
|
|
60
|
+
display = updated_value[:300] + "..." if len(str(updated_value)) > 300 else str(updated_value)
|
|
61
|
+
print(f" New value: {display}")
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adgine/geo-citation
|
|
3
|
+
description: Actively submits AI search prompts to live AI platforms (ChatGPT,
|
|
4
|
+
Perplexity, Google AI Overviews, Gemini) and measures whether your brand/website
|
|
5
|
+
appears in the real responses — citation rate, cited URLs, full AI reply text.
|
|
6
|
+
Requires GEO_API_KEY and a platform project with configured prompts. Use when
|
|
7
|
+
the user wants to run citation tests (运行引用测试 / 跑引用测试), check if AI
|
|
8
|
+
platforms actually cite their brand (AI 有没有引用我 / 品牌被引用了吗), measure
|
|
9
|
+
citation rates, review real AI platform responses, or see which URLs AI cited.
|
|
10
|
+
NOT for website technical/structural GEO audits — use adgine-geo-site-audit.
|
|
11
|
+
NOT for reading pre-aggregated visibility scores — use adgine-geo-visibility.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# GEO Citation Tests
|
|
15
|
+
|
|
16
|
+
## 触发条件
|
|
17
|
+
|
|
18
|
+
当用户说出以下意图时使用本 skill:
|
|
19
|
+
- “运行引用测试” / “跑引用测试” / “run citation test”
|
|
20
|
+
- “AI 有没有引用我” / “AI 搜索能找到我吗” / “ChatGPT 有没有提到我”
|
|
21
|
+
- “品牌被引用了吗” / “引用率多少” / “查看引用结果”
|
|
22
|
+
- “哪些 URL 被 AI 引用了” / “查看 AI 回复原文”
|
|
23
|
+
|
|
24
|
+
**⛔ 以下意图不属于本 skill:**
|
|
25
|
+
- “审计网站” / “GEO 评分” / “检测网站结构” → **adgine-geo-site-audit**
|
|
26
|
+
- “我的 Visibility Score” / “声量份额” / “可见性得分” → **adgine-geo-visibility**
|
|
27
|
+
- “创建提示词” / “管理主题” → **adgine-geo-topics**
|
|
28
|
+
|
|
29
|
+
## Output rules — IDs (apply to every reply)
|
|
30
|
+
|
|
31
|
+
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.
|
|
32
|
+
|
|
33
|
+
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.
|
|
34
|
+
- Index numbers restart from 1 in each new list — they are not stable across calls.
|
|
35
|
+
- 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.
|
|
36
|
+
|
|
37
|
+
2. **Single-item operations — prefer a human name over an ID.**
|
|
38
|
+
- ✅ *"Project **Poki vs Competitors** deleted."*
|
|
39
|
+
- ✅ *"Topic **Brand mentions in 2024** updated — name → 'Brand mentions 2025'."*
|
|
40
|
+
- ❌ *"Project `a4305b57-1c79-4cec-a17c-16eb1d959ea6` deleted."*
|
|
41
|
+
- 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.
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
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.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Step 1: Make sure GEO_API_KEY is configured
|
|
50
|
+
|
|
51
|
+
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).
|
|
52
|
+
|
|
53
|
+
- ✅ Key already in `<skills-root>/.env` → proceed.
|
|
54
|
+
- ❌ 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.
|
|
55
|
+
|
|
56
|
+
> ⚠️ **IMPORTANT:** In all shell/exec commands, always reference the key as `$GEO_API_KEY` (the environment variable). Never hardcode the key value or use placeholder strings like `API_KEY=***` or `API_KEY=geo_sk_live_xxx` directly in a command — this will cause authentication failures.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## Project selection
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
export GEO_PROJECT_ID=<project-id> # session shortcut — resets when terminal closes
|
|
63
|
+
# Run python3 scripts/list_projects.py from adgine-geo-projects skill to find your IDs
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## What are citation tests?
|
|
67
|
+
|
|
68
|
+
A citation test submits an AI search prompt to multiple AI platforms (ChatGPT, Perplexity, Google AI Overviews, etc.) and checks whether your website or brand appears in the response — either as a cited source or in the generated text.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Commands
|
|
73
|
+
|
|
74
|
+
### Run citation tests on prompts
|
|
75
|
+
```bash
|
|
76
|
+
python3 scripts/create_tests.py --prompt-ids <id1,id2,...> [--project-id <id>]
|
|
77
|
+
```
|
|
78
|
+
Creates citation tests for each prompt × platform combination. Results are processed asynchronously.
|
|
79
|
+
|
|
80
|
+
### Get results for a prompt
|
|
81
|
+
```bash
|
|
82
|
+
python3 scripts/get_results.py --prompt-id <id> [--project-id <id>] [--json]
|
|
83
|
+
```
|
|
84
|
+
Shows citation test results for a specific prompt, including:
|
|
85
|
+
- Platform
|
|
86
|
+
- Test status
|
|
87
|
+
- Whether your brand was cited
|
|
88
|
+
- Full AI response text
|
|
89
|
+
- List of URLs cited
|
|
90
|
+
|
|
91
|
+
### Get results with date filtering
|
|
92
|
+
```bash
|
|
93
|
+
python3 scripts/get_results.py --prompt-id <id> --start-date 2025-02-01 --end-date 2025-03-01 [--json]
|
|
94
|
+
```
|
|
95
|
+
When `--start-date` / `--end-date` are provided, uses the analytics endpoint to filter executions by date range. Useful for comparing citation performance across time periods.
|
|
96
|
+
|
|
97
|
+
### Get project-level citation aggregate
|
|
98
|
+
```bash
|
|
99
|
+
python3 scripts/get_aggregate.py [--start-date 2025-02-22] [--end-date 2025-03-14] [--platform chatgpt,perplexity] [--json]
|
|
100
|
+
```
|
|
101
|
+
Returns project-level citation metrics for a time window (auto-compared with previous equal-length period):
|
|
102
|
+
- **Citation Count**: total times your brand was cited
|
|
103
|
+
- **Citation Share**: your brand's citations as % of all brand citations
|
|
104
|
+
- **Citation Rank**: your brand's ranking among all brands by citation count
|
|
105
|
+
- **By Platform**: per-platform breakdown
|
|
106
|
+
- **Competitor Ranking**: all brands ranked by citations
|
|
107
|
+
|
|
108
|
+
### Get aggregated citation URLs
|
|
109
|
+
```bash
|
|
110
|
+
python3 scripts/get_results.py --aggregate [--project-id <id>] \
|
|
111
|
+
--prompt-ids <id1,id2,...> [--json]
|
|
112
|
+
```
|
|
113
|
+
Shows all URLs that were cited across tests for a set of prompts.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Workflow
|
|
118
|
+
|
|
119
|
+
1. Generate prompts: `adgine-geo-topics/scripts/generate_prompts.py --topic-id <id>`
|
|
120
|
+
2. Run tests: `python3 scripts/create_tests.py --prompt-ids <id1,id2,...>`
|
|
121
|
+
3. Wait ~5–15 minutes for AI platforms to respond
|
|
122
|
+
4. Review results: `python3 scripts/get_results.py --prompt-id <id>`
|
|
123
|
+
5. Analyze trends: `python3 scripts/get_aggregate.py --start-date <from> --end-date <to>`
|
|
124
|
+
|
|
125
|
+
### Period Comparison Workflow
|
|
126
|
+
|
|
127
|
+
To compare citation performance across two time periods (e.g., "early GEO" vs "recent"):
|
|
128
|
+
1. Run `python3 scripts/get_aggregate.py --start-date <early_start> --end-date <early_end> --json` for the early period
|
|
129
|
+
2. Run `python3 scripts/get_aggregate.py --start-date <recent_start> --end-date <recent_end> --json` for the recent period
|
|
130
|
+
3. Compare the `citation_count`, `citation_share`, and `citation_rank` values between the two responses
|
|
131
|
+
|
|
132
|
+
## Output Format
|
|
133
|
+
|
|
134
|
+
> ⚠️ **CRITICAL — Table cell content rule (must follow exactly):**
|
|
135
|
+
> Tables use fenced code blocks with box-drawing borders. They only align correctly when **every cell contains ASCII characters exclusively**.
|
|
136
|
+
> - **NEVER** put emoji inside table cells. They are 2 display units wide but count as 1 character, permanently misaligning all following columns.
|
|
137
|
+
> - Emoji go ONLY on the label line **above** the ` ``` ` fence.
|
|
138
|
+
> - Status values in cells: `Done` / `Pending` / `---`
|
|
139
|
+
> - Cited values in cells: `Yes` / `No` (NOT ✅/❌)
|
|
140
|
+
> - Use `[label](url)` for clickable URLs (outside fenced blocks).
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
### When submitting tests (`create_tests.py`)
|
|
145
|
+
|
|
146
|
+
> ✅ **Citation tests submitted** for **N** prompt(s).
|
|
147
|
+
> Results ready in ~5–15 min. Check with `get_results.py --prompt-id <id>`.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### When showing results for a prompt (`get_results.py`)
|
|
152
|
+
|
|
153
|
+
> 🔍 **Citation Results**
|
|
154
|
+
> Prompt: *"<prompt text>"*
|
|
155
|
+
> ID: `<prompt-id>`
|
|
156
|
+
|
|
157
|
+
🎯 Per-Platform Results
|
|
158
|
+
```
|
|
159
|
+
┌──────────────┬───────────┬───────┬────────────┐
|
|
160
|
+
│ Platform │ Status │ Cited │ URLs found │
|
|
161
|
+
├──────────────┼───────────┼───────┼────────────┤
|
|
162
|
+
│ ChatGPT │ Done │ Yes │ 2 │
|
|
163
|
+
│ Perplexity │ Done │ No │ 0 │
|
|
164
|
+
│ Google AIO │ Pending │ --- │ - │
|
|
165
|
+
└──────────────┴───────────┴───────┴────────────┘
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
For each platform where Cited = `Yes`, list URLs as clickable links:
|
|
169
|
+
|
|
170
|
+
**ChatGPT** — cited URLs:
|
|
171
|
+
- [example.com/article-1](https://example.com/article-1)
|
|
172
|
+
- [example.com/about](https://example.com/about)
|
|
173
|
+
|
|
174
|
+
*Response excerpt: "<first 200 chars>…"*
|
|
175
|
+
|
|
176
|
+
> 📊 **Citation rate: 2 / 3 platforms (67%)** for this prompt.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### When showing aggregated URLs (`--aggregate`)
|
|
181
|
+
|
|
182
|
+
> 🔗 **Most Cited URLs** — across **N** prompts
|
|
183
|
+
|
|
184
|
+
🔝 Top URLs
|
|
185
|
+
```
|
|
186
|
+
┌────┬──────────────────────────────────────┬────────┐
|
|
187
|
+
│ # │ URL │ Cited │
|
|
188
|
+
├────┼──────────────────────────────────────┼────────┤
|
|
189
|
+
│ 1 │ example.com/guide │ 8x │
|
|
190
|
+
│ 2 │ example.com/about │ 5x │
|
|
191
|
+
│ 3 │ example.com/pricing │ 2x │
|
|
192
|
+
└────┴──────────────────────────────────────┴────────┘
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Then list as clickable links below the table:
|
|
196
|
+
- [example.com/guide](https://example.com/guide) — 8×
|
|
197
|
+
- [example.com/about](https://example.com/about) — 5×
|
|
198
|
+
|
|
199
|
+
> 📊 **N unique URLs** cited across **M prompts**.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Post-task recommendations
|
|
204
|
+
|
|
205
|
+
After reviewing citation results, suggest contextual next actions:
|
|
206
|
+
|
|
207
|
+
| What you saw | → use skill (agent-internal) |
|
|
208
|
+
|---|---|
|
|
209
|
+
| Citation tests completed | 查看可见性趋势和竞品对比 *(→ adgine-geo-visibility)*|
|
|
210
|
+
| Low citation rate (< 30%) | 生成针对性的 GEO 优化文章 *(→ adgine-geo-content)*|
|
|
211
|
+
| Low citation rate (< 30%) | 检查被引用页面的 AI 优化健康度 *(→ adgine-geo-performance)*|
|
|
212
|
+
| Specific URLs cited | 查看这些页面的 AI 爬虫访问明细 *(→ adgine-geo-aiagent)*|
|
|
213
|
+
| High citation rate | 扩大内容覆盖,生成更多 GEO 文章 *(→ adgine-geo-content)*|
|
|
214
|
+
| Aggregate results reviewed | 发布表现最好的内容到 WordPress *(→ adgine-geo-wordpress)*|
|
|
215
|
+
|
|
216
|
+
**⚠️ 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.
|
|
217
|
+
|
|
218
|
+
> 💡 **建议下一步:**
|
|
219
|
+
> 1. **[行动标题]** — *"[可直接发送给 AI 的自然语言提示词]"*
|
|
220
|
+
> 2. **[行动标题]** — *"[可直接发送给 AI 的自然语言提示词]"*
|