@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.
Files changed (115) hide show
  1. package/README.md +93 -0
  2. package/bin/adgine-geo.js +224 -0
  3. package/package.json +38 -0
  4. package/skills/.env.example +11 -0
  5. package/skills/README.md +247 -0
  6. package/skills/SKILL.md +117 -0
  7. package/skills/VERSION +1 -0
  8. package/skills/adgine-geo-aiagent/SKILL.md +200 -0
  9. package/skills/adgine-geo-aiagent/scripts/_client.py +341 -0
  10. package/skills/adgine-geo-aiagent/scripts/bot_traffic.py +278 -0
  11. package/skills/adgine-geo-aiagent/scripts/human_traffic.py +357 -0
  12. package/skills/adgine-geo-aiagent/scripts/page_analytics.py +317 -0
  13. package/skills/adgine-geo-aiagent/scripts/page_detail.py +273 -0
  14. package/skills/adgine-geo-analytics/API.md +89 -0
  15. package/skills/adgine-geo-analytics/SKILL.md +191 -0
  16. package/skills/adgine-geo-analytics/scripts/_client.py +341 -0
  17. package/skills/adgine-geo-analytics/scripts/get_overview.py +129 -0
  18. package/skills/adgine-geo-billing/SKILL.md +118 -0
  19. package/skills/adgine-geo-billing/scripts/_client.py +341 -0
  20. package/skills/adgine-geo-billing/scripts/get_credits.py +51 -0
  21. package/skills/adgine-geo-billing/scripts/get_credits_pricing.py +52 -0
  22. package/skills/adgine-geo-billing/scripts/get_subscription.py +48 -0
  23. package/skills/adgine-geo-billing/scripts/list_plans.py +66 -0
  24. package/skills/adgine-geo-brand/SKILL.md +193 -0
  25. package/skills/adgine-geo-brand/WORKFLOW.md +66 -0
  26. package/skills/adgine-geo-brand/scripts/_client.py +341 -0
  27. package/skills/adgine-geo-brand/scripts/generate_brand.py +95 -0
  28. package/skills/adgine-geo-brand/scripts/get_brand.py +67 -0
  29. package/skills/adgine-geo-brand/scripts/list_jobs.py +153 -0
  30. package/skills/adgine-geo-brand/scripts/update_brand.py +61 -0
  31. package/skills/adgine-geo-citation/SKILL.md +220 -0
  32. package/skills/adgine-geo-citation/scripts/_client.py +341 -0
  33. package/skills/adgine-geo-citation/scripts/create_tests.py +62 -0
  34. package/skills/adgine-geo-citation/scripts/get_aggregate.py +108 -0
  35. package/skills/adgine-geo-citation/scripts/get_results.py +179 -0
  36. package/skills/adgine-geo-content/SKILL.md +224 -0
  37. package/skills/adgine-geo-content/WORKFLOW.md +100 -0
  38. package/skills/adgine-geo-content/scripts/_client.py +341 -0
  39. package/skills/adgine-geo-content/scripts/generate_article.py +108 -0
  40. package/skills/adgine-geo-content/scripts/generate_outline.py +113 -0
  41. package/skills/adgine-geo-content/scripts/generate_titles.py +51 -0
  42. package/skills/adgine-geo-content/scripts/list_content.py +65 -0
  43. package/skills/adgine-geo-content/scripts/manage_content.py +90 -0
  44. package/skills/adgine-geo-content/scripts/manage_jobs.py +184 -0
  45. package/skills/adgine-geo-dashboard/SKILL.md +181 -0
  46. package/skills/adgine-geo-dashboard/scripts/_client.py +341 -0
  47. package/skills/adgine-geo-dashboard/scripts/check_integrations.py +139 -0
  48. package/skills/adgine-geo-dashboard/scripts/get_overview.py +155 -0
  49. package/skills/adgine-geo-dashboard/scripts/get_visibility_overview.py +93 -0
  50. package/skills/adgine-geo-docs/SKILL.md +60 -0
  51. package/skills/adgine-geo-domains/SKILL.md +223 -0
  52. package/skills/adgine-geo-domains/scripts/_client.py +341 -0
  53. package/skills/adgine-geo-domains/scripts/get_domain.py +75 -0
  54. package/skills/adgine-geo-domains/scripts/list_domains.py +66 -0
  55. package/skills/adgine-geo-domains/scripts/search_domains.py +85 -0
  56. package/skills/adgine-geo-integrations/SKILL.md +188 -0
  57. package/skills/adgine-geo-integrations/scripts/_client.py +341 -0
  58. package/skills/adgine-geo-integrations/scripts/cloudflare_connect.py +155 -0
  59. package/skills/adgine-geo-integrations/scripts/cloudflare_worker.py +212 -0
  60. package/skills/adgine-geo-integrations/scripts/ga4_data.py +192 -0
  61. package/skills/adgine-geo-integrations/scripts/ga4_setup.py +120 -0
  62. package/skills/adgine-geo-opportunities/SKILL.md +162 -0
  63. package/skills/adgine-geo-opportunities/scripts/_client.py +341 -0
  64. package/skills/adgine-geo-opportunities/scripts/get_opportunity.py +90 -0
  65. package/skills/adgine-geo-opportunities/scripts/list_opportunities.py +92 -0
  66. package/skills/adgine-geo-performance/SKILL.md +177 -0
  67. package/skills/adgine-geo-performance/scripts/_client.py +341 -0
  68. package/skills/adgine-geo-performance/scripts/analyze_page.py +100 -0
  69. package/skills/adgine-geo-projects/SKILL.md +279 -0
  70. package/skills/adgine-geo-projects/scripts/_client.py +341 -0
  71. package/skills/adgine-geo-projects/scripts/check_auth.py +45 -0
  72. package/skills/adgine-geo-projects/scripts/list_projects.py +47 -0
  73. package/skills/adgine-geo-projects/scripts/manage_competitors.py +122 -0
  74. package/skills/adgine-geo-projects/scripts/manage_project.py +88 -0
  75. package/skills/adgine-geo-saas/SKILL.md +144 -0
  76. package/skills/adgine-geo-saas/scripts/_client.py +341 -0
  77. package/skills/adgine-geo-saas/scripts/check_domain.py +43 -0
  78. package/skills/adgine-geo-saas/scripts/create_website.py +56 -0
  79. package/skills/adgine-geo-saas/scripts/get_task.py +94 -0
  80. package/skills/adgine-geo-site-audit/README.md +281 -0
  81. package/skills/adgine-geo-site-audit/SKILL.md +449 -0
  82. package/skills/adgine-geo-site-audit/requirements.txt +5 -0
  83. package/skills/adgine-geo-site-audit/scripts/_client.py +341 -0
  84. package/skills/adgine-geo-site-audit/scripts/_geo_notice.py +55 -0
  85. package/skills/adgine-geo-site-audit/scripts/export_skill_package.py +197 -0
  86. package/skills/adgine-geo-site-audit/scripts/geo_collect.py +2443 -0
  87. package/skills/adgine-geo-site-audit/scripts/geo_score.py +490 -0
  88. package/skills/adgine-geo-site-audit/scripts/geo_timing.py +399 -0
  89. package/skills/adgine-geo-site-audit/scripts/geo_visibility.py +718 -0
  90. package/skills/adgine-geo-site-audit/scripts/render_report_pdf.py +1502 -0
  91. package/skills/adgine-geo-topics/SKILL.md +232 -0
  92. package/skills/adgine-geo-topics/scripts/_client.py +341 -0
  93. package/skills/adgine-geo-topics/scripts/generate_prompts.py +140 -0
  94. package/skills/adgine-geo-topics/scripts/manage_prompts.py +153 -0
  95. package/skills/adgine-geo-topics/scripts/manage_topics.py +145 -0
  96. package/skills/adgine-geo-visibility/SKILL.md +168 -0
  97. package/skills/adgine-geo-visibility/scripts/_client.py +341 -0
  98. package/skills/adgine-geo-visibility/scripts/get_execution.py +151 -0
  99. package/skills/adgine-geo-visibility/scripts/get_matrix.py +111 -0
  100. package/skills/adgine-geo-visibility/scripts/get_prompt_metrics.py +104 -0
  101. package/skills/adgine-geo-visibility/scripts/get_topic_metrics.py +201 -0
  102. package/skills/adgine-geo-visibility/scripts/get_visibility.py +166 -0
  103. package/skills/adgine-geo-wordpress/SKILL.md +151 -0
  104. package/skills/adgine-geo-wordpress/scripts/_client.py +341 -0
  105. package/skills/adgine-geo-wordpress/scripts/list_publishable.py +62 -0
  106. package/skills/adgine-geo-wordpress/scripts/manage_credentials.py +139 -0
  107. package/skills/adgine-geo-wordpress/scripts/manage_publishes.py +108 -0
  108. package/skills/adgine-geo-wordpress/scripts/publish.py +79 -0
  109. package/skills/scripts/check_version.py +290 -0
  110. package/skills/setup.py +248 -0
  111. package/src/config.js +126 -0
  112. package/src/doctor.js +137 -0
  113. package/src/install.js +90 -0
  114. package/src/manifest.js +99 -0
  115. 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 的自然语言提示词]"*