@biggora/claude-plugins 1.0.0 → 1.1.0

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 (80) hide show
  1. package/.claude/settings.local.json +13 -0
  2. package/CLAUDE.md +55 -0
  3. package/LICENSE +1 -1
  4. package/README.md +208 -39
  5. package/bin/cli.js +39 -0
  6. package/package.json +30 -17
  7. package/registry/registry.json +166 -1
  8. package/registry/schema.json +10 -0
  9. package/src/commands/skills/add.js +194 -0
  10. package/src/commands/skills/list.js +52 -0
  11. package/src/commands/skills/remove.js +27 -0
  12. package/src/commands/skills/update.js +74 -0
  13. package/src/config.js +5 -0
  14. package/src/skills/codex-cli/SKILL.md +265 -0
  15. package/src/skills/commafeed-api/SKILL.md +1012 -0
  16. package/src/skills/gemini-cli/SKILL.md +379 -0
  17. package/src/skills/gemini-cli/references/commands.md +145 -0
  18. package/src/skills/gemini-cli/references/configuration.md +182 -0
  19. package/src/skills/gemini-cli/references/headless-and-scripting.md +181 -0
  20. package/src/skills/gemini-cli/references/mcp-and-extensions.md +254 -0
  21. package/src/skills/n8n-api/SKILL.md +623 -0
  22. package/src/skills/notebook-lm/SKILL.md +217 -0
  23. package/src/skills/notebook-lm/references/artifact-options.md +168 -0
  24. package/src/skills/notebook-lm/references/auth.md +58 -0
  25. package/src/skills/notebook-lm/references/workflows.md +144 -0
  26. package/src/skills/screen-recording/SKILL.md +309 -0
  27. package/src/skills/screen-recording/references/approach1-programmatic.md +311 -0
  28. package/src/skills/screen-recording/references/approach2-xvfb.md +232 -0
  29. package/src/skills/screen-recording/references/design-patterns.md +168 -0
  30. package/src/skills/test-mobile-app/SKILL.md +212 -0
  31. package/src/skills/test-mobile-app/references/report-template.md +95 -0
  32. package/src/skills/test-mobile-app/references/setup-appium.md +154 -0
  33. package/src/skills/test-mobile-app/scripts/analyze_apk.py +164 -0
  34. package/src/skills/test-mobile-app/scripts/check_environment.py +116 -0
  35. package/src/skills/test-mobile-app/scripts/generate_report.py +250 -0
  36. package/src/skills/test-mobile-app/scripts/run_tests.py +326 -0
  37. package/src/skills/test-web-ui/SKILL.md +232 -0
  38. package/src/skills/test-web-ui/references/test_case_schema.md +102 -0
  39. package/src/skills/test-web-ui/scripts/discover.py +176 -0
  40. package/src/skills/test-web-ui/scripts/generate_report.py +237 -0
  41. package/src/skills/test-web-ui/scripts/run_tests.py +296 -0
  42. package/src/skills/text-to-speech/SKILL.md +236 -0
  43. package/src/skills/text-to-speech/references/espeak-cli.md +277 -0
  44. package/src/skills/text-to-speech/references/kokoro-onnx.md +124 -0
  45. package/src/skills/text-to-speech/references/online-engines.md +128 -0
  46. package/src/skills/text-to-speech/references/pyttsx3-espeak.md +143 -0
  47. package/src/skills/tm-search/SKILL.md +240 -0
  48. package/src/skills/tm-search/references/field-guide.md +79 -0
  49. package/src/skills/tm-search/references/scraping-fallback.md +140 -0
  50. package/src/skills/tm-search/scripts/tm_search.py +375 -0
  51. package/src/skills/wp-rest-api/SKILL.md +114 -0
  52. package/src/skills/wp-rest-api/references/authentication.md +18 -0
  53. package/src/skills/wp-rest-api/references/custom-content-types.md +20 -0
  54. package/src/skills/wp-rest-api/references/discovery-and-params.md +20 -0
  55. package/src/skills/wp-rest-api/references/responses-and-fields.md +30 -0
  56. package/src/skills/wp-rest-api/references/routes-and-endpoints.md +36 -0
  57. package/src/skills/wp-rest-api/references/schema.md +22 -0
  58. package/src/skills/youtube-search/SKILL.md +412 -0
  59. package/src/skills/youtube-search/references/parsing-examples.md +159 -0
  60. package/src/skills/youtube-search/references/youtube-api-quota.md +85 -0
  61. package/src/skills/youtube-thumbnail/SKILL.md +1060 -0
  62. package/tests/commands/info.test.js +49 -0
  63. package/tests/commands/install.test.js +36 -0
  64. package/tests/commands/list.test.js +66 -0
  65. package/tests/commands/publish.test.js +182 -0
  66. package/tests/commands/search.test.js +45 -0
  67. package/tests/commands/uninstall.test.js +29 -0
  68. package/tests/commands/update.test.js +59 -0
  69. package/tests/functional/skills-lifecycle.test.js +293 -0
  70. package/tests/helpers/fixtures.js +63 -0
  71. package/tests/integration/cli.test.js +83 -0
  72. package/tests/skills/add.test.js +138 -0
  73. package/tests/skills/list.test.js +63 -0
  74. package/tests/skills/remove.test.js +38 -0
  75. package/tests/skills/update.test.js +60 -0
  76. package/tests/unit/config.test.js +31 -0
  77. package/tests/unit/registry.test.js +79 -0
  78. package/tests/unit/utils.test.js +150 -0
  79. package/tests/validation/registry-schema.test.js +112 -0
  80. package/tests/validation/skills-validation.test.js +96 -0
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ tm-search: US Trademark Search CLI Tool
4
+ Searches USPTO trademark database via tmsearch.uspto.gov and tsdrapi.uspto.gov
5
+
6
+ Usage:
7
+ python tm_search.py keyword <word> [--status=A] [--rows=25] [--json]
8
+ python tm_search.py available <word>
9
+ python tm_search.py status <serial_number> [--api-key=KEY]
10
+ python tm_search.py batch <word1,word2,...> [--status=A] [--csv]
11
+ python tm_search.py validate <file.txt> [--status=A] [--output=results.csv]
12
+ """
13
+
14
+ import sys
15
+ import json
16
+ import time
17
+ import csv
18
+ import argparse
19
+ import xml.etree.ElementTree as ET
20
+ from typing import Optional
21
+
22
+ try:
23
+ import requests
24
+ except ImportError:
25
+ print("Error: 'requests' library required. Run: pip install requests")
26
+ sys.exit(1)
27
+
28
+ # ─── Constants ─────────────────────────────────────────────────────────────────
29
+
30
+ SEARCH_BASE = "https://tmsearch.uspto.gov"
31
+ TSDR_BASE = "https://tsdrapi.uspto.gov/ts/cd"
32
+
33
+ HEADERS = {
34
+ "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (compatible; tm-search/1.0)",
35
+ "Accept": "application/json, text/plain, */*",
36
+ "Accept-Language": "en-US,en;q=0.9",
37
+ "Referer": f"{SEARCH_BASE}/search/search-information",
38
+ "Origin": SEARCH_BASE,
39
+ }
40
+
41
+ DISCLAIMER = (
42
+ "\n⚠️ DISCLAIMER: This is a preliminary search only. Trademark availability depends on many\n"
43
+ " factors. Consult a licensed trademark attorney before filing.\n"
44
+ )
45
+
46
+ # ─── Search Functions ───────────────────────────────────────────────────────────
47
+
48
+ def search_trademark(
49
+ keyword: str,
50
+ status: str = "A",
51
+ rows: int = 25,
52
+ start: int = 0,
53
+ plural_variants: bool = False,
54
+ session: Optional[requests.Session] = None
55
+ ) -> dict:
56
+ """
57
+ Search USPTO trademark database by keyword.
58
+
59
+ Args:
60
+ keyword: Word/phrase to search (auto-uppercased)
61
+ status: "A"=active, "D"=dead, ""=all
62
+ rows: Number of results (max 500)
63
+ start: Pagination offset
64
+ plural_variants: Include plural forms
65
+
66
+ Returns:
67
+ dict with "totalFound" and "trademarks" list
68
+ """
69
+ s = session or requests.Session()
70
+ keyword = keyword.upper().strip()
71
+
72
+ payload = {
73
+ "keyword": keyword,
74
+ "searchType": "1",
75
+ "statusType": status,
76
+ "pluralVariants": plural_variants,
77
+ "start": start,
78
+ "rows": rows,
79
+ }
80
+
81
+ try:
82
+ # Try POST first
83
+ resp = s.post(
84
+ f"{SEARCH_BASE}/search/keyword",
85
+ json=payload,
86
+ headers=HEADERS,
87
+ timeout=30
88
+ )
89
+ if resp.status_code == 200:
90
+ return resp.json()
91
+
92
+ # Fallback: GET with query params
93
+ params = {k: str(v).lower() if isinstance(v, bool) else v for k, v in payload.items()}
94
+ resp = s.get(
95
+ f"{SEARCH_BASE}/search/keyword",
96
+ params=params,
97
+ headers=HEADERS,
98
+ timeout=30
99
+ )
100
+ resp.raise_for_status()
101
+ return resp.json()
102
+
103
+ except requests.exceptions.RequestException as e:
104
+ return {"error": str(e), "totalFound": 0, "trademarks": []}
105
+
106
+
107
+ def check_availability(keyword: str) -> dict:
108
+ """Check if a keyword is available (no active trademarks)."""
109
+ result = search_trademark(keyword, status="A", rows=5)
110
+ dead_result = search_trademark(keyword, status="D", rows=5)
111
+
112
+ return {
113
+ "keyword": keyword.upper(),
114
+ "available": result.get("totalFound", 0) == 0,
115
+ "active_count": result.get("totalFound", 0),
116
+ "dead_count": dead_result.get("totalFound", 0),
117
+ "top_matches": result.get("trademarks", [])[:3],
118
+ }
119
+
120
+
121
+ def get_status_by_serial(serial_number: str, api_key: Optional[str] = None) -> dict:
122
+ """Get trademark case status by serial number via TSDR API."""
123
+ headers = {"Accept": "application/xml"}
124
+ if api_key:
125
+ headers["USPTO-API-KEY"] = api_key
126
+
127
+ # Remove non-digits
128
+ sn = "".join(filter(str.isdigit, serial_number))
129
+ url = f"{TSDR_BASE}/casestatus/sn{sn}/info.xml"
130
+
131
+ try:
132
+ resp = requests.get(url, headers=headers, timeout=30)
133
+ resp.raise_for_status()
134
+
135
+ # Parse XML response
136
+ root = ET.fromstring(resp.content)
137
+ ns = {"ns1": "urn:us:gov:doc:uspto:trademark:status"}
138
+
139
+ def find_text(path):
140
+ el = root.find(path, ns)
141
+ return el.text if el is not None else None
142
+
143
+ return {
144
+ "serialNumber": sn,
145
+ "status": find_text(".//ns1:MarkCurrentStatusExternalDescriptionText"),
146
+ "statusDate": find_text(".//ns1:MarkCurrentStatusDate"),
147
+ "wordMark": find_text(".//ns1:MarkVerbalElementText"),
148
+ "owner": find_text(".//ns1:EntityName"),
149
+ "filingDate": find_text(".//ns1:ApplicationDate"),
150
+ "registrationDate": find_text(".//ns1:RegistrationDate"),
151
+ "registrationNumber": find_text(".//ns1:RegistrationNumber"),
152
+ }
153
+ except requests.exceptions.RequestException as e:
154
+ return {"error": str(e), "serialNumber": sn}
155
+ except ET.ParseError:
156
+ return {"error": "Failed to parse XML response", "serialNumber": sn}
157
+
158
+
159
+ # ─── Output Formatting ──────────────────────────────────────────────────────────
160
+
161
+ def format_trademark(tm: dict) -> str:
162
+ """Format a single trademark result for display."""
163
+ classes = ", ".join(tm.get("internationalClassification", []))
164
+ return (
165
+ f" • \"{tm.get('wordMark', 'N/A')}\" | "
166
+ f"Owner: {tm.get('owner', 'N/A')} | "
167
+ f"Classes: {classes or 'N/A'} | "
168
+ f"Status: {tm.get('status', 'N/A')} | "
169
+ f"Serial: {tm.get('serialNumber', 'N/A')}"
170
+ )
171
+
172
+
173
+ def print_search_results(keyword: str, results: dict, show_json: bool = False):
174
+ """Print search results to stdout."""
175
+ if show_json:
176
+ print(json.dumps(results, indent=2))
177
+ return
178
+
179
+ total = results.get("totalFound", 0)
180
+ trademarks = results.get("trademarks", [])
181
+
182
+ print(f"\n{'='*60}")
183
+ print(f"KEYWORD: \"{keyword.upper()}\"")
184
+ print(f"Total found: {total}")
185
+
186
+ if total == 0:
187
+ print("Status: ✅ LIKELY AVAILABLE (no matching active marks)")
188
+ else:
189
+ print(f"Status: ❌ REGISTERED/PENDING MARKS FOUND")
190
+ print(f"\nTop results:")
191
+ for tm in trademarks[:10]:
192
+ print(format_trademark(tm))
193
+ if total > 10:
194
+ print(f" ... and {total - 10} more")
195
+
196
+ print(DISCLAIMER)
197
+
198
+
199
+ def print_availability(result: dict, show_json: bool = False):
200
+ """Print availability check result."""
201
+ if show_json:
202
+ print(json.dumps(result, indent=2))
203
+ return
204
+
205
+ keyword = result["keyword"]
206
+ print(f"\n{'='*60}")
207
+ print(f"AVAILABILITY CHECK: \"{keyword}\"")
208
+
209
+ if result["available"]:
210
+ print(f"Status: ✅ LIKELY AVAILABLE")
211
+ print(f"Active marks: 0")
212
+ print(f"Dead/cancelled marks: {result['dead_count']} (historical, may not block registration)")
213
+ else:
214
+ print(f"Status: ❌ NOT AVAILABLE — {result['active_count']} active mark(s) found")
215
+ if result["top_matches"]:
216
+ print("\nConflicting marks:")
217
+ for tm in result["top_matches"]:
218
+ print(format_trademark(tm))
219
+
220
+ print(DISCLAIMER)
221
+
222
+
223
+ # ─── Batch Validation ───────────────────────────────────────────────────────────
224
+
225
+ def batch_validate(
226
+ keywords: list[str],
227
+ status: str = "A",
228
+ delay: float = 0.75,
229
+ output_csv: Optional[str] = None
230
+ ) -> list[dict]:
231
+ """
232
+ Validate a list of keywords against USPTO trademarks.
233
+
234
+ Args:
235
+ keywords: List of words to check
236
+ status: Filter status
237
+ delay: Seconds between requests (avoid rate limiting)
238
+ output_csv: If set, write results to this CSV file
239
+
240
+ Returns:
241
+ List of result dicts
242
+ """
243
+ results = []
244
+ session = requests.Session()
245
+
246
+ print(f"Checking {len(keywords)} keywords against USPTO trademarks...")
247
+ print(f"Status filter: {'ACTIVE' if status == 'A' else 'DEAD' if status == 'D' else 'ALL'}\n")
248
+
249
+ for i, word in enumerate(keywords, 1):
250
+ word = word.strip()
251
+ if not word:
252
+ continue
253
+
254
+ print(f"[{i}/{len(keywords)}] Checking: {word.upper()}", end=" ... ", flush=True)
255
+
256
+ r = search_trademark(word, status=status, rows=5, session=session)
257
+ count = r.get("totalFound", 0)
258
+ top = r.get("trademarks", [{}])
259
+
260
+ result = {
261
+ "keyword": word.upper(),
262
+ "status": "AVAILABLE" if count == 0 else "TAKEN",
263
+ "count": count,
264
+ "top_owner": top[0].get("owner", "") if top else "",
265
+ "top_mark": top[0].get("wordMark", "") if top else "",
266
+ "top_serial": top[0].get("serialNumber", "") if top else "",
267
+ }
268
+ results.append(result)
269
+
270
+ print(f"{'✅ AVAILABLE' if count == 0 else f'❌ TAKEN ({count} marks)'}")
271
+
272
+ if i < len(keywords):
273
+ time.sleep(delay)
274
+
275
+ if output_csv:
276
+ with open(output_csv, "w", newline="") as f:
277
+ writer = csv.DictWriter(f, fieldnames=results[0].keys())
278
+ writer.writeheader()
279
+ writer.writerows(results)
280
+ print(f"\n✅ Results saved to: {output_csv}")
281
+
282
+ return results
283
+
284
+
285
+ # ─── CLI Entry Point ────────────────────────────────────────────────────────────
286
+
287
+ def main():
288
+ parser = argparse.ArgumentParser(
289
+ description="Search and validate US trademarks via USPTO",
290
+ formatter_class=argparse.RawDescriptionHelpFormatter,
291
+ epilog="""
292
+ Examples:
293
+ tm_search.py keyword "CLOUDPEAK"
294
+ tm_search.py keyword "APPLE" --status=A --rows=50
295
+ tm_search.py available "NEONPULSE"
296
+ tm_search.py status 78787878 --api-key=YOUR_KEY
297
+ tm_search.py batch "BRAND1,BRAND2,BRAND3" --csv
298
+ tm_search.py validate names.txt --output=results.csv
299
+ """
300
+ )
301
+
302
+ subparsers = parser.add_subparsers(dest="command", required=True)
303
+
304
+ # keyword command
305
+ p_kw = subparsers.add_parser("keyword", help="Search by keyword")
306
+ p_kw.add_argument("word", help="Keyword to search")
307
+ p_kw.add_argument("--status", default="A", choices=["A", "D", ""], help="A=active, D=dead")
308
+ p_kw.add_argument("--rows", type=int, default=25, help="Results per page (max 500)")
309
+ p_kw.add_argument("--plural", action="store_true", help="Include plural variants")
310
+ p_kw.add_argument("--json", action="store_true", help="Output raw JSON")
311
+
312
+ # available command
313
+ p_av = subparsers.add_parser("available", help="Check if keyword is available")
314
+ p_av.add_argument("word", help="Keyword to check")
315
+ p_av.add_argument("--json", action="store_true", help="Output raw JSON")
316
+
317
+ # status command
318
+ p_st = subparsers.add_parser("status", help="Get case status by serial number")
319
+ p_st.add_argument("serial", help="Serial number (8 digits)")
320
+ p_st.add_argument("--api-key", help="USPTO API key for bulk access")
321
+ p_st.add_argument("--json", action="store_true", help="Output raw JSON")
322
+
323
+ # batch command
324
+ p_bt = subparsers.add_parser("batch", help="Check multiple comma-separated keywords")
325
+ p_bt.add_argument("words", help="Comma-separated keywords")
326
+ p_bt.add_argument("--status", default="A", choices=["A", "D", ""])
327
+ p_bt.add_argument("--csv", action="store_true", help="Output as CSV")
328
+ p_bt.add_argument("--delay", type=float, default=0.75, help="Delay between requests (seconds)")
329
+
330
+ # validate command
331
+ p_vl = subparsers.add_parser("validate", help="Validate keywords from a file")
332
+ p_vl.add_argument("file", help="Text file with one keyword per line")
333
+ p_vl.add_argument("--status", default="A", choices=["A", "D", ""])
334
+ p_vl.add_argument("--output", help="Output CSV file path")
335
+ p_vl.add_argument("--delay", type=float, default=0.75)
336
+
337
+ args = parser.parse_args()
338
+
339
+ if args.command == "keyword":
340
+ result = search_trademark(args.word, status=args.status, rows=args.rows, plural_variants=args.plural)
341
+ print_search_results(args.word, result, show_json=args.json)
342
+
343
+ elif args.command == "available":
344
+ result = check_availability(args.word)
345
+ print_availability(result, show_json=args.json)
346
+
347
+ elif args.command == "status":
348
+ result = get_status_by_serial(args.serial, api_key=args.api_key)
349
+ if args.json:
350
+ print(json.dumps(result, indent=2))
351
+ else:
352
+ print(f"\nSerial: {result.get('serialNumber')}")
353
+ for k, v in result.items():
354
+ if k != "serialNumber" and v:
355
+ print(f" {k}: {v}")
356
+
357
+ elif args.command == "batch":
358
+ words = [w.strip() for w in args.words.split(",") if w.strip()]
359
+ results = batch_validate(words, status=args.status, delay=args.delay)
360
+ if args.csv:
361
+ import io
362
+ output = io.StringIO()
363
+ writer = csv.DictWriter(output, fieldnames=results[0].keys())
364
+ writer.writeheader()
365
+ writer.writerows(results)
366
+ print("\n" + output.getvalue())
367
+
368
+ elif args.command == "validate":
369
+ with open(args.file) as f:
370
+ words = [line.strip() for line in f if line.strip()]
371
+ batch_validate(words, status=args.status, delay=args.delay, output_csv=args.output)
372
+
373
+
374
+ if __name__ == "__main__":
375
+ main()
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: wp-rest-api
3
+ description: "Use when building, extending, or debugging WordPress REST API endpoints/routes: register_rest_route, WP_REST_Controller/controller classes, schema/argument validation, permission_callback/authentication, response shaping, register_rest_field/register_meta, or exposing CPTs/taxonomies via show_in_rest."
4
+ compatibility: "Targets WordPress 6.9+ (PHP 7.2.24+). Filesystem-based agent with bash + node. Some workflows require WP-CLI."
5
+ ---
6
+
7
+ # WP REST API
8
+
9
+ ## When to use
10
+
11
+ Use this skill when you need to:
12
+
13
+ - create or update REST routes/endpoints
14
+ - debug 401/403/404 errors or permission/nonce issues
15
+ - add custom fields/meta to REST responses
16
+ - expose custom post types or taxonomies via REST
17
+ - implement schema + argument validation
18
+ - adjust response links/embedding/pagination
19
+
20
+ ## Inputs required
21
+
22
+ - Repo root + target plugin/theme/mu-plugin (path to entrypoint).
23
+ - Desired namespace + version (e.g. `my-plugin/v1`) and routes.
24
+ - Authentication mode (cookie + nonce vs application passwords vs auth plugin).
25
+ - Target WordPress version constraints (if below 6.9, call out).
26
+
27
+ ## Procedure
28
+
29
+ ### 0) Triage and locate REST usage
30
+
31
+ 1. Run triage:
32
+ - `node skills/wp-project-triage/scripts/detect_wp_project.mjs`
33
+ 2. Search for existing REST usage:
34
+ - `register_rest_route`
35
+ - `WP_REST_Controller`
36
+ - `rest_api_init`
37
+ - `show_in_rest`, `rest_base`, `rest_controller_class`
38
+
39
+ If this is a full site repo, pick the specific plugin/theme before changing code.
40
+
41
+ ### 1) Choose the right approach
42
+
43
+ - **Expose CPT/taxonomy in `wp/v2`:**
44
+ - Use `show_in_rest => true` + `rest_base` if needed.
45
+ - Optionally provide `rest_controller_class`.
46
+ - Read `references/custom-content-types.md`.
47
+ - **Custom endpoints:**
48
+ - Use `register_rest_route()` on `rest_api_init`.
49
+ - Prefer a controller class (`WP_REST_Controller` subclass) for anything non-trivial.
50
+ - Read `references/routes-and-endpoints.md` and `references/schema.md`.
51
+
52
+ ### 2) Register routes safely (namespaces, methods, permissions)
53
+
54
+ - Use a unique namespace `vendor/v1`; avoid `wp/*` unless core.
55
+ - Always provide `permission_callback` (use `__return_true` for public endpoints).
56
+ - Use `WP_REST_Server::READABLE/CREATABLE/EDITABLE/DELETABLE` constants.
57
+ - Return data via `rest_ensure_response()` or `WP_REST_Response`.
58
+ - Return errors via `WP_Error` with an explicit `status`.
59
+
60
+ Read `references/routes-and-endpoints.md`.
61
+
62
+ ### 3) Validate/sanitize request args
63
+
64
+ - Define `args` with `type`, `default`, `required`, `validate_callback`, `sanitize_callback`.
65
+ - Prefer JSON Schema validation with `rest_validate_value_from_schema` then `rest_sanitize_value_from_schema`.
66
+ - Never read `$_GET`/`$_POST` directly inside endpoints; use `WP_REST_Request`.
67
+
68
+ Read `references/schema.md`.
69
+
70
+ ### 4) Responses, fields, and links
71
+
72
+ - Do **not** remove core fields from default endpoints; add fields instead.
73
+ - Use `register_rest_field` for computed fields; `register_meta` with `show_in_rest` for meta.
74
+ - For `object`/`array` meta, define schema in `show_in_rest.schema`.
75
+ - If you need unfiltered post content (e.g., ToC plugins injecting HTML), request `?context=edit` to access `content.raw` (auth required). Pair with `_fields=content.raw` to keep responses small.
76
+ - Add related resource links via `WP_REST_Response::add_link()`.
77
+
78
+ Read `references/responses-and-fields.md`.
79
+
80
+ ### 5) Authentication and authorization
81
+
82
+ - For wp-admin/JS: cookie auth + `X-WP-Nonce` (action `wp_rest`).
83
+ - For external clients: application passwords (basic auth) or an auth plugin.
84
+ - Use capability checks in `permission_callback` (authorization), not just “logged in”.
85
+
86
+ Read `references/authentication.md`.
87
+
88
+ ### 6) Client-facing behavior (discovery, pagination, embeds)
89
+
90
+ - Ensure discovery works (`Link` header or `<link rel="https://api.w.org/">`).
91
+ - Support `_fields`, `_embed`, `_method`, `_envelope`, pagination headers.
92
+ - Remember `per_page` is capped at 100.
93
+
94
+ Read `references/discovery-and-params.md`.
95
+
96
+ ## Verification
97
+
98
+ - `/wp-json/` index includes your namespace.
99
+ - `OPTIONS` on your route returns schema (when provided).
100
+ - Endpoint returns expected data; permission failures return 401/403 as appropriate.
101
+ - CPT/taxonomy routes appear under `wp/v2` when `show_in_rest` is true.
102
+ - Run repo lint/tests and any PHP/JS build steps.
103
+
104
+ ## Failure modes / debugging
105
+
106
+ - 404: `rest_api_init` not firing, route typo, or permalinks off (use `?rest_route=`).
107
+ - 401/403: missing nonce/auth, or `permission_callback` too strict.
108
+ - `_doing_it_wrong` for missing `permission_callback`: add it (use `__return_true` if public).
109
+ - Invalid params: missing/incorrect `args` schema or validation callbacks.
110
+ - Fields missing: `show_in_rest` false, meta not registered, or CPT lacks `custom-fields` support.
111
+
112
+ ## Escalation
113
+
114
+ If version support or behavior is unclear, consult the REST API Handbook and core docs before inventing patterns.
@@ -0,0 +1,18 @@
1
+ # Authentication (summary)
2
+
3
+ ## Cookie authentication (in-dashboard / same-site)
4
+
5
+ - Standard for wp-admin and theme/plugin JS.
6
+ - Requires a REST nonce (`wp_rest`) sent as `X-WP-Nonce` header or `_wpnonce` param.
7
+ - If the nonce is missing, the request is treated as unauthenticated even if cookies exist.
8
+
9
+ ## Application Passwords (external clients)
10
+
11
+ - Available in WordPress 5.6+.
12
+ - Use HTTPS + Basic Auth with the application password.
13
+ - Recommended over the legacy Basic Auth plugin.
14
+
15
+ ## Auth plugins
16
+
17
+ - OAuth 1.0a or JWT plugins are common for external apps.
18
+ - Use only if required; follow plugin docs and security guidance.
@@ -0,0 +1,20 @@
1
+ # Custom Content Types (summary)
2
+
3
+ ## Custom post types
4
+
5
+ - Set `show_in_rest => true` in `register_post_type()` to expose in `wp/v2`.
6
+ - Use `rest_base` to change the route slug.
7
+ - Optionally set `rest_controller_class` (must extend `WP_REST_Controller`).
8
+
9
+ ## Custom taxonomies
10
+
11
+ - Set `show_in_rest => true` in `register_taxonomy()`.
12
+ - Use `rest_base` and optional `rest_controller_class` (default `WP_REST_Terms_Controller`).
13
+
14
+ ## Adding REST support to existing types
15
+
16
+ - Use `register_post_type_args` or `register_taxonomy_args` filters to enable `show_in_rest` for types you do not control.
17
+
18
+ ## Discovery links for custom controllers
19
+
20
+ - If you use a custom controller class, use `rest_route_for_post` or `rest_route_for_term` filters to map objects to routes.
@@ -0,0 +1,20 @@
1
+ # Discovery and Global Parameters (summary)
2
+
3
+ ## API discovery
4
+
5
+ - REST API root is discovered via the `Link` header: `rel="https://api.w.org/"`.
6
+ - HTML pages also include a `<link rel="https://api.w.org/" href="...">` element.
7
+ - For non-pretty permalinks, use `?rest_route=/`.
8
+
9
+ ## Global parameters
10
+
11
+ - `_fields` limits response fields (supports nested meta keys).
12
+ - `_embed` includes linked resources in `_embedded`.
13
+ - `_method` or `X-HTTP-Method-Override` allows POST to simulate PUT/DELETE.
14
+ - `_envelope` puts headers/status in the response body.
15
+ - `_jsonp` enables JSONP for legacy clients.
16
+
17
+ ## Pagination
18
+
19
+ - Collections accept `page`, `per_page` (1-100), and `offset`.
20
+ - Pagination headers: `X-WP-Total` and `X-WP-TotalPages`.
@@ -0,0 +1,30 @@
1
+ # Responses and Fields (summary)
2
+
3
+ ## Do not remove core fields
4
+
5
+ - Removing or changing core fields breaks clients (including wp-admin).
6
+ - Prefer adding new fields or using `_fields` to limit response size.
7
+
8
+ ## register_rest_field
9
+
10
+ - Use for computed or custom fields.
11
+ - Provide `get_callback`, optional `update_callback`, and `schema`.
12
+ - Register on `rest_api_init`.
13
+
14
+ ## Raw vs rendered content
15
+
16
+ - For posts, `content.rendered` reflects filters (plugins like ToC inject HTML).
17
+ - Use `?context=edit` (authenticated) to access `content.raw`.
18
+ - Combine with `_fields=content.raw` when you only need the editable body.
19
+
20
+ ## register_meta / register_post_meta / register_term_meta
21
+
22
+ - Use when the data is stored as meta.
23
+ - Set `show_in_rest => true` to expose under `.meta`.
24
+ - For `object` or `array` types, provide a JSON schema in `show_in_rest.schema`.
25
+
26
+ ## Links and embedding
27
+
28
+ - Add links with `WP_REST_Response::add_link( $rel, $href, $attrs )`.
29
+ - Use `embeddable => true` to allow `_embed`.
30
+ - Use IANA rels or a custom URI relation; CURIEs can be registered via `rest_response_link_curies`.
@@ -0,0 +1,36 @@
1
+ # Routes and Endpoints (summary)
2
+
3
+ ## Registering routes
4
+
5
+ - Register routes on the `rest_api_init` hook with `register_rest_route( $namespace, $route, $args )`.
6
+ - A **route** is the URL pattern; an **endpoint** is the method + callback bound to that route.
7
+ - For non-pretty permalinks, the route is accessed via `?rest_route=/namespace/route`.
8
+
9
+ ## Namespacing
10
+
11
+ - Always namespace routes (`vendor/v1`).
12
+ - **Do not** use the `wp/*` namespace unless you are targeting core.
13
+
14
+ ## Methods
15
+
16
+ - Use `WP_REST_Server::READABLE` (GET), `CREATABLE` (POST), `EDITABLE` (PUT/PATCH), `DELETABLE` (DELETE).
17
+ - Multiple endpoints can share a route, one per method.
18
+
19
+ ## permission_callback (required)
20
+
21
+ - Always provide `permission_callback`.
22
+ - Public endpoints should use `__return_true`.
23
+ - For restricted endpoints, use capability checks (`current_user_can`) or object-level authorization.
24
+ - Missing `permission_callback` emits a `_doing_it_wrong` notice in modern WP.
25
+
26
+ ## Arguments
27
+
28
+ - Register `args` to validate and sanitize inputs.
29
+ - Use `type`, `required`, `default`, `validate_callback`, `sanitize_callback`.
30
+ - Access params via the `WP_REST_Request` object, not `$_GET`/`$_POST`.
31
+
32
+ ## Return values
33
+
34
+ - Return data via `rest_ensure_response()` or a `WP_REST_Response`.
35
+ - Return `WP_Error` with a `status` in `data` for error responses.
36
+ - Do not call `wp_send_json()` in REST callbacks.
@@ -0,0 +1,22 @@
1
+ # Schema and Argument Validation (summary)
2
+
3
+ ## JSON Schema in WordPress
4
+
5
+ - REST API uses JSON Schema (draft 4 subset) for resource and argument definitions.
6
+ - Provide schema via `get_item_schema()` on controllers or `schema` callbacks on routes.
7
+ - Schema enables discovery (`OPTIONS`) and validation.
8
+
9
+ ## Validation + sanitization
10
+
11
+ - Use `rest_validate_value_from_schema( $value, $schema )` then `rest_sanitize_value_from_schema( $value, $schema )`.
12
+ - If you override `sanitize_callback`, built-in schema validation will not run; use `rest_validate_request_arg` to keep it.
13
+ - `WP_REST_Controller::get_endpoint_args_for_item_schema()` wires validation automatically.
14
+
15
+ ## Schema caching
16
+
17
+ - Cache the generated schema on the controller instance (`$this->schema`) to avoid recomputation.
18
+
19
+ ## Formats and types
20
+
21
+ - Common formats: `date-time`, `uri`, `email`, `ip`, `uuid`, `hex-color`.
22
+ - For `array` and `object` types, you must define `items` or `properties` schemas.