@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,104 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Per-prompt visibility overview (Visibility Score + Avg Position with
|
|
3
|
+
trend, previous-period delta, and per-platform breakdown).
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python3 scripts/get_prompt_metrics.py --prompt-id <prompt_id>
|
|
7
|
+
python3 scripts/get_prompt_metrics.py --prompt-id <id> --start 2025-11-01 --end 2025-11-30
|
|
8
|
+
"""
|
|
9
|
+
import sys
|
|
10
|
+
import os
|
|
11
|
+
import argparse
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
14
|
+
from _client import (
|
|
15
|
+
get_api_config, get_project_id,
|
|
16
|
+
api_get,
|
|
17
|
+
extract_data, print_json, truncate,
|
|
18
|
+
pad,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _fmt(v):
|
|
23
|
+
if v is None:
|
|
24
|
+
return "--"
|
|
25
|
+
try:
|
|
26
|
+
return f"{float(v):.2f}"
|
|
27
|
+
except (TypeError, ValueError):
|
|
28
|
+
return str(v)[:8]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _fmt_change(c):
|
|
32
|
+
if c is None:
|
|
33
|
+
return "--"
|
|
34
|
+
try:
|
|
35
|
+
f = float(c)
|
|
36
|
+
sign = "+" if f >= 0 else ""
|
|
37
|
+
return f"{sign}{f:.2f}"
|
|
38
|
+
except (TypeError, ValueError):
|
|
39
|
+
return str(c)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main():
|
|
43
|
+
parser = argparse.ArgumentParser(description="Per-prompt visibility overview")
|
|
44
|
+
parser.add_argument("--project-id", help="Project ID (or set GEO_PROJECT_ID env var)")
|
|
45
|
+
parser.add_argument("--prompt-id", required=True)
|
|
46
|
+
parser.add_argument("--start", help="Start date YYYY-MM-DD")
|
|
47
|
+
parser.add_argument("--end", help="End date YYYY-MM-DD")
|
|
48
|
+
parser.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
49
|
+
args = parser.parse_args()
|
|
50
|
+
|
|
51
|
+
key, base = get_api_config()
|
|
52
|
+
pid = get_project_id(args.project_id)
|
|
53
|
+
|
|
54
|
+
params = {}
|
|
55
|
+
if args.start:
|
|
56
|
+
params["start_date"] = args.start
|
|
57
|
+
if args.end:
|
|
58
|
+
params["end_date"] = args.end
|
|
59
|
+
|
|
60
|
+
result = api_get(
|
|
61
|
+
f"/api/projects/{pid}/analytics/prompts/{args.prompt_id}/overview",
|
|
62
|
+
key, base, params=params or None,
|
|
63
|
+
)
|
|
64
|
+
data = extract_data(result) or {}
|
|
65
|
+
|
|
66
|
+
if args.json:
|
|
67
|
+
print_json(data)
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
vs = data.get("visibility_score") or {}
|
|
71
|
+
ap = data.get("average_position") or {}
|
|
72
|
+
|
|
73
|
+
print(f"Prompt overview — {args.prompt_id}")
|
|
74
|
+
if data.get("prompt_text"):
|
|
75
|
+
print(f" text: {truncate(data['prompt_text'], 80)}")
|
|
76
|
+
print()
|
|
77
|
+
print("```")
|
|
78
|
+
print("┌────────────────────┬──────────────┬──────────────┬──────────────┐")
|
|
79
|
+
print("│ Metric │ Current │ Previous │ Change │")
|
|
80
|
+
print("├────────────────────┼──────────────┼──────────────┼──────────────┤")
|
|
81
|
+
print(f"│ {pad('Visibility Score', 18)} │ {_fmt(vs.get('current')):>12} │ {_fmt(vs.get('previous')):>12} │ {_fmt_change(vs.get('change')):>12} │")
|
|
82
|
+
print(f"│ {pad('Average Position', 18)} │ {_fmt(ap.get('current')):>12} │ {_fmt(ap.get('previous')):>12} │ {_fmt_change(ap.get('change')):>12} │")
|
|
83
|
+
print("└────────────────────┴──────────────┴──────────────┴──────────────┘")
|
|
84
|
+
print("```")
|
|
85
|
+
|
|
86
|
+
by_platform = data.get("by_platform") or data.get("platforms") or []
|
|
87
|
+
if by_platform:
|
|
88
|
+
print()
|
|
89
|
+
print("By platform:")
|
|
90
|
+
print("```")
|
|
91
|
+
print("┌────────────────────┬──────────┬──────────┐")
|
|
92
|
+
print("│ Platform │ Vis(%) │ AvgPos │")
|
|
93
|
+
print("├────────────────────┼──────────┼──────────┤")
|
|
94
|
+
for p in by_platform:
|
|
95
|
+
name = truncate(p.get("name") or p.get("platform") or p.get("code"), 18)
|
|
96
|
+
v = _fmt(p.get("visibility_score") or p.get("visibility"))
|
|
97
|
+
a = _fmt(p.get("average_position") or p.get("avg_position"))
|
|
98
|
+
print(f"│ {pad(name, 18)} │ {v:>8} │ {a:>8} │")
|
|
99
|
+
print("└────────────────────┴──────────┴──────────┘")
|
|
100
|
+
print("```")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
main()
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Topic-level AI visibility metrics.
|
|
3
|
+
|
|
4
|
+
Subcommands:
|
|
5
|
+
list — full Topic dimension (Visibility / SoV /
|
|
6
|
+
Avg Position + previous-period delta)
|
|
7
|
+
visibility — lightweight list (id + name + score)
|
|
8
|
+
used for content-creation topic picker
|
|
9
|
+
prompts --topic-id <id> — prompts under a topic (full metrics)
|
|
10
|
+
prompts-visibility --topic-id <id> — prompts under a topic (lightweight)
|
|
11
|
+
|
|
12
|
+
Time-window options on `list`:
|
|
13
|
+
--start <YYYY-MM-DD> --end <YYYY-MM-DD>
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
python3 scripts/get_topic_metrics.py list
|
|
17
|
+
python3 scripts/get_topic_metrics.py visibility # for picker UI
|
|
18
|
+
python3 scripts/get_topic_metrics.py prompts --topic-id <topic_id>
|
|
19
|
+
"""
|
|
20
|
+
import sys
|
|
21
|
+
import os
|
|
22
|
+
import argparse
|
|
23
|
+
|
|
24
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
25
|
+
from _client import (
|
|
26
|
+
get_api_config, get_project_id,
|
|
27
|
+
api_get,
|
|
28
|
+
extract_data, print_json, truncate,
|
|
29
|
+
pad,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _fmt(v):
|
|
34
|
+
if v is None:
|
|
35
|
+
return "--"
|
|
36
|
+
try:
|
|
37
|
+
return f"{float(v):.1f}"
|
|
38
|
+
except (TypeError, ValueError):
|
|
39
|
+
return str(v)[:6]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _fmt_change(c):
|
|
43
|
+
if c is None:
|
|
44
|
+
return "--"
|
|
45
|
+
try:
|
|
46
|
+
f = float(c)
|
|
47
|
+
sign = "+" if f >= 0 else ""
|
|
48
|
+
return f"{sign}{f:.1f}"
|
|
49
|
+
except (TypeError, ValueError):
|
|
50
|
+
return str(c)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def cmd_list(args, key, base, pid):
|
|
54
|
+
params = {}
|
|
55
|
+
if args.start:
|
|
56
|
+
params["start_date"] = args.start
|
|
57
|
+
if args.end:
|
|
58
|
+
params["end_date"] = args.end
|
|
59
|
+
result = api_get(f"/api/projects/{pid}/analytics/topics", key, base,
|
|
60
|
+
params=params or None)
|
|
61
|
+
data = extract_data(result)
|
|
62
|
+
items = data if isinstance(data, list) else (data or {}).get("topics", [])
|
|
63
|
+
if args.json:
|
|
64
|
+
print_json(items)
|
|
65
|
+
return
|
|
66
|
+
if not items:
|
|
67
|
+
print("No topic data.")
|
|
68
|
+
return
|
|
69
|
+
print(f"Topic Visibility ({len(items)})")
|
|
70
|
+
print()
|
|
71
|
+
print("```")
|
|
72
|
+
print("┌──────────────────────────────┬──────────┬──────────┬──────────┬──────────┐")
|
|
73
|
+
print("│ Topic │ Vis(%) │ SoV(%) │ AvgPos │ ΔVis │")
|
|
74
|
+
print("├──────────────────────────────┼──────────┼──────────┼──────────┼──────────┤")
|
|
75
|
+
for t in items:
|
|
76
|
+
name = truncate(t.get("name") or t.get("topic"), 28)
|
|
77
|
+
vis = _fmt(t.get("visibility_score") or t.get("visibility"))
|
|
78
|
+
sov = _fmt(t.get("share_of_voice") or t.get("sov"))
|
|
79
|
+
ap = _fmt(t.get("average_position") or t.get("avg_position"))
|
|
80
|
+
ch = _fmt_change(t.get("visibility_change") or t.get("change"))
|
|
81
|
+
print(f"│ {pad(name, 28)} │ {vis:>8} │ {sov:>8} │ {ap:>8} │ {ch:>8} │")
|
|
82
|
+
print("└──────────────────────────────┴──────────┴──────────┴──────────┴──────────┘")
|
|
83
|
+
print("```")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def cmd_visibility(args, key, base, pid):
|
|
87
|
+
result = api_get(f"/api/projects/{pid}/analytics/topics/visibility",
|
|
88
|
+
key, base)
|
|
89
|
+
data = extract_data(result)
|
|
90
|
+
items = data if isinstance(data, list) else (data or {}).get("topics", [])
|
|
91
|
+
if args.json:
|
|
92
|
+
print_json(items)
|
|
93
|
+
return
|
|
94
|
+
if not items:
|
|
95
|
+
print("No topics.")
|
|
96
|
+
return
|
|
97
|
+
print(f"Topics ({len(items)}) — sorted by visibility, last 30 days")
|
|
98
|
+
print()
|
|
99
|
+
print("```")
|
|
100
|
+
print("┌──────────────────────────────────────┬──────────────────────────────┬──────────┐")
|
|
101
|
+
print("│ Topic ID │ Name │ Vis(%) │")
|
|
102
|
+
print("├──────────────────────────────────────┼──────────────────────────────┼──────────┤")
|
|
103
|
+
for t in items:
|
|
104
|
+
tid = truncate(t.get("id") or t.get("topic_id"), 36)
|
|
105
|
+
name = truncate(t.get("name") or t.get("topic"), 28)
|
|
106
|
+
vis = _fmt(t.get("visibility_score") or t.get("score") or t.get("visibility"))
|
|
107
|
+
print(f"│ {pad(tid, 36)} │ {pad(name, 28)} │ {vis:>8} │")
|
|
108
|
+
print("└──────────────────────────────────────┴──────────────────────────────┴──────────┘")
|
|
109
|
+
print("```")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def cmd_prompts(args, key, base, pid):
|
|
113
|
+
result = api_get(
|
|
114
|
+
f"/api/projects/{pid}/analytics/topics/{args.topic_id}/prompts",
|
|
115
|
+
key, base,
|
|
116
|
+
)
|
|
117
|
+
data = extract_data(result)
|
|
118
|
+
items = data if isinstance(data, list) else (data or {}).get("prompts", [])
|
|
119
|
+
if args.json:
|
|
120
|
+
print_json(items)
|
|
121
|
+
return
|
|
122
|
+
if not items:
|
|
123
|
+
print(f"No prompts under topic {args.topic_id}.")
|
|
124
|
+
return
|
|
125
|
+
print(f"Prompt Visibility — topic {args.topic_id} ({len(items)})")
|
|
126
|
+
print()
|
|
127
|
+
print("```")
|
|
128
|
+
print("┌──────────────────────────────────────────┬──────────┬──────────┬──────────┐")
|
|
129
|
+
print("│ Prompt │ Vis(%) │ SoV(%) │ AvgPos │")
|
|
130
|
+
print("├──────────────────────────────────────────┼──────────┼──────────┼──────────┤")
|
|
131
|
+
for p in items:
|
|
132
|
+
text = truncate(p.get("text") or p.get("prompt") or p.get("name"), 40)
|
|
133
|
+
vis = _fmt(p.get("visibility_score") or p.get("visibility"))
|
|
134
|
+
sov = _fmt(p.get("share_of_voice") or p.get("sov"))
|
|
135
|
+
ap = _fmt(p.get("average_position") or p.get("avg_position"))
|
|
136
|
+
print(f"│ {pad(text, 40)} │ {vis:>8} │ {sov:>8} │ {ap:>8} │")
|
|
137
|
+
print("└──────────────────────────────────────────┴──────────┴──────────┴──────────┘")
|
|
138
|
+
print("```")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def cmd_prompts_visibility(args, key, base, pid):
|
|
142
|
+
result = api_get(
|
|
143
|
+
f"/api/projects/{pid}/analytics/topics/{args.topic_id}/prompts/visibility",
|
|
144
|
+
key, base,
|
|
145
|
+
)
|
|
146
|
+
data = extract_data(result)
|
|
147
|
+
items = data if isinstance(data, list) else (data or {}).get("prompts", [])
|
|
148
|
+
if args.json:
|
|
149
|
+
print_json(items)
|
|
150
|
+
return
|
|
151
|
+
if not items:
|
|
152
|
+
print(f"No prompts under topic {args.topic_id}.")
|
|
153
|
+
return
|
|
154
|
+
print(f"Prompts under topic {args.topic_id} — sorted by visibility")
|
|
155
|
+
print()
|
|
156
|
+
print("```")
|
|
157
|
+
print("┌──────────────────────────────────────┬──────────────────────────────┬──────────┐")
|
|
158
|
+
print("│ Prompt ID │ Text │ Vis(%) │")
|
|
159
|
+
print("├──────────────────────────────────────┼──────────────────────────────┼──────────┤")
|
|
160
|
+
for p in items:
|
|
161
|
+
pid_ = truncate(p.get("id") or p.get("prompt_id"), 36)
|
|
162
|
+
text = truncate(p.get("text") or p.get("prompt"), 28)
|
|
163
|
+
vis = _fmt(p.get("visibility_score") or p.get("score") or p.get("visibility"))
|
|
164
|
+
print(f"│ {pad(pid_, 36)} │ {pad(text, 28)} │ {vis:>8} │")
|
|
165
|
+
print("└──────────────────────────────────────┴──────────────────────────────┴──────────┘")
|
|
166
|
+
print("```")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def main():
|
|
170
|
+
parser = argparse.ArgumentParser(description="Topic-level AI visibility metrics")
|
|
171
|
+
parser.add_argument("--project-id", help="Project ID (or set GEO_PROJECT_ID env var)")
|
|
172
|
+
parser.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
173
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
174
|
+
|
|
175
|
+
p_l = sub.add_parser("list", help="Full Topic dimension metrics")
|
|
176
|
+
p_l.add_argument("--start", help="Start date YYYY-MM-DD")
|
|
177
|
+
p_l.add_argument("--end", help="End date YYYY-MM-DD")
|
|
178
|
+
|
|
179
|
+
sub.add_parser("visibility", help="Lightweight topic list for pickers")
|
|
180
|
+
|
|
181
|
+
p_p = sub.add_parser("prompts", help="Prompts under topic (full)")
|
|
182
|
+
p_p.add_argument("--topic-id", required=True)
|
|
183
|
+
|
|
184
|
+
p_pv = sub.add_parser("prompts-visibility", help="Prompts under topic (light)")
|
|
185
|
+
p_pv.add_argument("--topic-id", required=True)
|
|
186
|
+
|
|
187
|
+
args = parser.parse_args()
|
|
188
|
+
key, base = get_api_config()
|
|
189
|
+
pid = get_project_id(args.project_id)
|
|
190
|
+
|
|
191
|
+
handlers = {
|
|
192
|
+
"list": cmd_list,
|
|
193
|
+
"visibility": cmd_visibility,
|
|
194
|
+
"prompts": cmd_prompts,
|
|
195
|
+
"prompts-visibility": cmd_prompts_visibility,
|
|
196
|
+
}
|
|
197
|
+
handlers[args.command](args, key, base, pid)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
if __name__ == "__main__":
|
|
201
|
+
main()
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Single-metric visibility queries (granular, for AI-agent fine-grained Q&A).
|
|
3
|
+
|
|
4
|
+
Three subcommands map 1:1 to the analytics single-metric endpoints. Each
|
|
5
|
+
returns the metric value plus 30/14/7-day trend points and previous-period
|
|
6
|
+
delta (when supported by the API).
|
|
7
|
+
|
|
8
|
+
Subcommands:
|
|
9
|
+
score — Visibility Score
|
|
10
|
+
share-of-voice — Share of Voice
|
|
11
|
+
average-position — Average Position
|
|
12
|
+
|
|
13
|
+
Common options:
|
|
14
|
+
--start <YYYY-MM-DD> --end <YYYY-MM-DD> time window
|
|
15
|
+
--platform <code> openai | google_aio | perplexity | gemini
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
python3 scripts/get_visibility.py score
|
|
19
|
+
python3 scripts/get_visibility.py share-of-voice --platform openai
|
|
20
|
+
python3 scripts/get_visibility.py average-position --start 2025-11-01 --end 2025-11-30
|
|
21
|
+
"""
|
|
22
|
+
import sys
|
|
23
|
+
import os
|
|
24
|
+
import argparse
|
|
25
|
+
|
|
26
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
27
|
+
from _client import (
|
|
28
|
+
get_api_config, get_project_id,
|
|
29
|
+
api_get,
|
|
30
|
+
extract_data, print_json,
|
|
31
|
+
pad,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _fmt_metric(v):
|
|
36
|
+
if v is None:
|
|
37
|
+
return "--"
|
|
38
|
+
try:
|
|
39
|
+
f = float(v)
|
|
40
|
+
return f"{f:.2f}" if abs(f) < 1000 else f"{f:,.0f}"
|
|
41
|
+
except (TypeError, ValueError):
|
|
42
|
+
return str(v)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _fmt_change(c):
|
|
46
|
+
if c is None:
|
|
47
|
+
return "--"
|
|
48
|
+
try:
|
|
49
|
+
f = float(c)
|
|
50
|
+
sign = "+" if f >= 0 else ""
|
|
51
|
+
return f"{sign}{f:.2f}"
|
|
52
|
+
except (TypeError, ValueError):
|
|
53
|
+
return str(c)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _common_params(args):
|
|
57
|
+
p = {}
|
|
58
|
+
if args.start:
|
|
59
|
+
p["start_date"] = args.start
|
|
60
|
+
if args.end:
|
|
61
|
+
p["end_date"] = args.end
|
|
62
|
+
if args.platform:
|
|
63
|
+
p["platform"] = args.platform
|
|
64
|
+
return p or None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _print_metric(label, unit, data, suffix=""):
|
|
68
|
+
cur = data.get("current") if isinstance(data, dict) else None
|
|
69
|
+
if cur is None and isinstance(data, dict):
|
|
70
|
+
cur = data.get("value") or data.get(label.lower().replace(" ", "_"))
|
|
71
|
+
prev = data.get("previous") if isinstance(data, dict) else None
|
|
72
|
+
change = data.get("change") if isinstance(data, dict) else None
|
|
73
|
+
print(f"{label}")
|
|
74
|
+
print()
|
|
75
|
+
print("```")
|
|
76
|
+
print("┌────────────────────┬──────────────┐")
|
|
77
|
+
print("│ Field │ Value │")
|
|
78
|
+
print("├────────────────────┼──────────────┤")
|
|
79
|
+
print(f"│ {pad('current', 18)} │ {(_fmt_metric(cur) + unit):>12} │")
|
|
80
|
+
if prev is not None:
|
|
81
|
+
print(f"│ {pad('previous', 18)} │ {(_fmt_metric(prev) + unit):>12} │")
|
|
82
|
+
if change is not None:
|
|
83
|
+
print(f"│ {pad('change', 18)} │ {_fmt_change(change):>12} │")
|
|
84
|
+
print("└────────────────────┴──────────────┘")
|
|
85
|
+
print("```")
|
|
86
|
+
if suffix:
|
|
87
|
+
print()
|
|
88
|
+
print(suffix)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _show_trend(data, unit):
|
|
92
|
+
trend = data.get("trend") or data.get("series")
|
|
93
|
+
if isinstance(trend, list) and trend:
|
|
94
|
+
print()
|
|
95
|
+
print(f"Trend ({len(trend)} points):")
|
|
96
|
+
for pt in trend[-10:]:
|
|
97
|
+
d = pt.get("date") or pt.get("day") or ""
|
|
98
|
+
v = pt.get("value") or pt.get("score") or pt.get("metric")
|
|
99
|
+
print(f" {d} {_fmt_metric(v)}{unit}")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def cmd_score(args, key, base, pid):
|
|
103
|
+
result = api_get(
|
|
104
|
+
f"/api/projects/{pid}/analytics/visibility/score",
|
|
105
|
+
key, base, params=_common_params(args),
|
|
106
|
+
)
|
|
107
|
+
data = extract_data(result) or {}
|
|
108
|
+
if args.json:
|
|
109
|
+
print_json(data)
|
|
110
|
+
return
|
|
111
|
+
_print_metric("Visibility Score", "%", data)
|
|
112
|
+
_show_trend(data, "%")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def cmd_sov(args, key, base, pid):
|
|
116
|
+
result = api_get(
|
|
117
|
+
f"/api/projects/{pid}/analytics/visibility/share-of-voice",
|
|
118
|
+
key, base, params=_common_params(args),
|
|
119
|
+
)
|
|
120
|
+
data = extract_data(result) or {}
|
|
121
|
+
if args.json:
|
|
122
|
+
print_json(data)
|
|
123
|
+
return
|
|
124
|
+
_print_metric("Share of Voice", "%", data)
|
|
125
|
+
_show_trend(data, "%")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def cmd_avg(args, key, base, pid):
|
|
129
|
+
result = api_get(
|
|
130
|
+
f"/api/projects/{pid}/analytics/visibility/average-position",
|
|
131
|
+
key, base, params=_common_params(args),
|
|
132
|
+
)
|
|
133
|
+
data = extract_data(result) or {}
|
|
134
|
+
if args.json:
|
|
135
|
+
print_json(data)
|
|
136
|
+
return
|
|
137
|
+
_print_metric("Average Position", "", data)
|
|
138
|
+
_show_trend(data, "")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def main():
|
|
142
|
+
parser = argparse.ArgumentParser(description="Single-metric visibility queries")
|
|
143
|
+
parser.add_argument("--project-id", help="Project ID (or set GEO_PROJECT_ID env var)")
|
|
144
|
+
parser.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
145
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
146
|
+
|
|
147
|
+
for name in ("score", "share-of-voice", "average-position"):
|
|
148
|
+
p = sub.add_parser(name)
|
|
149
|
+
p.add_argument("--start", help="Start date YYYY-MM-DD")
|
|
150
|
+
p.add_argument("--end", help="End date YYYY-MM-DD")
|
|
151
|
+
p.add_argument("--platform", choices=["openai", "google_aio", "perplexity", "gemini"])
|
|
152
|
+
|
|
153
|
+
args = parser.parse_args()
|
|
154
|
+
key, base = get_api_config()
|
|
155
|
+
pid = get_project_id(args.project_id)
|
|
156
|
+
|
|
157
|
+
handlers = {
|
|
158
|
+
"score": cmd_score,
|
|
159
|
+
"share-of-voice": cmd_sov,
|
|
160
|
+
"average-position": cmd_avg,
|
|
161
|
+
}
|
|
162
|
+
handlers[args.command](args, key, base, pid)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
main()
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adgine/geo-wordpress
|
|
3
|
+
description: Connects a GEO project to a WordPress site (站点凭证管理 — site URL + username + application password), lists WP categories, publishes generated articles to WordPress (one-click or direct markdown), tracks publish history, and updates an existing WP post with the latest content version. Use when the user wants to publish GEO articles to WordPress (发布到 WordPress, push to WP, publish article), manage WP credentials (连接 WordPress, 绑定 WP, save WordPress credentials, application password), update an already-published post with the newest content (更新 WordPress 文章, sync to WP), check publish history (发布历史), or list WordPress categories.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GEO WordPress
|
|
7
|
+
|
|
8
|
+
End-to-end WordPress publishing for GEO-generated content. Three steps:
|
|
9
|
+
|
|
10
|
+
1. **Connect** your WordPress site (save URL + username + application password).
|
|
11
|
+
2. **Publish** an article — either by `content_id` from the GEO content
|
|
12
|
+
library, or directly with a title + markdown body.
|
|
13
|
+
3. **Update** an existing WP post when the GEO article is revised.
|
|
14
|
+
|
|
15
|
+
Use the **adgine-geo-content** skill to generate articles before publishing them.
|
|
16
|
+
|
|
17
|
+
## Output rules — IDs (apply to every reply)
|
|
18
|
+
|
|
19
|
+
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.
|
|
20
|
+
|
|
21
|
+
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.
|
|
22
|
+
- Index numbers restart from 1 in each new list — they are not stable across calls.
|
|
23
|
+
- 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.
|
|
24
|
+
|
|
25
|
+
2. **Single-item operations — prefer a human name over an ID.**
|
|
26
|
+
- ✅ *"Project **Poki vs Competitors** deleted."*
|
|
27
|
+
- ✅ *"Topic **Brand mentions in 2024** updated — name → 'Brand mentions 2025'."*
|
|
28
|
+
- ❌ *"Project `a4305b57-1c79-4cec-a17c-16eb1d959ea6` deleted."*
|
|
29
|
+
- 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.
|
|
30
|
+
|
|
31
|
+
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.
|
|
32
|
+
|
|
33
|
+
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.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Step 1: Make sure GEO_API_KEY is configured
|
|
38
|
+
|
|
39
|
+
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).
|
|
40
|
+
|
|
41
|
+
- ✅ Key already in `<skills-root>/.env` → proceed.
|
|
42
|
+
- ❌ 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.
|
|
43
|
+
|
|
44
|
+
> ⚠️ **IMPORTANT:** In all shell/exec commands, always reference the key as `$GEO_API_KEY` (the environment variable). Never hardcode the literal value.
|
|
45
|
+
|
|
46
|
+
## Project selection
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
export GEO_PROJECT_ID=<project-id>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Scripts
|
|
53
|
+
|
|
54
|
+
### 1) Credentials & categories
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
python3 scripts/manage_credentials.py status # connection state
|
|
58
|
+
python3 scripts/manage_credentials.py categories # live WP categories
|
|
59
|
+
python3 scripts/manage_credentials.py connect \
|
|
60
|
+
--site-url https://example.com \
|
|
61
|
+
--username admin \
|
|
62
|
+
--password "abcd efgh ijkl mnop"
|
|
63
|
+
python3 scripts/manage_credentials.py disconnect --yes # DESTRUCTIVE
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> **WordPress application password** must be created in WP Admin → Users →
|
|
67
|
+
> Profile → Application Passwords. Plain user passwords will **not** work.
|
|
68
|
+
|
|
69
|
+
### 2) Publish an article
|
|
70
|
+
|
|
71
|
+
First find publishable content:
|
|
72
|
+
```bash
|
|
73
|
+
python3 scripts/list_publishable.py
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then publish:
|
|
77
|
+
```bash
|
|
78
|
+
# Mode A: by GEO content ID
|
|
79
|
+
python3 scripts/publish.py --content-id <uuid> [--category-ids 2,5] [--status publish|draft]
|
|
80
|
+
|
|
81
|
+
# Mode B: raw title + markdown body
|
|
82
|
+
python3 scripts/publish.py --title "My Post" --content-body "# Hello..."
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 3) Manage existing publishes
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
python3 scripts/manage_publishes.py list # publish history
|
|
89
|
+
python3 scripts/manage_publishes.py update --record-id <id> # push latest content
|
|
90
|
+
python3 scripts/manage_publishes.py update --record-id <id> --status draft
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Output Format
|
|
94
|
+
|
|
95
|
+
ASCII tables only. Status vocabulary: `Connected` / `Disconnected` /
|
|
96
|
+
`publish` / `draft`. Width ≤80 chars max for tables with WP IDs / URLs.
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
┌────────────────────┬──────────────────────────────┐
|
|
100
|
+
│ Field │ Value │
|
|
101
|
+
├────────────────────┼──────────────────────────────┤
|
|
102
|
+
│ Status │ Connected │
|
|
103
|
+
│ site_url │ https://example.com │
|
|
104
|
+
│ username │ admin │
|
|
105
|
+
└────────────────────┴──────────────────────────────┘
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Workflow narrative
|
|
109
|
+
|
|
110
|
+
> 1. **Connect**: `manage_credentials.py connect ...` (one-time).
|
|
111
|
+
> 2. **Verify**: `manage_credentials.py status` returns `Connected`.
|
|
112
|
+
> 3. **Discover**: `manage_credentials.py categories` to know which IDs to assign.
|
|
113
|
+
> 4. **List candidates**: `list_publishable.py`.
|
|
114
|
+
> 5. **Publish**: `publish.py --content-id <uuid> --category-ids 2,5`.
|
|
115
|
+
> 6. **Iterate**: if the article is revised in GEO, run
|
|
116
|
+
> `manage_publishes.py update --record-id <id>` to push the new version
|
|
117
|
+
> to the same WP post.
|
|
118
|
+
|
|
119
|
+
## Related endpoints
|
|
120
|
+
|
|
121
|
+
| Method | Path |
|
|
122
|
+
|---|---|
|
|
123
|
+
| GET | `/api/projects/{id}/integrations/wordpress/credentials` |
|
|
124
|
+
| PUT | `/api/projects/{id}/integrations/wordpress/credentials` |
|
|
125
|
+
| DELETE | `/api/projects/{id}/integrations/wordpress/credentials` |
|
|
126
|
+
| GET | `/api/projects/{id}/integrations/wordpress/categories` |
|
|
127
|
+
| GET | `/api/projects/{id}/integrations/wordpress/publishable-content` |
|
|
128
|
+
| POST | `/api/projects/{id}/integrations/wordpress/publish` |
|
|
129
|
+
| GET | `/api/projects/{id}/integrations/wordpress/publishes` |
|
|
130
|
+
| PUT | `/api/projects/{id}/integrations/wordpress/publishes/{record_id}` |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Post-task recommendations
|
|
135
|
+
|
|
136
|
+
After WordPress operations, suggest the next content or analysis step:
|
|
137
|
+
|
|
138
|
+
| You just… | → use skill (agent-internal) |
|
|
139
|
+
|---|---|
|
|
140
|
+
| Connected WordPress | 生成 GEO 文章(现在可以直接发布) *(→ adgine-geo-content)*|
|
|
141
|
+
| Published an article | 检查已发布页面的 AI 优化健康度 *(→ adgine-geo-performance)*|
|
|
142
|
+
| Updated a published post | 运行引用测试,验证更新后 AI 引用是否提升 *(→ adgine-geo-citation)*|
|
|
143
|
+
| Listed publishable content | 选择一篇发布到 WordPress *(→ adgine-geo-wordpress)*|
|
|
144
|
+
| Checked publish history | 生成更多文章扩展内容库 *(→ adgine-geo-content)*|
|
|
145
|
+
| Disconnected WordPress | 管理其他集成(GA4 / Cloudflare) *(→ adgine-geo-integrations)*|
|
|
146
|
+
|
|
147
|
+
**⚠️ 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.
|
|
148
|
+
|
|
149
|
+
> 💡 **建议下一步:**
|
|
150
|
+
> 1. **[行动标题]** — *"[可直接发送给 AI 的自然语言提示词]"*
|
|
151
|
+
> 2. **[行动标题]** — *"[可直接发送给 AI 的自然语言提示词]"*
|