{"id":"dda5a07c-40ec-4ec4-a4b1-aff2552c21cf","shortId":"nLHGA8","kind":"skill","title":"blog-taxonomy","tagline":"Extract, suggest, and sync tags and categories for blog posts across all major CMS platforms. Supports WordPress REST API, Shopify GraphQL, Ghost Content API, Strapi REST/GraphQL, and Sanity GROQ. Generates tag suggestions from content analysis (keyword frequency, heading extract","description":"# Blog Taxonomy\n\nManage tags, categories, and topic clusters across CMS platforms.\n\n## Commands\n\n| Command | Purpose |\n|---------|---------|\n| `/blog taxonomy suggest <file>` | Extract candidate tags and categories from content |\n| `/blog taxonomy sync <cms>` | Push taxonomy to CMS via authenticated API |\n| `/blog taxonomy audit [directory]` | Check for thin tags, orphan tags, taxonomy bloat |\n\n## Tag Suggestion Workflow\n\n### Step 1: Parse Content Structure\n\nRead the target file and extract:\n- All H2 and H3 headings (primary topic signals)\n- Bold and italic phrases (emphasis signals)\n- Existing frontmatter tags/categories if present\n\n### Step 2: Frequency Analysis\n\nScan the body text for high-frequency phrases:\n- 1-word terms: minimum 4 occurrences (excluding stop words)\n- 2-word phrases: minimum 3 occurrences\n- 3-word phrases: minimum 2 occurrences\n\nExclude common non-tag words: articles, prepositions, conjunctions, pronouns.\n\n### Step 3: Semantic Grouping\n\nGroup related candidates into clusters:\n- Merge singular/plural variants (keep the more common form)\n- Merge hyphenated and non-hyphenated forms\n- Group synonyms under the highest-frequency term\n\n### Step 4: Deduplicate and Rank\n\n- Fuzzy match on slugified names (Levenshtein distance <= 2)\n- Score each candidate: `(frequency * 2) + (heading_presence * 5) + (emphasis * 1)`\n- Return top 5-10 ranked suggestions\n\n### Output Format\n\n```\n## Tag Suggestions: [Post Title]\n\n| Rank | Tag | Score | Source |\n|------|-----|-------|--------|\n| 1 | content-marketing | 18 | H2 + 6 mentions |\n| 2 | seo-strategy | 14 | H3 + 4 mentions |\n| 3 | keyword-research | 11 | 5 mentions + bold |\n\n### Suggested Categories\n- Primary: [best-fit category]\n- Secondary: [optional second category]\n```\n\n## CMS Adapters\n\n### Adapter Overview\n\n| CMS | API Type | Auth Method | Tags Model |\n|-----|----------|-------------|------------|\n| WordPress | REST | Application Passwords (base64) | First-class entities with IDs |\n| Shopify | GraphQL (Admin API) | Admin API access token | String array on Article |\n| Ghost | REST (Admin API) | API key with JWT signing | First-class entities |\n| Strapi | REST or GraphQL | API token (Bearer) | User-defined content type |\n| Sanity | GROQ / Mutations | Project token (Bearer) | Document type |\n\n### WordPress Adapter\n\n**List tags**:\n```\nGET {CMS_URL}/wp-json/wp/v2/tags?per_page=100&search={keyword}\nAuthorization: Basic {base64(username:app_password)}\n```\n\n**Create tag**:\n```\nPOST {CMS_URL}/wp-json/wp/v2/tags\nBody: {\"name\": \"Tag Name\", \"slug\": \"tag-name\", \"description\": \"Optional\"}\n```\n\n**List categories** (hierarchical, supports parent field):\n```\nGET {CMS_URL}/wp-json/wp/v2/categories?per_page=100\n```\n\n**Create category**:\n```\nPOST {CMS_URL}/wp-json/wp/v2/categories\nBody: {\"name\": \"Category\", \"slug\": \"category\", \"parent\": 0}\n```\n\n**Assign tags to post**:\n```\nPOST {CMS_URL}/wp-json/wp/v2/posts/{id}\nBody: {\"tags\": [1, 2, 3], \"categories\": [4]}\n```\n\nPagination: follow `X-WP-TotalPages` header for full listing.\n\n### Shopify Adapter\n\nTags on Shopify are string arrays on the Article object, not first-class entities.\n\n**Update article tags** (GraphQL Admin API):\n```graphql\nmutation {\n  articleUpdate(id: \"gid://shopify/Article/123\", article: {\n    tags: [\"tag-one\", \"tag-two\", \"tag-three\"]\n  }) {\n    article { id tags }\n    userErrors { field message }\n  }\n}\n```\n\n**List all tags in use** (GraphQL):\n```graphql\n{\n  articles(first: 250) {\n    edges {\n      node { id title tags }\n    }\n  }\n}\n```\n\nAuth header: `X-Shopify-Access-Token: {token}`\n\nNote: REST API marked legacy Oct 2024. GraphQL required for new apps since Apr 2025.\n\n### Ghost Adapter\n\n**List tags**:\n```\nGET {CMS_URL}/ghost/api/admin/tags/?limit=all\nAuthorization: Ghost {jwt_token}\n```\n\n**Create tag**:\n```\nPOST {CMS_URL}/ghost/api/admin/tags/\nBody: {\"tags\": [{\"name\": \"Tag Name\", \"slug\": \"tag-name\"}]}\n```\n\nJWT generation: sign with admin API key (id:secret format), iat = now, exp = 5 min,\naudience = `/admin/`.\n\n### Strapi Adapter\n\nEndpoint auto-generated from content types. Typical setup:\n\n```\nGET {CMS_URL}/api/tags?pagination[pageSize]=100\nPOST {CMS_URL}/api/tags\nBody: {\"data\": {\"name\": \"Tag Name\", \"slug\": \"tag-name\"}}\nAuthorization: Bearer {api_token}\n```\n\nStrapi v4+ uses the `data` wrapper. Check your content type schema for field names.\n\n### Sanity Adapter\n\n**Query tags** (GROQ):\n```\n*[_type == \"tag\"] { _id, name, slug }\n```\n\n**Create tag** (Mutations API):\n```\nPOST https://{project_id}.api.sanity.io/v2024-01-01/data/mutate/{dataset}\nBody: {\"mutations\": [{\"create\": {\"_type\": \"tag\", \"name\": \"Tag\", \"slug\": {\"current\": \"tag\"}}}]}\nAuthorization: Bearer {token}\n```\n\n## Taxonomy Audit Workflow\n\n### Step 1: Inventory\n\nScan all posts in the target directory (or fetch from CMS). Build a map:\n- tag_name -> [list of post files/IDs using this tag]\n- category_name -> [list of post files/IDs]\n\n### Step 2: Health Checks\n\n| Check | Threshold | Action |\n|-------|-----------|--------|\n| Thin tag archives | < 5 posts per tag | Recommend noindex or merge |\n| Orphan tags | 0 posts | Recommend deletion |\n| Tag bloat | > 50 total tags | Recommend consolidation |\n| Category depth | > 3 levels | Recommend flattening |\n| Uncategorized posts | No category assigned | Assign to appropriate category |\n| Duplicate slugs | Same slug, different name | Merge into canonical version |\n\n### Step 3: Recommendations\n\nGroup findings by priority:\n- **Critical**: orphan tags creating empty archive pages (crawl waste)\n- **High**: thin tags with < 3 posts (poor user experience, weak SEO signal)\n- **Medium**: tag bloat over 50 (diluted taxonomy, harder to navigate)\n- **Low**: naming inconsistencies (mixed case, hyphen vs space)\n\n### Output Format\n\n```\n## Taxonomy Audit: [Site/Directory]\n\n**Total tags**: [n] | **Total categories**: [n]\n**Healthy**: [n] | **Thin**: [n] | **Orphan**: [n]\n\n### Critical Issues\n- [orphan tags list]\n\n### Recommendations\n1. Merge [tag-a] and [tag-b] (same topic, [n] combined posts)\n2. Delete orphan tags: [list]\n3. Add noindex to tag archives with < 5 posts\n```\n\n## Site-Wide Guidelines\n\n- Aim for 5-10 main categories per site (broad topics)\n- Tags should have at least 5 posts before creating an archive page\n- Use consistent slug format: lowercase, hyphen-separated\n- Every post needs exactly 1 primary category\n- Tags per post: 3-8 recommended, never exceed 15\n\n## Environment Variables\n\n| Variable | Purpose | Example |\n|----------|---------|---------|\n| CMS_TYPE | Platform identifier | wordpress, shopify, ghost, strapi, sanity |\n| CMS_URL | Base URL of the CMS | https://example.com |\n| CMS_API_KEY | Authentication credential | Application password, API token, or key |\n\nThese must be set in the shell environment. Never store credentials in files or\ncommit them to version control. The skill reads them via `$CMS_TYPE`, `$CMS_URL`,\nand `$CMS_API_KEY` at runtime.\n\n## Error Handling\n\n- **Missing environment variables**: If CMS_TYPE, CMS_URL, or CMS_API_KEY is unset, report which variable is missing and provide the expected format\n- **Invalid credentials**: If the CMS API returns 401/403, report \"Authentication failed - check CMS_API_KEY\" and do not retry\n- **Connection timeouts**: If the CMS endpoint is unreachable after 10 seconds, report the timeout and suggest checking CMS_URL\n- **Duplicate tag slugs**: If a tag already exists on the CMS, skip creation and note \"Tag already exists: [name]\"\n- **Rate limits**: If the CMS API returns 429, wait and retry once. Report if the limit persists\n- **Unsupported CMS**: If CMS_TYPE is not one of the 5 supported platforms, list the valid options and exit","tags":["blog","taxonomy","claude","agricidaniel","agent-skills","ai-citations","ai-content","ai-marketing","ai-marketing-hub","blog-writing","claude-code","claude-code-skill"],"capabilities":["skill","source-agricidaniel","skill-blog-taxonomy","topic-agent-skills","topic-ai-citations","topic-ai-content","topic-ai-marketing","topic-ai-marketing-hub","topic-blog","topic-blog-writing","topic-claude-code","topic-claude-code-skill","topic-claude-plugin","topic-claude-skill","topic-content-creation"],"categories":["claude-blog"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/AgriciDaniel/claude-blog/blog-taxonomy","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add AgriciDaniel/claude-blog","source_repo":"https://github.com/AgriciDaniel/claude-blog","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 753 github stars · SKILL.md body (7,609 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:53:31.615Z","embedding":null,"createdAt":"2026-05-18T12:54:54.624Z","updatedAt":"2026-05-18T18:53:31.615Z","lastSeenAt":"2026-05-18T18:53:31.615Z","tsv":"'-10':224,833 '-8':871 '/admin':554 '/api/tags':569,576 '/blog':57,67,77 '/ghost/api/admin/tags':516,528 '/v2024-01-01/data/mutate/':623 '/wp-json/wp/v2/categories':383,392 '/wp-json/wp/v2/posts':407 '/wp-json/wp/v2/tags':346,363 '0':399,693 '1':93,135,220,237,411,642,798,864 '10':997 '100':349,386,572 '11':257 '14':249 '15':875 '18':241 '2':123,144,154,210,215,245,412,674,812 '2024':500 '2025':508 '250':480 '3':148,150,167,253,413,706,730,749,817,870 '4':139,199,251,415 '401/403':976 '429':1033 '5':218,223,258,551,683,824,832,845,1053 '50':699,761 '6':243 'access':300,491 'across':14,51 'action':679 'adapt':273,274,340,427,510,556,605 'add':818 'admin':296,298,308,447,542 'aim':830 'alreadi':1013,1023 'analysi':38,125 'api':22,27,76,277,297,299,309,310,323,448,496,543,588,617,899,905,939,955,974,982,1031 'api.sanity.io':622 'api.sanity.io/v2024-01-01/data/mutate/':621 'app':356,505 'applic':285,903 'appropri':717 'apr':507 'archiv':682,741,822,850 'array':303,433 'articl':162,305,436,444,454,465,478 'articleupd':451 'assign':400,714,715 'audienc':553 'audit':79,639,778 'auth':279,486 'authent':75,901,978 'author':352,519,586,635 'auto':559 'auto-gener':558 'b':806 'base':892 'base64':287,354 'basic':353 'bearer':325,336,587,636 'best':265 'best-fit':264 'bloat':88,698,759 'blog':2,12,43 'blog-taxonomi':1 'bodi':128,364,393,409,529,577,625 'bold':111,260 'broad':838 'build':655 'candid':61,172,213 'canon':727 'case':771 'categori':10,47,64,262,267,271,375,388,395,397,414,667,704,713,718,784,835,866 'check':81,596,676,677,980,1004 'class':290,317,441 'cluster':50,174 'cms':17,52,73,272,276,344,361,381,390,405,514,526,567,574,654,881,890,896,898,933,935,938,949,951,954,973,981,992,1005,1017,1030,1044,1046 'combin':810 'command':54,55 'commit':923 'common':157,181 'conjunct':164 'connect':988 'consist':853 'consolid':703 'content':26,37,66,95,239,329,562,598 'content-market':238 'control':927 'crawl':743 'creat':358,387,523,614,627,739,848 'creation':1019 'credenti':902,919,970 'critic':736,792 'current':633 'data':578,594 'dataset':624 'dedupl':200 'defin':328 'delet':696,813 'depth':705 'descript':372 'differ':723 'dilut':762 'directori':80,650 'distanc':209 'document':337 'duplic':719,1007 'edg':481 'emphasi':115,219 'empti':740 'endpoint':557,993 'entiti':291,318,442 'environ':876,916,946 'error':943 'everi':860 'exact':863 'exampl':880 'example.com':897 'exceed':874 'exclud':141,156 'exist':117,1014,1024 'exit':1061 'exp':550 'expect':967 'experi':753 'extract':4,42,60,102 'fail':979 'fetch':652 'field':379,469,602 'file':100,921 'files/ids':663,672 'find':733 'first':289,316,440,479 'first-class':288,315,439 'fit':266 'flatten':709 'follow':417 'form':182,189 'format':228,547,776,855,968 'frequenc':40,124,133,196,214 'frontmatt':118 'full':424 'fuzzi':203 'generat':33,539,560 'get':343,380,513,566 'ghost':25,306,509,520,887 'graphql':24,295,322,446,449,476,477,501 'groq':32,332,608 'group':169,170,190,732 'guidelin':829 'h2':104,242 'h3':106,250 'handl':944 'harder':764 'head':41,107,216 'header':422,487 'health':675 'healthi':786 'hierarch':376 'high':132,745 'high-frequ':131 'highest':195 'highest-frequ':194 'hyphen':184,188,772,858 'hyphen-separ':857 'iat':548 'id':293,408,452,466,483,545,611,620 'identifi':884 'inconsist':769 'invalid':969 'inventori':643 'issu':793 'ital':113 'jwt':313,521,538 'keep':178 'key':311,544,900,908,940,956,983 'keyword':39,255,351 'keyword-research':254 'least':844 'legaci':498 'level':707 'levenshtein':208 'limit':517,1027,1041 'list':341,374,425,471,511,660,669,796,816,1056 'low':767 'lowercas':856 'main':834 'major':16 'manag':45 'map':657 'mark':497 'market':240 'match':204 'medium':757 'mention':244,252,259 'merg':175,183,690,725,799 'messag':470 'method':280 'min':552 'minimum':138,147,153 'miss':945,963 'mix':770 'model':282 'must':910 'mutat':333,450,616,626 'n':782,785,787,789,791,809 'name':207,365,367,371,394,531,533,537,579,581,585,603,612,630,659,668,724,768,1025 'navig':766 'need':862 'never':873,917 'new':504 'node':482 'noindex':688,819 'non':159,187 'non-hyphen':186 'non-tag':158 'note':494,1021 'object':437 'occurr':140,149,155 'oct':499 'one':458,1050 'option':269,373,1059 'orphan':85,691,737,790,794,814 'output':227,775 'overview':275 'page':348,385,742,851 'pages':571 'pagin':416,570 'parent':378,398 'pars':94 'password':286,357,904 'per':347,384,685,836,868 'persist':1042 'phrase':114,134,146,152 'platform':18,53,883,1055 'poor':751 'post':13,231,360,389,403,404,525,573,618,646,662,671,684,694,711,750,811,825,846,861,869 'preposit':163 'presenc':217 'present':121 'primari':108,263,865 'prioriti':735 'project':334,619 'pronoun':165 'provid':965 'purpos':56,879 'push':70 'queri':606 'rank':202,225,233 'rate':1026 'read':97,930 'recommend':687,695,702,708,731,797,872 'relat':171 'report':959,977,999,1038 'requir':502 'research':256 'rest':21,284,307,320,495 'rest/graphql':29 'retri':987,1036 'return':221,975,1032 'runtim':942 'saniti':31,331,604,889 'scan':126,644 'schema':600 'score':211,235 'search':350 'second':270,998 'secondari':268 'secret':546 'semant':168 'seo':247,755 'seo-strategi':246 'separ':859 'set':912 'setup':565 'shell':915 'shopifi':23,294,426,430,490,886 'shopify/article/123':453 'sign':314,540 'signal':110,116,756 'sinc':506 'singular/plural':176 'site':827,837 'site-wid':826 'site/directory':779 'skill':929 'skill-blog-taxonomy' 'skip':1018 'slug':368,396,534,582,613,632,720,722,854,1009 'slugifi':206 'sourc':236 'source-agricidaniel' 'space':774 'step':92,122,166,198,641,673,729 'stop':142 'store':918 'strapi':28,319,555,590,888 'strategi':248 'string':302,432 'structur':96 'suggest':5,35,59,90,226,230,261,1003 'support':19,377,1054 'sync':7,69 'synonym':191 'tag':8,34,46,62,84,86,89,160,229,234,281,342,359,366,370,401,410,428,445,455,457,460,463,467,473,485,512,524,530,532,536,580,584,607,610,615,629,631,634,658,666,681,686,692,697,701,738,747,758,781,795,801,805,815,821,840,867,1008,1012,1022 'tag-a':800 'tag-b':804 'tag-nam':369,535,583 'tag-on':456 'tag-thre':462 'tag-two':459 'tags/categories':119 'target':99,649 'taxonomi':3,44,58,68,71,78,87,638,763,777 'term':137,197 'text':129 'thin':83,680,746,788 'three':464 'threshold':678 'timeout':989,1001 'titl':232,484 'token':301,324,335,492,493,522,589,637,906 'top':222 'topic':49,109,808,839 'topic-agent-skills' 'topic-ai-citations' 'topic-ai-content' 'topic-ai-marketing' 'topic-ai-marketing-hub' 'topic-blog' 'topic-blog-writing' 'topic-claude-code' 'topic-claude-code-skill' 'topic-claude-plugin' 'topic-claude-skill' 'topic-content-creation' 'total':700,780,783 'totalpag':421 'two':461 'type':278,330,338,563,599,609,628,882,934,950,1047 'typic':564 'uncategor':710 'unreach':995 'unset':958 'unsupport':1043 'updat':443 'url':345,362,382,391,406,515,527,568,575,891,893,936,952,1006 'use':475,592,664,852 'user':327,752 'user-defin':326 'usererror':468 'usernam':355 'v4':591 'valid':1058 'variabl':877,878,947,961 'variant':177 'version':728,926 'via':74,932 'vs':773 'wait':1034 'wast':744 'weak':754 'wide':828 'word':136,143,145,151,161 'wordpress':20,283,339,885 'workflow':91,640 'wp':420 'wrapper':595 'x':419,489 'x-shopify-access-token':488 'x-wp-totalpag':418","prices":[{"id":"7b18ee8a-73e3-47e9-8855-bd508ee94cb1","listingId":"dda5a07c-40ec-4ec4-a4b1-aff2552c21cf","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"AgriciDaniel","category":"claude-blog","install_from":"skills.sh"},"createdAt":"2026-05-18T12:54:54.624Z"}],"sources":[{"listingId":"dda5a07c-40ec-4ec4-a4b1-aff2552c21cf","source":"github","sourceId":"AgriciDaniel/claude-blog/blog-taxonomy","sourceUrl":"https://github.com/AgriciDaniel/claude-blog/tree/main/skills/blog-taxonomy","isPrimary":false,"firstSeenAt":"2026-05-18T12:54:54.624Z","lastSeenAt":"2026-05-18T18:53:31.615Z"}],"details":{"listingId":"dda5a07c-40ec-4ec4-a4b1-aff2552c21cf","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"AgriciDaniel","slug":"blog-taxonomy","github":{"repo":"AgriciDaniel/claude-blog","stars":753,"topics":["agent-skills","ai","ai-citations","ai-content","ai-marketing","ai-marketing-hub","blog","blog-writing","claude-code","claude-code-skill","claude-plugin","claude-skill","content-creation","content-optimization","content-strategy","eeat","geo","multilingual","open-source","seo"],"license":"mit","html_url":"https://github.com/AgriciDaniel/claude-blog","pushed_at":"2026-05-15T04:45:18Z","description":"Claude Code blog skill suite: 30 sub-skills, 5 agents, 5-gate v1.9.0 Blog Delivery Contract, dual-optimized for Google rankings and AI citations. Active development at AI-Marketing-Hub/claude-blog (AI Marketing Hub Pro community); public releases ship here.","skill_md_sha":"fb3a2c844795aad52183dc536cb06c23d677add9","skill_md_path":"skills/blog-taxonomy/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/AgriciDaniel/claude-blog/tree/main/skills/blog-taxonomy"},"layout":"multi","source":"github","category":"claude-blog","frontmatter":{"name":"blog-taxonomy","license":"MIT","description":"Extract, suggest, and sync tags and categories for blog posts across all major CMS platforms. Supports WordPress REST API, Shopify GraphQL, Ghost Content API, Strapi REST/GraphQL, and Sanity GROQ. Generates tag suggestions from content analysis (keyword frequency, heading extraction, semantic grouping), enforces minimum post-count thresholds to prevent thin tag archives, and syncs taxonomy via authenticated API calls. Use when user says \"tags\", \"categories\", \"taxonomy\", \"tag suggestions\", \"sync tags\", \"WordPress tags\", \"Shopify tags\"."},"skills_sh_url":"https://skills.sh/AgriciDaniel/claude-blog/blog-taxonomy"},"updatedAt":"2026-05-18T18:53:31.615Z"}}