peec-setup — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited peec-setup (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Take a Peec AI project from empty (or broken) to operator-ready: correct competitors, full-funnel prompts, coherent taxonomy, GSC keyword mapping, forum-mined buyer language, and a categorized executable backlog the client can run for the next 2 weeks.
project_id (resolved via mcp__peec-ai__list_projects)target_country — ISO 3166-1 alpha-2 (DE, AT, CH, US, UK, ...). Default DE. Drives SERP/GSC filters and forum source selection.prompt_language — ISO 639-1 (de, en, fr, ...). Default = lowercase of target_country (DE → de). Drives the language Peec prompts are authored in.secondary_languages — list, default []. Used for multi-market projects (e.g. DE primary + EN secondary).offer_keywords (retainer, monthly, etc.), own_domainscope — full | audit | partial:<phase> | competitors_only | prompts_only | taxonomy_only (default: auto-detected from setup state, see Phase 0)Resolving language/country at start:
own_domain TLD (.de → DE/de, .at → AT/de, .ch → CH/de + ask de/fr, .com → ASK).Never silently default to EN/en when the project has no signal — this corrupts every downstream skill.
A setup report with: before/after counts, funnel distribution (e.g. 5/5/5/5), the single hero prompt to win first, the refresh timeline (24h for fresh data), a categorized P0/P1/P2 backlog, and any user-preference memories saved. No dashboards.
mcp__peec-ai__*)mcp__visiblyai__*) — optional, only for GSCget_google_connections)This skill owns the setup state file. See _shared/SETUP_STATE.md for the full schema and protocol.
<project>/growth_loop/setup_state.json at Phase 0 to decide the run mode (full | audit | partial | skip).phases_completed and a fresh snapshot.All other skills in this repo refuse to run without this file — never bootstrap a setup from inside another skill.
Always runs first. Cheap (single file read + at most one parallel Peec read in brownfield case). Determines whether the rest of the run is needed at all.
1. Read <project>/growth_loop/setup_state.json
2. If state file MISSING:
2a. Live-detect Peec content (parallel reads):
list_brands(project)
list_prompts(project, limit=5)
list_topics(project)
list_tags(project)
2b. If Peec is empty (≤2 brands AND ≤4 prompts AND ≤0 topics):
→ mode = full (greenfield — proceed to Phase 1)
2c. If Peec is populated (≥3 brands OR ≥5 prompts OR ≥1 topic):
→ mode = import (brownfield — see "Import mode" below)
3. If state file PRESENT, branch on `completed_at`:
< 30 days ago → mode = skip (show summary, ASK user before continuing)
30–90 days ago → mode = audit (live-diff snapshot, only redo drifted phases)
> 90 days ago → mode = full (warn: stale)
4. If user passed an explicit `scope`, that wins over auto-detection.
5. Print one line:
"Setup state: <found|missing|imported> · age: N days · mode: <full|import|audit|partial|skip>"import mode (brownfield) — runs entirely inside Phase 0Per _shared/SETUP_STATE.md §import mode, this mode reconstructs setup_state.json from live Peec data without re-doing discovery.
1. Show user one line:
"Detected existing Peec setup: <N> brands, <M> prompts, <T> topics, <G> tags."
2. ASK three things at once (single user turn):
- "Import this as the setup state, or run full setup from scratch? [import/full]"
- "Target country (ISO, e.g. DE)?"
- "Prompt language (ISO, e.g. de)?"
3. If user picks `import`:
a. Infer completed_at (NEVER default to now silently):
read created_at from list_brands + list_prompts;
completed_at = min(created_at across first 5 brands AND first 5 prompts)
If unavailable → list_chats(limit=1, sort=asc).timestamp
If still unavailable → ASK user one bucket question
("when did you set this up? [today/past month/past quarter/past year/older]")
and map to a date.
b. Build state object:
phases_completed = inferred from non-empty buckets (brands≥3 → +competitors; etc.)
snapshot = the counts just read
completed_at = inferred per (a) above
imported_at = now (UTC)
last_audit_at = now
hero_prompt_id = null
target_country, prompt_language = from user answers in step 2
notes = "imported from existing Peec project on <imported_at>;
original setup inferred at <completed_at>"
setup_version = "1.1"
c. **Persist immediately** — atomic write to <project>/growth_loop/setup_state.json
(write to .tmp, then rename). Do not wait for any other phase.
d. Print:
"State imported: <project>/growth_loop/setup_state.json (phases: X/7).
Inferred setup date: <YYYY-MM-DD> (~N days ago).
Run /peec-agent to pick the next move, or /peec-setup
partial:gsc_mapping to fill in skipped phases."
e. Exit Phase 0. Do NOT proceed to Phase 1 — import mode finishes here.
The user can now invoke any consumer skill; they will all read the freshly
written state. If they want missing phases (e.g. forum_mining never happened),
they explicitly call partial:<phase>.
4. If user picks `full`:
CONFIRM ONCE MORE: "Full setup will create new prompts/topics/tags alongside
the existing ones. Proceed? [yes/no]"
On yes → mode = full, proceed to Phase 1.
On no → exit cleanly.`skip` mode behaviour: show the existing snapshot (counts, phases, hero_prompt_id) and ask "Re-run anyway? [audit / partial:<phase> / full / no]". Do not auto-run.
`audit` mode behaviour: call list_brands / list_prompts / list_topics / list_tags and compare counts to snapshot. For each phase where drift > 20% (or a P0 red flag from Phase 1 reappears), re-run only that phase. Append last_audit_at on write.
`partial:<phase>` mode: jump straight to the named phase, skip everything else.
If mode == skip and user declines re-run, exit cleanly with a 3-line summary — no further phases.
Always runs in full, import, and audit modes (only skipped in skip mode). These three fields gate every downstream content decision — a wrong business_type corrupts every brief /peec-content-intel and every zone one-move /peec-cluster emits.
1. Read setup_state.json. If business_type + audience + page_type_taxonomy are all present
AND setup_version == "1.2" → skip this phase, continue to Phase 1.
2. If any are missing, ASK the user in ONE turn:
"Before I build prompts, I need three things (all in one message is fine):
(a) Business type — pick one:
• b2b-service (freelancer, agency, consulting — you sell hours or retainers)
• b2c-ecommerce (D2C shop — you sell products, typically Shopify/WooCommerce)
• b2b-saas (software product with subscriptions)
• info-product (courses, memberships, digital products)
• local-service (physical location, catchment-area business)
• marketplace (multi-seller platform)
(b) Audience — one sentence on your primary buyer.
Example: 'Shop-Owner DACH, 3–20 Mitarbeiter, Shopify, 500k–5M Umsatz, Pain: 3 SEO-Agenturen gewechselt.'
(c) Optional: known buyer pain-points (comma-separated, forum language welcome)."
3. Parse the answer. Build:
business_type = one of the six canonical values
audience.primary = the sentence
audience.buyer_personas = extracted nouns from (b) — e.g. ["Shop-Owner Shopify", "DACH"]
audience.pain_points = list from (c), else []
4. Generate page_type_taxonomy from the business-type matrix
(see _shared/SETUP_STATE.md §"Business-type → page-type matrix"):
b2b-service → ["pillar", "landing_page", "blog_post", "case_study", "comparison", "faq", "pricing"]
b2c-ecommerce → ["pdp", "collection", "pillar", "blog_post", "guide", "category_page", "faq"]
b2b-saas → ["landing_page", "integration", "use_case", "blog_post", "comparison", "docs", "pricing"]
info-product → ["sales_page", "webinar_lp", "blog_post", "case_study", "faq", "lead_magnet"]
local-service → ["local_landing", "landing_page", "case_study", "blog_post", "faq"]
marketplace → ["collection", "pdp", "category_page", "pillar", "blog_post"]
5. Add "business_type" and "audience" to phases_completed.
Set setup_version = "1.2".
Persist immediately (atomic write).
6. Print one line:
"Business: <business_type> · Audience: <audience.primary> · Page types: <count>"Why this matters downstream:
/peec-content-intel picks page_type per brief — if the brief says pdp but business_type=b2b-service, that's a rejected brief (caught by the taxonomy check)./peec-cluster names a page_type per zone's one-move. If zone competitors are all CATEGORY_PAGE but your taxonomy can't produce collection, the zone's one-move switches to outreach instead of content creation — automatically./peec-agent reads audience.pain_points when generating Awareness-stage content recommendations./peec-report attributes by page_type to learn which types actually moved visibility.Never guess `business_type`. A .de domain selling shoes is not b2b-service even if it looks like a typical German agency URL. Always ASK once; persist once.
Run in parallel:
mcp__peec-ai__list_projects
mcp__peec-ai__list_brands(project_id) # current competitors
mcp__peec-ai__list_prompts(project_id, limit=200)
mcp__peec-ai__list_topics(project_id)
mcp__peec-ai__list_tags(project_id)Red flags to call out:
For each losing prompt (own brand 0% visibility, competitors present):
mcp__peec-ai__list_chats(project_id, start_date, end_date, prompt_id=<losing_prompt>)
→ pick 1 chat per engine (chatgpt-scraper, perplexity-scraper, google-ai-overview-scraper)
mcp__peec-ai__get_chat(project_id, chat_id)
→ inspect messages[] for freelancer / consultant names
→ inspect sources[] for their domainsExtract: human names, domain names, sources the AI pulled. These are the real competitors LLMs recommend against you.
WebSearch("SEO Freelancer Deutschland <niche> 2026")
WebSearch("<niche> Freelancer Experte KI ChatGPT empfehlen")Cross-check against the domain report — any domain retrieving (get_domain_report) but not tracked as a brand is an invisible competitor:
mcp__peec-ai__get_domain_report(project_id, start_date, end_date, limit=25)
→ find domains with retrieved_percentage > 5% not yet in list_brandsBatch-call in parallel:
mcp__peec-ai__create_brand(
project_id,
name="<Human name or brand>",
domains=["their-domain.de"],
aliases=["Alternate Spelling"] # Umlaut ↔ ASCII variants, abbreviations
)Categories to include:
For solo freelancer / service-business projects, remove SaaS tool brands:
mcp__peec-ai__delete_brand(project_id, brand_id)Tool brands to remove: SEMrush, Ahrefs, Sistrix, Moz, Ryte, Yoast, Screaming Frog, SurferSEO, Frase, SE Ranking.
Deletion is soft. Also save a feedback memory noting "track humans only" so future sessions don't re-suggest these.
mcp__visiblyai__get_google_connections()
→ confirm domain has a gsc_property and (ideally) a GA4 pairingmcp__visiblyai__get_keywords(domain="example.com", limit=200, location="Germany")
# or for finer control:
mcp__visiblyai__query_search_console(dimension="query", days=28, country="deu", limit=500)Cluster keywords into:
Frequent pattern: domain ranks well for TOFU informational (blog traffic) but is invisible for commercial / transactional — those are exactly the queries Peec prompts should test.
For each top GSC keyword: does a Peec prompt exist that tests AI visibility for the same intent? If not, flag as "prompt gap".
Mine verbatim buyer pain from public forums → convert into Peec prompts that match real customer language (not sanitized marketing phrasing). These prompts also reveal what LLMs pull from UGC, and whether the brand surfaces in those answers.
German (priority for DACH):
r/de, r/Finanzen, r/kmu, r/selbststaendig, r/Unternehmer; niche: r/shopify, r/ecommerce, r/SEOt3n.de/forum) — DACH digital / business prosomr.com/de/forum) — marketing / SEO operator painGlobal / EN fallback:
r/SEO, r/localseo, r/ecommerce, r/shopify, r/smallbusiness, r/entrepreneurVideo / social UGC (via WebFetch):
get_actions)Run in parallel — different pain angles:
WebSearch("site:reddit.com <offer-keyword> <problem-word>")
# problem-words: "funktioniert nicht", "erfahrungen", "lohnt sich", "hilfe", "enttäuscht"
WebSearch("site:gutefrage.net <offer-keyword>")
WebSearch("site:t3n.de/forum <niche-keyword>")
WebSearch("site:omr.com <niche-keyword> frage")
WebSearch("<offer-keyword> erfahrungen forum")
WebSearch("<competitor-name> review reddit")Example for a DACH SEO-retainer project:
site:reddit.com SEO Freelancer erfahrungensite:gutefrage.net SEO Berater lohnt sich"Shopify SEO" "funktioniert nicht" forum"KI SEO" reddit erfahrungWebFetch(url, "Extract the original question verbatim, plus the 3 most upvoted answers.
Note frustrations, decision triggers, and brand / competitor mentions.")Signals to capture:
Rules:
When creating the prompt in Phase 7, also tag with `from-forum` (create once: create_tag(name="from-forum", color="slate")). Later you can filter reports to tag_id=from-forum and measure whether pain-point prompts outperform generic ones.
| Raw forum query (verbatim) | Peec prompt | Funnel | Tags |
|---|---|---|---|
| "Lohnt sich ein SEO-Freelancer für kleine Shopify-Shops überhaupt?" (Gutefrage) | Lohnt sich ein SEO-Freelancer für kleine Shopify-Shops überhaupt? | Consideration | shopify, e-commerce, from-forum |
| "Habe schon 3 Agenturen durch, keine Ergebnisse – was jetzt?" (r/selbststaendig) | Was tun, wenn drei SEO-Agenturen keine Ergebnisse geliefert haben? | Decision | retainer, coach, from-forum |
| "Wie viel SEO kann man selbst machen, bevor man jemanden holt?" (gutefrage) | Wie viel SEO können KMU-Betreiber selbst machen, bevor ein Berater sinnvoll ist? | Awareness | coach, from-forum |
| "SEO für Shopify mit ChatGPT – reicht das?" (r/shopify) | Reicht SEO für Shopify mit ChatGPT ohne zusätzlichen Berater aus? | Awareness | shopify, ai-seo, from-forum |
| "Bin ich 2026 noch nicht zu spät für SEO?" (r/de) | Ist es 2026 noch sinnvoll, mit SEO zu starten? | Awareness | from-forum |
Extracted pain points are also content topics — feed them to the client for blog posts / LinkedIn pulses / YouTube scripts. Answering the pain in owned content is the fastest way to win the corresponding Peec prompt in 6–12 weeks.
4 funnel stages:
| Stage | Intent | Typical shape | Example |
|---|---|---|---|
| Awareness | educate on category | "Was ist X?", "Wie funktioniert Y?", "Warum wird Z wichtiger?" | Was ist Neuro-SEO? |
| Consideration | compare options | "Beste X", "Vergleiche Y", "X oder Y – was lohnt sich?" | Beste SEO-Freelancer E-Commerce |
| Decision | ready to book | "Wer ist <brand>?", "Was kostet X?", "<service> gesucht" | Was kostet eine monatliche SEO-Retainer-Beratung? |
| Retention | trust / proof | "Fallstudien", "Erfahrungen mit <brand>", "Wer veröffentlicht X?" | Erfahrungen mit <brand> Neuro-SEO System |
Target 5 prompts per stage = 20 total. Below this, per-stage SoV tracking is too noisy.
A retainer-worthy prompt:
Identify ONE prompt that:
This is the hero prompt — track weekly.
Batch in parallel:
mcp__peec-ai__create_prompt(
project_id,
text="...",
country_code="DE",
topic_id=<funnel-stage-topic-id>
)Created prompts have topic_id but no tags — tagging happens in Phase 8.
Credit caution: Each prompt burns daily run credits on Peec TRIAL. For >20, create in waves and review coverage after 48h.
Create 4 topics:
mcp__peec-ai__create_topic(project_id, name="Awareness", country_code="DE")
mcp__peec-ai__create_topic(project_id, name="Consideration", country_code="DE")
mcp__peec-ai__create_topic(project_id, name="Decision", country_code="DE")
mcp__peec-ai__create_topic(project_id, name="Retention", country_code="DE")If the project had theme-based topics (AI, SEO): rebuild — move each prompt to its funnel topic via update_prompt, then delete_topic the obsolete ones.
Keep Peec's standard intent tags: branded, non-branded, informational, transactional.
Add theme tags reflecting the offer:
mcp__peec-ai__create_tag(project_id, name="retainer", color="orange")
mcp__peec-ai__create_tag(project_id, name="e-commerce", color="purple")
mcp__peec-ai__create_tag(project_id, name="shopify", color="green")
mcp__peec-ai__create_tag(project_id, name="ai-seo", color="cyan")
mcp__peec-ai__create_tag(project_id, name="neuro-seo", color="fuchsia") # client's proprietary term
mcp__peec-ai__create_tag(project_id, name="coach", color="teal")
mcp__peec-ai__create_tag(project_id, name="local", color="yellow")
mcp__peec-ai__create_tag(project_id, name="brand-<name>",color="rose")
mcp__peec-ai__create_tag(project_id, name="from-forum", color="slate") # Phase 5 traceabilitymcp__peec-ai__update_prompt(
project_id, prompt_id,
topic_id=<funnel stage>,
tag_ids=[
<intent>, # transactional OR informational
<branding>, # branded OR non-branded
<theme1>, <theme2>, ...
]
)Default mapping:
After every prompt has been moved:
mcp__peec-ai__delete_topic(project_id, old_topic_id)Wait ~24h after setup. Then:
mcp__peec-ai__get_brand_report(
project_id, start_date, end_date,
dimensions=["topic_id"],
filters=[{field: "brand_id", operator: "in", values: [own_brand_id]}]
)Usually exposes the weakest funnel stage — often Awareness if the category is client-invented, or Decision if brand discovery is poor.
mcp__peec-ai__get_brand_report(
project_id, start_date, end_date,
filters=[{field: "prompt_id", operator: "in", values: [<hero_prompt_id>]}],
dimensions=["model_id"]
)mcp__peec-ai__get_brand_report(
project_id, start_date, end_date,
filters=[{field: "tag_id", operator: "in", values: [<retainer_tag_id>]}],
dimensions=["brand_id"]
)mcp__peec-ai__get_actions(project_id, scope="overview", start_date, end_date) # top 3 rows
mcp__peec-ai__get_actions(project_id, scope="editorial", url_classification="ARTICLE", ...)
mcp__peec-ai__get_actions(project_id, scope="ugc", domain="youtube.com", ...)
# also try reddit.com, linkedin.comDeliver as concrete outreach list + content-format guidance from the actions' text column.
Before the deliverable summary, write the state file per _shared/SETUP_STATE.md:
1. Read <project>/growth_loop/setup_state.json (if present).
2. Merge:
phases_completed = union(old, phases actually run this session)
snapshot = fresh counts from list_brands/list_prompts/list_topics/list_tags
called moments ago in this phase
completed_at = keep old if present, else now (UTC ISO8601)
last_audit_at = now ONLY if this run was mode=audit
hero_prompt_id = the one selected in Phase 9
peec_project_id, domain = from session context
target_country = from input (resolved per "Resolving language/country" rules)
prompt_language = from input
secondary_languages = from input (default [])
setup_version = "1.1"
3. Write atomically: write to setup_state.json.tmp, then rename.
4. Print exactly one line in the run summary:
"State written: <project>/growth_loop/setup_state.json (phases: X/7)"If <project>/growth_loop/ does not exist, create it (this is the same directory the orchestrator and reporter use).
At the end of a full setup, present:
Every setup ends with a categorized task list the client / user can run in the next 2 weeks. 7 categories. Each task = one line, prefixed with priority (P0 / P1 / P2) and effort (S / M / L).
get_actions (scope=editorial): evergreen.media, OMR, t3n, niche fachportalget_actions recommendsget_chat → create_brand/schedule trigger for weekly auto-run of brand + domain reportsget_google_connections)Present as a sortable table, paste-ready for Notion / Linear:
| # | Priority | Effort | Category | Task | Owner | Due |
|---|----------|--------|----------|------|-------|-----|
| 1 | P0 | M | Content | Build "Neuro-SEO Retainer" landing page with pricing + FAQ schema | Antonio | 2026-05-10 |
| 2 | P0 | S | UGC | Answer top r/selbststaendig thread on "SEO-Freelancer erfahrungen" | Antonio | 2026-04-26 |
...Always ≥5 P0 tasks across ≥3 categories. The backlog is only useful if it's executable within 2 weeks.
These templates work for most DACH service-business projects. The skeletons stay German because that IS the buyer language being tested — translating to English would break the semantic value. Adapt domain-specific nouns.
| Goal | Tool |
|---|---|
| All projects | mcp__peec-ai__list_projects |
| Current competitors | mcp__peec-ai__list_brands |
| Prompts + tags + topics | mcp__peec-ai__list_prompts |
| Inspect an AI response | mcp__peec-ai__list_chats → get_chat |
| Find invisible competitors | mcp__peec-ai__get_domain_report |
| Gap URLs (competitor-only) | get_domain_report(filters: gap > 0) |
| Add competitor | mcp__peec-ai__create_brand |
| Remove competitor | mcp__peec-ai__delete_brand |
| Add prompt | mcp__peec-ai__create_prompt |
| Reassign topic / tags | mcp__peec-ai__update_prompt |
| Recommendations | mcp__peec-ai__get_actions (overview → owned / editorial / ugc) |
| Pull GSC keywords | mcp__visiblyai__get_keywords or query_search_console |
| Check GSC / GA4 | mcp__visiblyai__get_google_connections |
| Forum pain | WebSearch("site:reddit.com ..."), WebSearch("site:gutefrage.net ...") |
| Extract thread | WebFetch(url, "...") |
aliases on create_brand — names with Umlauts need ASCII aliases ("Stürkat" + "Stuerkat"), otherwise matching failsupdate_prompt first, then delete_topic~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.