@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,140 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""AI-powered bulk prompt generation for a GEO topic (async job).
|
|
3
|
+
|
|
4
|
+
Starts a generate-prompts job, polls until completed (~1–3 min), and prints
|
|
5
|
+
the generated prompts.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python3 scripts/generate_prompts.py --topic-id <id> [--project-id <id>] \
|
|
9
|
+
[--count 10] [--language "English (en-US)"] [--region US] \
|
|
10
|
+
[--platforms "openai,perplexity,google_aio"] \
|
|
11
|
+
[--instructions "Focus on enterprise buyers"] [--json]
|
|
12
|
+
"""
|
|
13
|
+
import sys
|
|
14
|
+
import os
|
|
15
|
+
import time
|
|
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,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
DEFAULT_PLATFORMS = ["openai", "google_aio", "perplexity"]
|
|
26
|
+
|
|
27
|
+
parser = argparse.ArgumentParser(description="AI-generate prompts for a GEO topic")
|
|
28
|
+
parser.add_argument("--topic-id", required=True, help="Topic ID to generate prompts for")
|
|
29
|
+
parser.add_argument("--project-id", help="Project ID (or set GEO_PROJECT_ID env var)")
|
|
30
|
+
parser.add_argument("--count", type=int, default=10,
|
|
31
|
+
help="Number of prompts to generate, 1–50 (default: 10)")
|
|
32
|
+
parser.add_argument("--language", default="English (en-US)",
|
|
33
|
+
help="Language for generated prompts (default: English (en-US))")
|
|
34
|
+
parser.add_argument("--region", default="US",
|
|
35
|
+
help="Target region code (default: US)")
|
|
36
|
+
parser.add_argument("--platforms",
|
|
37
|
+
help=f"Comma-separated platform IDs (default: {', '.join(DEFAULT_PLATFORMS)}). "
|
|
38
|
+
"Valid values: openai, google_aio, perplexity")
|
|
39
|
+
parser.add_argument("--instructions",
|
|
40
|
+
help="Additional instructions to guide prompt generation")
|
|
41
|
+
parser.add_argument("--json", action="store_true", help="Output raw JSON task result")
|
|
42
|
+
args = parser.parse_args()
|
|
43
|
+
|
|
44
|
+
key, base = get_api_config()
|
|
45
|
+
pid = get_project_id(args.project_id)
|
|
46
|
+
|
|
47
|
+
platforms = DEFAULT_PLATFORMS
|
|
48
|
+
if args.platforms:
|
|
49
|
+
platforms = [p.strip() for p in args.platforms.split(",") if p.strip()]
|
|
50
|
+
|
|
51
|
+
if not (1 <= args.count <= 50):
|
|
52
|
+
print("ERROR: --count must be between 1 and 50")
|
|
53
|
+
sys.exit(1)
|
|
54
|
+
|
|
55
|
+
print(f"Starting AI prompt generation for topic: {args.topic_id}")
|
|
56
|
+
print(f" Count : {args.count}")
|
|
57
|
+
print(f" Language : {args.language} | Region: {args.region}")
|
|
58
|
+
print(f" Platforms : {', '.join(platforms)}")
|
|
59
|
+
print()
|
|
60
|
+
|
|
61
|
+
body = {
|
|
62
|
+
"count": args.count,
|
|
63
|
+
"language": args.language,
|
|
64
|
+
"region": args.region,
|
|
65
|
+
"platforms": platforms,
|
|
66
|
+
}
|
|
67
|
+
if args.instructions:
|
|
68
|
+
body["additional_instructions"] = args.instructions
|
|
69
|
+
|
|
70
|
+
result = api_post(
|
|
71
|
+
f"/api/projects/{pid}/topics/{args.topic_id}/generate-prompts",
|
|
72
|
+
key, base, body
|
|
73
|
+
)
|
|
74
|
+
task_data = extract_data(result)
|
|
75
|
+
task_id = task_data.get("task_id") or task_data.get("id")
|
|
76
|
+
|
|
77
|
+
if not task_id:
|
|
78
|
+
print("ERROR: Generate-prompts endpoint did not return a task ID.")
|
|
79
|
+
print_json(task_data)
|
|
80
|
+
sys.exit(1)
|
|
81
|
+
|
|
82
|
+
print(f"Task ID: {task_id}")
|
|
83
|
+
print("Polling for completion (this typically takes 1–3 minutes)...")
|
|
84
|
+
|
|
85
|
+
# Poll the task status endpoint
|
|
86
|
+
frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
87
|
+
elapsed = 0
|
|
88
|
+
max_wait = 600
|
|
89
|
+
interval = 5
|
|
90
|
+
idx = 0
|
|
91
|
+
final_task = None
|
|
92
|
+
|
|
93
|
+
while elapsed < max_wait:
|
|
94
|
+
status_result = api_get(
|
|
95
|
+
f"/api/projects/{pid}/topics/{args.topic_id}/generate-prompts/{task_id}",
|
|
96
|
+
key, base
|
|
97
|
+
)
|
|
98
|
+
task = extract_data(status_result)
|
|
99
|
+
status = task.get("status", "")
|
|
100
|
+
phase = task.get("current_phase") or status
|
|
101
|
+
count_so_far = task.get("generated_count", "")
|
|
102
|
+
label = f"{phase} ({count_so_far} generated)" if count_so_far else phase
|
|
103
|
+
print(f"\r {frames[idx % len(frames)]} {label}...", end="", flush=True)
|
|
104
|
+
idx += 1
|
|
105
|
+
|
|
106
|
+
if status in ("completed", "failed", "done", "success", "error"):
|
|
107
|
+
print()
|
|
108
|
+
final_task = task
|
|
109
|
+
break
|
|
110
|
+
time.sleep(interval)
|
|
111
|
+
elapsed += interval
|
|
112
|
+
|
|
113
|
+
if final_task is None:
|
|
114
|
+
print()
|
|
115
|
+
print(f"WARNING: Task still running after {max_wait}s.")
|
|
116
|
+
print(f" Check status manually:")
|
|
117
|
+
print(f" GET /api/projects/{pid}/topics/{args.topic_id}/generate-prompts/{task_id}")
|
|
118
|
+
sys.exit(0)
|
|
119
|
+
|
|
120
|
+
if args.json:
|
|
121
|
+
print_json(final_task)
|
|
122
|
+
sys.exit(0)
|
|
123
|
+
|
|
124
|
+
if final_task.get("status") == "failed":
|
|
125
|
+
print(f"ERROR: Prompt generation failed — {final_task.get('error', 'unknown error')}")
|
|
126
|
+
sys.exit(1)
|
|
127
|
+
|
|
128
|
+
prompts = final_task.get("prompts") or []
|
|
129
|
+
print(f"\nGenerated {len(prompts)} prompts for topic {args.topic_id}:")
|
|
130
|
+
print()
|
|
131
|
+
for i, p in enumerate(prompts, 1):
|
|
132
|
+
content = p.get("content", "")
|
|
133
|
+
prompt_id = p.get("id", "")
|
|
134
|
+
print(f" {i:>2}. [{prompt_id[:36]}]")
|
|
135
|
+
print(f" {content}")
|
|
136
|
+
print()
|
|
137
|
+
|
|
138
|
+
print(f"These prompts are ready for:")
|
|
139
|
+
print(f" - Citation testing : use the geo-citation skill → python3 scripts/create_tests.py --prompt-ids <id1,id2,...>")
|
|
140
|
+
print(f" - Article outlines : use the geo-content skill → python3 scripts/generate_outline.py --topic-id {args.topic_id} --prompt-ids <id1,id2,...>")
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""List, create, update, or delete prompts within a GEO topic.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python3 scripts/manage_prompts.py list --topic-id <tid> [--project-id <id>] [--json]
|
|
6
|
+
python3 scripts/manage_prompts.py list-all [--project-id <id>] [--json]
|
|
7
|
+
python3 scripts/manage_prompts.py create --topic-id <tid> --content "What is...?" \
|
|
8
|
+
[--language "English (en-US)"] [--region US] \
|
|
9
|
+
[--platforms "ChatGPT,Perplexity,Google AI Overviews"]
|
|
10
|
+
python3 scripts/manage_prompts.py update --topic-id <tid> --prompt-id <pid> --content "..."
|
|
11
|
+
python3 scripts/manage_prompts.py delete --topic-id <tid> --prompt-id <pid>
|
|
12
|
+
"""
|
|
13
|
+
import sys
|
|
14
|
+
import os
|
|
15
|
+
import argparse
|
|
16
|
+
|
|
17
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
18
|
+
from _client import (
|
|
19
|
+
get_api_config, get_project_id,
|
|
20
|
+
api_get, api_post, api_put, api_delete,
|
|
21
|
+
extract_data, print_json,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
parser = argparse.ArgumentParser(description="Manage GEO prompts")
|
|
25
|
+
parser.add_argument("action", choices=["list", "list-all", "get", "create", "update", "delete"])
|
|
26
|
+
parser.add_argument("--project-id", help="Project ID (or set GEO_PROJECT_ID env var)")
|
|
27
|
+
parser.add_argument("--topic-id", help="Topic ID")
|
|
28
|
+
parser.add_argument("--prompt-id", help="Prompt ID (required for update/delete)")
|
|
29
|
+
parser.add_argument("--content", help="Prompt text content")
|
|
30
|
+
parser.add_argument("--language", default="English (en-US)", help="Language (default: English (en-US))")
|
|
31
|
+
parser.add_argument("--region", default="US", help="Region code (default: US)")
|
|
32
|
+
parser.add_argument("--platforms", help="Comma-separated platform IDs (e.g. openai,perplexity,google_aio)")
|
|
33
|
+
parser.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
34
|
+
args = parser.parse_args()
|
|
35
|
+
|
|
36
|
+
key, base = get_api_config()
|
|
37
|
+
pid = get_project_id(args.project_id)
|
|
38
|
+
|
|
39
|
+
def _fmt_platforms(platforms_list):
|
|
40
|
+
if not platforms_list:
|
|
41
|
+
return "—"
|
|
42
|
+
return ", ".join(platforms_list)[:60]
|
|
43
|
+
|
|
44
|
+
# ── LIST (by topic) ───────────────────────────────────────────────────────────
|
|
45
|
+
if args.action == "list":
|
|
46
|
+
if not args.topic_id:
|
|
47
|
+
print("ERROR: --topic-id is required for list")
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
result = api_get(f"/api/projects/{pid}/topics/{args.topic_id}/prompts", key, base)
|
|
50
|
+
data = extract_data(result)
|
|
51
|
+
items = data if isinstance(data, list) else data.get("items") or data.get("prompts") or []
|
|
52
|
+
if args.json:
|
|
53
|
+
print_json(data)
|
|
54
|
+
sys.exit(0)
|
|
55
|
+
print(f"Prompts for topic {args.topic_id} ({len(items)} found)")
|
|
56
|
+
print()
|
|
57
|
+
for p in items:
|
|
58
|
+
print(f" ID: {p.get('id', '')[:36]}")
|
|
59
|
+
content = p.get("content", "")
|
|
60
|
+
print(f" {content[:120]}{'...' if len(content) > 120 else ''}")
|
|
61
|
+
print(f" Platforms: {_fmt_platforms(p.get('platforms'))}")
|
|
62
|
+
print()
|
|
63
|
+
|
|
64
|
+
# ── LIST-ALL (project-wide) ───────────────────────────────────────────────────
|
|
65
|
+
elif args.action == "list-all":
|
|
66
|
+
result = api_get(f"/api/projects/{pid}/prompts", key, base)
|
|
67
|
+
data = extract_data(result)
|
|
68
|
+
items = data if isinstance(data, list) else data.get("items") or data.get("prompts") or []
|
|
69
|
+
if args.json:
|
|
70
|
+
print_json(data)
|
|
71
|
+
sys.exit(0)
|
|
72
|
+
total = data.get("total", len(items)) if isinstance(data, dict) else len(items)
|
|
73
|
+
print(f"All prompts in project {pid} ({total} found)")
|
|
74
|
+
print()
|
|
75
|
+
for p in items:
|
|
76
|
+
content = p.get("content", "")
|
|
77
|
+
print(f" [{p.get('id', '')[:36]}] {content[:100]}{'...' if len(content) > 100 else ''}")
|
|
78
|
+
|
|
79
|
+
# ── GET (detail) ──────────────────────────────────────────────────────────────
|
|
80
|
+
elif args.action == "get":
|
|
81
|
+
if not args.topic_id or not args.prompt_id:
|
|
82
|
+
print("ERROR: --topic-id and --prompt-id are required for get")
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
result = api_get(
|
|
85
|
+
f"/api/projects/{pid}/topics/{args.topic_id}/prompts/{args.prompt_id}",
|
|
86
|
+
key, base,
|
|
87
|
+
)
|
|
88
|
+
prompt = extract_data(result) or {}
|
|
89
|
+
if args.json:
|
|
90
|
+
print_json(prompt)
|
|
91
|
+
sys.exit(0)
|
|
92
|
+
print(f"Prompt detail: {args.prompt_id}")
|
|
93
|
+
print()
|
|
94
|
+
print(f" Content : {prompt.get('content', '')}")
|
|
95
|
+
print(f" Language : {prompt.get('language', '--')}")
|
|
96
|
+
print(f" Region : {prompt.get('region', '--')}")
|
|
97
|
+
print(f" Platforms : {_fmt_platforms(prompt.get('platforms'))}")
|
|
98
|
+
print(f" Created : {prompt.get('created_at', '--')}")
|
|
99
|
+
|
|
100
|
+
# ── CREATE ────────────────────────────────────────────────────────────────────
|
|
101
|
+
elif args.action == "create":
|
|
102
|
+
if not args.topic_id:
|
|
103
|
+
print("ERROR: --topic-id is required for create")
|
|
104
|
+
sys.exit(1)
|
|
105
|
+
if not args.content:
|
|
106
|
+
print("ERROR: --content is required for create")
|
|
107
|
+
sys.exit(1)
|
|
108
|
+
body = {
|
|
109
|
+
"content": args.content,
|
|
110
|
+
"language": args.language,
|
|
111
|
+
"region": args.region,
|
|
112
|
+
}
|
|
113
|
+
if args.platforms:
|
|
114
|
+
body["platforms"] = [p.strip() for p in args.platforms.split(",") if p.strip()]
|
|
115
|
+
result = api_post(f"/api/projects/{pid}/topics/{args.topic_id}/prompts", key, base, body)
|
|
116
|
+
prompt = extract_data(result)
|
|
117
|
+
if args.json:
|
|
118
|
+
print_json(prompt)
|
|
119
|
+
sys.exit(0)
|
|
120
|
+
print(f"✓ Created prompt")
|
|
121
|
+
print(f" ID : {prompt.get('id')}")
|
|
122
|
+
print(f" Content : {prompt.get('content', '')[:100]}")
|
|
123
|
+
|
|
124
|
+
# ── UPDATE ────────────────────────────────────────────────────────────────────
|
|
125
|
+
elif args.action == "update":
|
|
126
|
+
if not args.topic_id or not args.prompt_id:
|
|
127
|
+
print("ERROR: --topic-id and --prompt-id are required for update")
|
|
128
|
+
sys.exit(1)
|
|
129
|
+
if not args.content:
|
|
130
|
+
print("ERROR: --content is required for update")
|
|
131
|
+
sys.exit(1)
|
|
132
|
+
body = {"content": args.content}
|
|
133
|
+
result = api_put(
|
|
134
|
+
f"/api/projects/{pid}/topics/{args.topic_id}/prompts/{args.prompt_id}",
|
|
135
|
+
key, base, body
|
|
136
|
+
)
|
|
137
|
+
prompt = extract_data(result)
|
|
138
|
+
if args.json:
|
|
139
|
+
print_json(prompt)
|
|
140
|
+
sys.exit(0)
|
|
141
|
+
print(f"✓ Updated prompt {args.prompt_id}")
|
|
142
|
+
print(f" Content : {prompt.get('content', '')[:100]}")
|
|
143
|
+
|
|
144
|
+
# ── DELETE ────────────────────────────────────────────────────────────────────
|
|
145
|
+
elif args.action == "delete":
|
|
146
|
+
if not args.topic_id or not args.prompt_id:
|
|
147
|
+
print("ERROR: --topic-id and --prompt-id are required for delete")
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
api_delete(
|
|
150
|
+
f"/api/projects/{pid}/topics/{args.topic_id}/prompts/{args.prompt_id}",
|
|
151
|
+
key, base
|
|
152
|
+
)
|
|
153
|
+
print(f"✓ Deleted prompt {args.prompt_id}")
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""List, create, batch-create, update, or delete GEO topics.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python3 scripts/manage_topics.py list [--project-id <id>] [--page 1] [--limit 20] [--json]
|
|
6
|
+
python3 scripts/manage_topics.py create --name "Product Reviews" [--description "..."]
|
|
7
|
+
python3 scripts/manage_topics.py batch --names "Topic A,Topic B,Topic C"
|
|
8
|
+
python3 scripts/manage_topics.py update --topic-id <id> [--name "..."] [--description "..."]
|
|
9
|
+
python3 scripts/manage_topics.py delete --topic-id <id>
|
|
10
|
+
"""
|
|
11
|
+
import sys
|
|
12
|
+
import os
|
|
13
|
+
import argparse
|
|
14
|
+
|
|
15
|
+
sys.path.insert(0, os.path.dirname(__file__))
|
|
16
|
+
from _client import (
|
|
17
|
+
get_api_config, get_project_id,
|
|
18
|
+
api_get, api_post, api_put, api_delete,
|
|
19
|
+
extract_data, print_json,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
parser = argparse.ArgumentParser(description="Manage GEO topics")
|
|
23
|
+
parser.add_argument("action", choices=["list", "get", "create", "batch", "update", "delete"])
|
|
24
|
+
parser.add_argument("--project-id", help="Project ID (or set GEO_PROJECT_ID env var)")
|
|
25
|
+
parser.add_argument("--topic-id", help="Topic ID (required for update/delete)")
|
|
26
|
+
parser.add_argument("--name", help="Topic name")
|
|
27
|
+
parser.add_argument("--description", help="Topic description")
|
|
28
|
+
parser.add_argument("--names", help="Comma-separated names for batch create")
|
|
29
|
+
parser.add_argument("--page", type=int, default=1, help="Page number (list only)")
|
|
30
|
+
parser.add_argument("--limit", type=int, default=20, help="Results per page (list only)")
|
|
31
|
+
parser.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
32
|
+
args = parser.parse_args()
|
|
33
|
+
|
|
34
|
+
key, base = get_api_config()
|
|
35
|
+
pid = get_project_id(args.project_id)
|
|
36
|
+
|
|
37
|
+
# ── LIST ─────────────────────────────────────────────────────────────────────
|
|
38
|
+
if args.action == "list":
|
|
39
|
+
result = api_get(
|
|
40
|
+
f"/api/projects/{pid}/topics",
|
|
41
|
+
key, base,
|
|
42
|
+
params={"page": args.page, "limit": args.limit},
|
|
43
|
+
)
|
|
44
|
+
topics = extract_data(result)
|
|
45
|
+
items = topics if isinstance(topics, list) else topics.get("items") or topics.get("topics") or []
|
|
46
|
+
|
|
47
|
+
if args.json:
|
|
48
|
+
print_json(topics)
|
|
49
|
+
sys.exit(0)
|
|
50
|
+
|
|
51
|
+
total = topics.get("total", len(items)) if isinstance(topics, dict) else len(items)
|
|
52
|
+
print(f"Topics ({len(items)} of {total}) | project: {pid}")
|
|
53
|
+
print()
|
|
54
|
+
if not items:
|
|
55
|
+
print(" No topics found. Create one with: python3 scripts/manage_topics.py create --name \"...\"")
|
|
56
|
+
else:
|
|
57
|
+
print(f" {'ID':<38} {'Name':<30} {'Prompts':>7} Description")
|
|
58
|
+
print(f" {'-'*38} {'-'*30} {'-'*7} {'-'*30}")
|
|
59
|
+
for t in items:
|
|
60
|
+
tid = t.get("id", "")[:36]
|
|
61
|
+
name = (t.get("name") or "")[:30]
|
|
62
|
+
count = t.get("prompt_count", 0)
|
|
63
|
+
desc = (t.get("description") or "")[:40]
|
|
64
|
+
print(f" {tid:<38} {name:<30} {count:>7} {desc}")
|
|
65
|
+
|
|
66
|
+
# ── GET (detail) ─────────────────────────────────────────────────────────────
|
|
67
|
+
elif args.action == "get":
|
|
68
|
+
if not args.topic_id:
|
|
69
|
+
print("ERROR: --topic-id is required for get")
|
|
70
|
+
sys.exit(1)
|
|
71
|
+
result = api_get(f"/api/projects/{pid}/topics/{args.topic_id}", key, base)
|
|
72
|
+
topic = extract_data(result) or {}
|
|
73
|
+
if args.json:
|
|
74
|
+
print_json(topic)
|
|
75
|
+
sys.exit(0)
|
|
76
|
+
print(f"Topic detail: {args.topic_id}")
|
|
77
|
+
print()
|
|
78
|
+
print(f" Name : {topic.get('name', '')}")
|
|
79
|
+
print(f" Description : {topic.get('description', '') or '--'}")
|
|
80
|
+
print(f" Prompts : {topic.get('prompt_count', 0)}")
|
|
81
|
+
print(f" Created : {topic.get('created_at', '--')}")
|
|
82
|
+
|
|
83
|
+
# ── CREATE ────────────────────────────────────────────────────────────────────
|
|
84
|
+
elif args.action == "create":
|
|
85
|
+
if not args.name:
|
|
86
|
+
print("ERROR: --name is required for create")
|
|
87
|
+
sys.exit(1)
|
|
88
|
+
body = {"name": args.name}
|
|
89
|
+
if args.description:
|
|
90
|
+
body["description"] = args.description
|
|
91
|
+
result = api_post(f"/api/projects/{pid}/topics", key, base, body)
|
|
92
|
+
topic = extract_data(result)
|
|
93
|
+
if args.json:
|
|
94
|
+
print_json(topic)
|
|
95
|
+
sys.exit(0)
|
|
96
|
+
print(f"✓ Created topic: {topic.get('name')}")
|
|
97
|
+
print(f" ID: {topic.get('id')}")
|
|
98
|
+
|
|
99
|
+
# ── BATCH CREATE ──────────────────────────────────────────────────────────────
|
|
100
|
+
elif args.action == "batch":
|
|
101
|
+
if not args.names:
|
|
102
|
+
print("ERROR: --names is required (comma-separated list of topic names)")
|
|
103
|
+
sys.exit(1)
|
|
104
|
+
name_list = [n.strip() for n in args.names.split(",") if n.strip()]
|
|
105
|
+
if not name_list:
|
|
106
|
+
print("ERROR: --names must contain at least one topic name")
|
|
107
|
+
sys.exit(1)
|
|
108
|
+
body = {"topics": [{"name": n} for n in name_list]}
|
|
109
|
+
result = api_post(f"/api/projects/{pid}/topics/batch", key, base, body)
|
|
110
|
+
created = extract_data(result)
|
|
111
|
+
if args.json:
|
|
112
|
+
print_json(created)
|
|
113
|
+
sys.exit(0)
|
|
114
|
+
items = created if isinstance(created, list) else created.get("topics") or created.get("items") or []
|
|
115
|
+
print(f"✓ Created {len(items)} topics:")
|
|
116
|
+
for t in items:
|
|
117
|
+
print(f" {t.get('id', '')[:36]} {t.get('name', '')}")
|
|
118
|
+
|
|
119
|
+
# ── UPDATE ────────────────────────────────────────────────────────────────────
|
|
120
|
+
elif args.action == "update":
|
|
121
|
+
if not args.topic_id:
|
|
122
|
+
print("ERROR: --topic-id is required for update")
|
|
123
|
+
sys.exit(1)
|
|
124
|
+
body = {}
|
|
125
|
+
if args.name:
|
|
126
|
+
body["name"] = args.name
|
|
127
|
+
if args.description:
|
|
128
|
+
body["description"] = args.description
|
|
129
|
+
if not body:
|
|
130
|
+
print("ERROR: Provide at least one of --name or --description to update")
|
|
131
|
+
sys.exit(1)
|
|
132
|
+
result = api_put(f"/api/projects/{pid}/topics/{args.topic_id}", key, base, body)
|
|
133
|
+
topic = extract_data(result)
|
|
134
|
+
if args.json:
|
|
135
|
+
print_json(topic)
|
|
136
|
+
sys.exit(0)
|
|
137
|
+
print(f"✓ Updated topic: {topic.get('name')}")
|
|
138
|
+
|
|
139
|
+
# ── DELETE ────────────────────────────────────────────────────────────────────
|
|
140
|
+
elif args.action == "delete":
|
|
141
|
+
if not args.topic_id:
|
|
142
|
+
print("ERROR: --topic-id is required for delete")
|
|
143
|
+
sys.exit(1)
|
|
144
|
+
api_delete(f"/api/projects/{pid}/topics/{args.topic_id}", key, base)
|
|
145
|
+
print(f"✓ Deleted topic {args.topic_id}")
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adgine/geo-visibility
|
|
3
|
+
description: Reads and analyzes already-collected AI visibility data for a GEO
|
|
4
|
+
project — Visibility Score, Share of Voice, Average Position, competitor matrix,
|
|
5
|
+
historical AI executions, full AI responses, brand mentions, cited URLs.
|
|
6
|
+
Use when the user wants to READ analytics results: AI visibility / 可见性得分 /
|
|
7
|
+
Visibility Score / 声量份额 / Share of Voice / 平均排名 / 竞品对比 / 平台矩阵 /
|
|
8
|
+
AI 回答历史 / prompt 历史测试 / AI 具体怎么回复的 / 品牌提及 / 引用链接.
|
|
9
|
+
NOT for running new citation tests (向 AI 平台发请求测试是否引用) — use
|
|
10
|
+
adgine-geo-citation to actively submit prompts and collect new results.
|
|
11
|
+
NOT for lightweight dashboard summary — use adgine-geo-dashboard instead.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# GEO Visibility (Analytics)
|
|
15
|
+
|
|
16
|
+
Deep analytics over `/api/projects/{id}/analytics/*` — 11 endpoints covering
|
|
17
|
+
the AI visibility funnel from brand-level single metrics down to individual
|
|
18
|
+
prompt executions.
|
|
19
|
+
|
|
20
|
+
> Lightweight dashboard summary lives in **adgine-geo-dashboard**.
|
|
21
|
+
> Prompt CRUD / start-analysis actions live in **adgine-geo-topics**.
|
|
22
|
+
|
|
23
|
+
## 触发条件
|
|
24
|
+
|
|
25
|
+
当用户说出以下意图时使用本 skill:
|
|
26
|
+
- “可见性得分” / “Visibility Score” / “我的可见度”
|
|
27
|
+
- “声量份额” / “Share of Voice” / “平均排名” / “Average Position”
|
|
28
|
+
- “竞品对比” / “平台矩阵” / “competitor matrix”
|
|
29
|
+
- “AI 回答历史” / “AI 具体怎么回复的” / “prompt 历史测试结果”
|
|
30
|
+
- “品牌提及” / “引用链接”(查看已有数据)
|
|
31
|
+
|
|
32
|
+
**⛔ 以下意图不属于本 skill:**
|
|
33
|
+
- “跑一次引用测试” / “测试 AI 是否引用”(发起新测试)→ **adgine-geo-citation**
|
|
34
|
+
- “项目总览” / “Dashboard” / “近七天趋势” → **adgine-geo-dashboard**
|
|
35
|
+
- “创建提示词” / “管理主题” → **adgine-geo-topics**
|
|
36
|
+
|
|
37
|
+
## Output rules — IDs (apply to every reply)
|
|
38
|
+
|
|
39
|
+
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.
|
|
40
|
+
|
|
41
|
+
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.
|
|
42
|
+
- Index numbers restart from 1 in each new list — they are not stable across calls.
|
|
43
|
+
- 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.
|
|
44
|
+
|
|
45
|
+
2. **Single-item operations — prefer a human name over an ID.**
|
|
46
|
+
- ✅ *"Project **Poki vs Competitors** deleted."*
|
|
47
|
+
- ✅ *"Topic **Brand mentions in 2024** updated — name → 'Brand mentions 2025'."*
|
|
48
|
+
- ❌ *"Project `a4305b57-1c79-4cec-a17c-16eb1d959ea6` deleted."*
|
|
49
|
+
- 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.
|
|
50
|
+
|
|
51
|
+
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.
|
|
52
|
+
|
|
53
|
+
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.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Step 1: Make sure GEO_API_KEY is configured
|
|
58
|
+
|
|
59
|
+
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).
|
|
60
|
+
|
|
61
|
+
- ✅ Key already in `<skills-root>/.env` → proceed.
|
|
62
|
+
- ❌ 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.
|
|
63
|
+
|
|
64
|
+
> ⚠️ **IMPORTANT:** In all shell/exec commands, always reference the key as `$GEO_API_KEY` (the environment variable). Never hardcode the literal value.
|
|
65
|
+
|
|
66
|
+
## Project selection
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
export GEO_PROJECT_ID=<project-id>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Scripts
|
|
73
|
+
|
|
74
|
+
### Brand-level single metrics
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
python3 scripts/get_visibility.py score
|
|
78
|
+
python3 scripts/get_visibility.py share-of-voice
|
|
79
|
+
python3 scripts/get_visibility.py average-position
|
|
80
|
+
# common opts: --platform openai|google_aio|perplexity|gemini --start ... --end ...
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Competitor × platform matrix
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
python3 scripts/get_matrix.py # default: visibility metric
|
|
87
|
+
python3 scripts/get_matrix.py --metric sov
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Topic-level metrics
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
python3 scripts/get_topic_metrics.py list # full Topic dimension
|
|
94
|
+
python3 scripts/get_topic_metrics.py visibility # lightweight picker list
|
|
95
|
+
python3 scripts/get_topic_metrics.py prompts --topic-id <id> # prompts under topic
|
|
96
|
+
python3 scripts/get_topic_metrics.py prompts-visibility --topic-id <id>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Per-prompt overview
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
python3 scripts/get_prompt_metrics.py --prompt-id <prompt_id>
|
|
103
|
+
# adds: previous-period delta + per-platform breakdown
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Prompt execution history & detail
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
python3 scripts/get_execution.py list --prompt-id <prompt_id>
|
|
110
|
+
python3 scripts/get_execution.py list --prompt-id <pid> --platform openai
|
|
111
|
+
python3 scripts/get_execution.py get --prompt-id <pid> --execution-id <eid>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Output Format
|
|
115
|
+
|
|
116
|
+
ASCII tables only inside fenced code blocks. Numeric formatting: 1 or 2
|
|
117
|
+
decimals depending on metric scale; "%" suffix for percentage metrics;
|
|
118
|
+
"--" for null. Change-from-previous-period is shown with explicit sign
|
|
119
|
+
("+1.5" / "-0.3").
|
|
120
|
+
|
|
121
|
+
## Metric vocabulary
|
|
122
|
+
|
|
123
|
+
| Metric | Unit | Typical range |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| Visibility Score | % | 0–100 |
|
|
126
|
+
| Share of Voice | % | 0–100 |
|
|
127
|
+
| Average Position | rank | 1+ (lower is better) |
|
|
128
|
+
|
|
129
|
+
## Platform codes
|
|
130
|
+
|
|
131
|
+
`openai` · `google_aio` · `perplexity` · `gemini`
|
|
132
|
+
|
|
133
|
+
## Related endpoints
|
|
134
|
+
|
|
135
|
+
| Method | Path |
|
|
136
|
+
|---|---|
|
|
137
|
+
| GET | `/api/projects/{id}/analytics/visibility/score` |
|
|
138
|
+
| GET | `/api/projects/{id}/analytics/visibility/share-of-voice` |
|
|
139
|
+
| GET | `/api/projects/{id}/analytics/visibility/average-position` |
|
|
140
|
+
| GET | `/api/projects/{id}/analytics/platforms/matrix` |
|
|
141
|
+
| GET | `/api/projects/{id}/analytics/topics` |
|
|
142
|
+
| GET | `/api/projects/{id}/analytics/topics/visibility` |
|
|
143
|
+
| GET | `/api/projects/{id}/analytics/topics/{topic_id}/prompts` |
|
|
144
|
+
| GET | `/api/projects/{id}/analytics/topics/{topic_id}/prompts/visibility` |
|
|
145
|
+
| GET | `/api/projects/{id}/analytics/prompts/{prompt_id}/overview` |
|
|
146
|
+
| GET | `/api/projects/{id}/analytics/prompts/{prompt_id}/executions` |
|
|
147
|
+
| GET | `/api/projects/{id}/analytics/prompts/{prompt_id}/executions/{execution_id}` |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Post-task recommendations
|
|
152
|
+
|
|
153
|
+
After visibility analysis, suggest actions to improve or act on the results:
|
|
154
|
+
|
|
155
|
+
| What you saw | → use skill (agent-internal) |
|
|
156
|
+
|---|---|
|
|
157
|
+
| Low Visibility Score | 运行新引用测试,诊断哪些平台未引用 *(→ adgine-geo-citation)*|
|
|
158
|
+
| Low Share of Voice | 针对弱项话题生成更多 GEO 内容 *(→ adgine-geo-content)*|
|
|
159
|
+
| Competitor outperforming | 优化品牌画像,强化差异化定位 *(→ adgine-geo-brand)*|
|
|
160
|
+
| Specific prompt performing well | 基于该 prompt 生成扩展文章 *(→ adgine-geo-content)*|
|
|
161
|
+
| Specific prompt performing poorly | 优化或替换该 prompt *(→ adgine-geo-topics)*|
|
|
162
|
+
| Execution history reviewed | 查看对应页面的 AI 爬虫访问数据 *(→ adgine-geo-aiagent)*|
|
|
163
|
+
|
|
164
|
+
**⚠️ 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.
|
|
165
|
+
|
|
166
|
+
> 💡 **建议下一步:**
|
|
167
|
+
> 1. **[行动标题]** — *"[可直接发送给 AI 的自然语言提示词]"*
|
|
168
|
+
> 2. **[行动标题]** — *"[可直接发送给 AI 的自然语言提示词]"*
|