@wootsup/mcp 0.1.0 → 0.4.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 (208) hide show
  1. package/CHANGELOG.md +157 -83
  2. package/README.md +31 -27
  3. package/SECURITY.md +15 -6
  4. package/dist/auth/keychain.d.ts +27 -1
  5. package/dist/auth/keychain.js +48 -2
  6. package/dist/auth/keychain.js.map +1 -1
  7. package/dist/catalog/build-catalog.d.ts +31 -0
  8. package/dist/catalog/build-catalog.js +68 -0
  9. package/dist/catalog/build-catalog.js.map +1 -0
  10. package/dist/cli-hint.d.ts +22 -0
  11. package/dist/cli-hint.js +55 -0
  12. package/dist/cli-hint.js.map +1 -0
  13. package/dist/index.js +129 -22
  14. package/dist/index.js.map +1 -1
  15. package/dist/install-skill.js +1 -1
  16. package/dist/modules/apimapper/auto-layout.d.ts +21 -0
  17. package/dist/modules/apimapper/auto-layout.js +54 -0
  18. package/dist/modules/apimapper/auto-layout.js.map +1 -0
  19. package/dist/modules/apimapper/cache.js +25 -17
  20. package/dist/modules/apimapper/cache.js.map +1 -1
  21. package/dist/modules/apimapper/client.d.ts +115 -4
  22. package/dist/modules/apimapper/client.js +699 -304
  23. package/dist/modules/apimapper/client.js.map +1 -1
  24. package/dist/modules/apimapper/connections-format.d.ts +31 -1
  25. package/dist/modules/apimapper/connections-format.js +97 -5
  26. package/dist/modules/apimapper/connections-format.js.map +1 -1
  27. package/dist/modules/apimapper/connections.d.ts +9 -7
  28. package/dist/modules/apimapper/connections.js +449 -127
  29. package/dist/modules/apimapper/connections.js.map +1 -1
  30. package/dist/modules/apimapper/credential-sanitizer.d.ts +5 -0
  31. package/dist/modules/apimapper/credential-sanitizer.js +60 -1
  32. package/dist/modules/apimapper/credential-sanitizer.js.map +1 -1
  33. package/dist/modules/apimapper/credentials.js +105 -61
  34. package/dist/modules/apimapper/credentials.js.map +1 -1
  35. package/dist/modules/apimapper/diagnose.js +21 -2
  36. package/dist/modules/apimapper/diagnose.js.map +1 -1
  37. package/dist/modules/apimapper/elicitation.d.ts +29 -0
  38. package/dist/modules/apimapper/elicitation.js +62 -0
  39. package/dist/modules/apimapper/elicitation.js.map +1 -1
  40. package/dist/modules/apimapper/example-extract.d.ts +13 -0
  41. package/dist/modules/apimapper/example-extract.js +111 -0
  42. package/dist/modules/apimapper/example-extract.js.map +1 -0
  43. package/dist/modules/apimapper/filter-operators.d.ts +24 -0
  44. package/dist/modules/apimapper/filter-operators.js +103 -0
  45. package/dist/modules/apimapper/filter-operators.js.map +1 -0
  46. package/dist/modules/apimapper/flows-format.js +92 -22
  47. package/dist/modules/apimapper/flows-format.js.map +1 -1
  48. package/dist/modules/apimapper/flows.d.ts +8 -7
  49. package/dist/modules/apimapper/flows.js +275 -120
  50. package/dist/modules/apimapper/flows.js.map +1 -1
  51. package/dist/modules/apimapper/gateway/advanced-read-tool.d.ts +9 -0
  52. package/dist/modules/apimapper/gateway/advanced-read-tool.js +172 -0
  53. package/dist/modules/apimapper/gateway/advanced-read-tool.js.map +1 -0
  54. package/dist/modules/apimapper/gateway/advanced-tool.js +66 -106
  55. package/dist/modules/apimapper/gateway/advanced-tool.js.map +1 -1
  56. package/dist/modules/apimapper/gateway/collect-module-tools.d.ts +17 -0
  57. package/dist/modules/apimapper/gateway/collect-module-tools.js +44 -0
  58. package/dist/modules/apimapper/gateway/collect-module-tools.js.map +1 -0
  59. package/dist/modules/apimapper/gateway/essentials.d.ts +1 -1
  60. package/dist/modules/apimapper/gateway/essentials.js +21 -2
  61. package/dist/modules/apimapper/gateway/essentials.js.map +1 -1
  62. package/dist/modules/apimapper/gateway/gateway-shared.d.ts +21 -0
  63. package/dist/modules/apimapper/gateway/gateway-shared.js +124 -0
  64. package/dist/modules/apimapper/gateway/gateway-shared.js.map +1 -0
  65. package/dist/modules/apimapper/gateway/test-support.d.ts +1 -17
  66. package/dist/modules/apimapper/gateway/test-support.js +4 -33
  67. package/dist/modules/apimapper/gateway/test-support.js.map +1 -1
  68. package/dist/modules/apimapper/get-skill-cores.d.ts +4 -0
  69. package/dist/modules/apimapper/get-skill-cores.js +220 -0
  70. package/dist/modules/apimapper/get-skill-cores.js.map +1 -0
  71. package/dist/modules/apimapper/get-skill.d.ts +1 -1
  72. package/dist/modules/apimapper/get-skill.js +74 -9
  73. package/dist/modules/apimapper/get-skill.js.map +1 -1
  74. package/dist/modules/apimapper/graph-builder.d.ts +85 -2
  75. package/dist/modules/apimapper/graph-builder.js +151 -15
  76. package/dist/modules/apimapper/graph-builder.js.map +1 -1
  77. package/dist/modules/apimapper/graph.js +152 -48
  78. package/dist/modules/apimapper/graph.js.map +1 -1
  79. package/dist/modules/apimapper/index.js +27 -13
  80. package/dist/modules/apimapper/index.js.map +1 -1
  81. package/dist/modules/apimapper/jmespath-test.d.ts +4 -0
  82. package/dist/modules/apimapper/jmespath-test.js +152 -0
  83. package/dist/modules/apimapper/jmespath-test.js.map +1 -0
  84. package/dist/modules/apimapper/library.js +553 -88
  85. package/dist/modules/apimapper/library.js.map +1 -1
  86. package/dist/modules/apimapper/license.js +12 -36
  87. package/dist/modules/apimapper/license.js.map +1 -1
  88. package/dist/modules/apimapper/list-footer.d.ts +27 -0
  89. package/dist/modules/apimapper/list-footer.js +57 -0
  90. package/dist/modules/apimapper/list-footer.js.map +1 -0
  91. package/dist/modules/apimapper/local-sources.js +100 -57
  92. package/dist/modules/apimapper/local-sources.js.map +1 -1
  93. package/dist/modules/apimapper/mcp-client-identity.d.ts +32 -0
  94. package/dist/modules/apimapper/mcp-client-identity.js +70 -0
  95. package/dist/modules/apimapper/mcp-client-identity.js.map +1 -0
  96. package/dist/modules/apimapper/merge-constants.d.ts +6 -0
  97. package/dist/modules/apimapper/merge-constants.js +26 -0
  98. package/dist/modules/apimapper/merge-constants.js.map +1 -0
  99. package/dist/modules/apimapper/misc.js +13 -27
  100. package/dist/modules/apimapper/misc.js.map +1 -1
  101. package/dist/modules/apimapper/node-schema.d.ts +52 -2
  102. package/dist/modules/apimapper/node-schema.js +95 -4
  103. package/dist/modules/apimapper/node-schema.js.map +1 -1
  104. package/dist/modules/apimapper/onboarding.d.ts +59 -1
  105. package/dist/modules/apimapper/onboarding.js +231 -28
  106. package/dist/modules/apimapper/onboarding.js.map +1 -1
  107. package/dist/modules/apimapper/read-cache.d.ts +16 -3
  108. package/dist/modules/apimapper/read-cache.js +59 -4
  109. package/dist/modules/apimapper/read-cache.js.map +1 -1
  110. package/dist/modules/apimapper/render/index.js +26 -5
  111. package/dist/modules/apimapper/render/index.js.map +1 -1
  112. package/dist/modules/apimapper/resource-id.d.ts +13 -0
  113. package/dist/modules/apimapper/resource-id.js +69 -0
  114. package/dist/modules/apimapper/resource-id.js.map +1 -0
  115. package/dist/modules/apimapper/schema.js +9 -18
  116. package/dist/modules/apimapper/schema.js.map +1 -1
  117. package/dist/modules/apimapper/settings.js +49 -52
  118. package/dist/modules/apimapper/settings.js.map +1 -1
  119. package/dist/modules/apimapper/sites-tools.d.ts +29 -0
  120. package/dist/modules/apimapper/sites-tools.js +165 -0
  121. package/dist/modules/apimapper/sites-tools.js.map +1 -0
  122. package/dist/modules/apimapper/tool-result.d.ts +66 -0
  123. package/dist/modules/apimapper/tool-result.js +125 -0
  124. package/dist/modules/apimapper/tool-result.js.map +1 -0
  125. package/dist/modules/apimapper/toolslist-size.d.ts +12 -11
  126. package/dist/modules/apimapper/toolslist-size.js +34 -21
  127. package/dist/modules/apimapper/toolslist-size.js.map +1 -1
  128. package/dist/modules/apimapper/types.d.ts +34 -0
  129. package/dist/modules/apimapper/types.js +1 -1
  130. package/dist/modules/apimapper/types.js.map +1 -1
  131. package/dist/modules/apimapper/whitelist-drift.d.ts +85 -0
  132. package/dist/modules/apimapper/whitelist-drift.js +375 -0
  133. package/dist/modules/apimapper/whitelist-drift.js.map +1 -0
  134. package/dist/modules/apimapper/workflows.js +302 -58
  135. package/dist/modules/apimapper/workflows.js.map +1 -1
  136. package/dist/modules/apimapper/yootheme-binding.d.ts +35 -0
  137. package/dist/modules/apimapper/yootheme-binding.js +267 -0
  138. package/dist/modules/apimapper/yootheme-binding.js.map +1 -0
  139. package/dist/platform/index.d.ts +56 -0
  140. package/dist/platform/index.js +158 -2
  141. package/dist/platform/index.js.map +1 -1
  142. package/dist/proxy/bridge.d.ts +35 -0
  143. package/dist/proxy/bridge.js +129 -0
  144. package/dist/proxy/bridge.js.map +1 -0
  145. package/dist/proxy/mode.d.ts +9 -0
  146. package/dist/proxy/mode.js +20 -0
  147. package/dist/proxy/mode.js.map +1 -0
  148. package/dist/setup/detect-clients.d.ts +40 -1
  149. package/dist/setup/detect-clients.js +148 -1
  150. package/dist/setup/detect-clients.js.map +1 -1
  151. package/dist/setup/probe-auth.d.ts +51 -0
  152. package/dist/setup/probe-auth.js +141 -0
  153. package/dist/setup/probe-auth.js.map +1 -0
  154. package/dist/setup/probe-handshake.js +40 -7
  155. package/dist/setup/probe-handshake.js.map +1 -1
  156. package/dist/setup/remove-config.d.ts +8 -0
  157. package/dist/setup/remove-config.js +145 -0
  158. package/dist/setup/remove-config.js.map +1 -0
  159. package/dist/setup/uninstall.d.ts +34 -0
  160. package/dist/setup/uninstall.js +147 -0
  161. package/dist/setup/uninstall.js.map +1 -0
  162. package/dist/setup-cli.d.ts +16 -0
  163. package/dist/setup-cli.js +63 -1
  164. package/dist/setup-cli.js.map +1 -1
  165. package/dist/sites/loader.d.ts +48 -0
  166. package/dist/sites/loader.js +134 -0
  167. package/dist/sites/loader.js.map +1 -0
  168. package/dist/sites/schema.d.ts +69 -0
  169. package/dist/sites/schema.js +71 -0
  170. package/dist/sites/schema.js.map +1 -0
  171. package/dist/sites/secret-resolver.d.ts +47 -0
  172. package/dist/sites/secret-resolver.js +150 -0
  173. package/dist/sites/secret-resolver.js.map +1 -0
  174. package/dist/skill-instructions.d.ts +14 -1
  175. package/dist/skill-instructions.js +35 -6
  176. package/dist/skill-instructions.js.map +1 -1
  177. package/dist/transports/stdio.js +4 -4
  178. package/dist/transports/stdio.js.map +1 -1
  179. package/dist/uninstall-skill.d.ts +27 -0
  180. package/dist/uninstall-skill.js +89 -0
  181. package/dist/uninstall-skill.js.map +1 -0
  182. package/docs/architecture.md +21 -21
  183. package/docs/customgraph-internal-migration.md +4 -4
  184. package/docs/security.md +2 -21
  185. package/docs/tools.md +40 -12
  186. package/manifest.json +77 -79
  187. package/package.json +69 -65
  188. package/skills/apimapper/SKILL.md +128 -7
  189. package/skills/apimapper/reference/conditional-style-multi-items.md +114 -0
  190. package/skills/apimapper/reference/dynamize-existing-layout.md +158 -0
  191. package/skills/apimapper/reference/jmespath-cookbook.md +241 -0
  192. package/skills/apimapper/reference/jmespath-pitfalls.md +189 -0
  193. package/skills/apimapper/reference/joomla.md +1 -1
  194. package/skills/apimapper/reference/library-template-discovery.md +65 -0
  195. package/skills/apimapper/reference/merge-two-sources-on-key.md +204 -0
  196. package/skills/apimapper/reference/oauth.md +143 -52
  197. package/skills/apimapper/reference/troubleshooting.md +22 -2
  198. package/skills/apimapper/reference/yootheme-source-to-builder-handoff.md +348 -0
  199. package/skills/apimapper/reference/yootheme.md +75 -44
  200. package/dist/auth/oauth-provider.d.ts +0 -68
  201. package/dist/auth/oauth-provider.js +0 -232
  202. package/dist/auth/oauth-provider.js.map +0 -1
  203. package/dist/server-http.d.ts +0 -22
  204. package/dist/server-http.js +0 -159
  205. package/dist/server-http.js.map +0 -1
  206. package/dist/transports/http.d.ts +0 -29
  207. package/dist/transports/http.js +0 -267
  208. package/dist/transports/http.js.map +0 -1
package/package.json CHANGED
@@ -1,67 +1,71 @@
1
1
  {
2
- "name": "@wootsup/mcp",
3
- "version": "0.1.0",
4
- "type": "module",
5
- "description": "API Mapper MCP Server — multi-platform (WordPress/Joomla), multi-AI-client, Stripe-style auth, bundled skills",
6
- "license": "MIT",
7
- "author": "WootsUp <hello@wootsup.com>",
8
- "homepage": "https://wootsup.com",
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/wootsup/api-mapper.git",
12
- "directory": "packages/apimapper-mcp"
13
- },
14
- "bugs": "https://github.com/wootsup/api-mapper/issues",
15
- "keywords": [
16
- "mcp",
17
- "model-context-protocol",
18
- "api-mapper",
19
- "wordpress",
20
- "joomla",
21
- "claude",
22
- "chatgpt",
23
- "cursor"
24
- ],
25
- "publishConfig": {
26
- "access": "public"
27
- },
28
- "bin": {
29
- "apimapper-mcp": "./dist/index.js"
30
- },
31
- "files": [
32
- "dist/",
33
- "skills/",
34
- "docs/",
35
- "manifest.json",
36
- "CHANGELOG.md",
37
- "SECURITY.md"
38
- ],
39
- "scripts": {
40
- "build": "tsc && chmod +x dist/index.js 2>/dev/null || true",
41
- "test": "vitest run",
42
- "test:watch": "vitest",
43
- "test:coverage": "vitest run --coverage",
44
- "dev": "tsx watch src/index.ts",
45
- "typecheck": "tsc --noEmit",
46
- "start:http": "node dist/server-http.js",
47
- "build:dxt": "node scripts/build-dxt.js",
48
- "token-baseline": "node scripts/token-baseline.mjs"
49
- },
50
- "dependencies": {
51
- "@clack/prompts": "^0.10.0",
52
- "@getimo/mcp-toolkit": "^1.1.1",
53
- "@modelcontextprotocol/sdk": "^1.27.0",
54
- "@napi-rs/keyring": "^1.3.0",
55
- "undici": "^7.25.0",
56
- "zod": "^4.3.6"
57
- },
58
- "devDependencies": {
59
- "@types/archiver": "^7.0.0",
60
- "@types/node": "^22.0.0",
61
- "@vitest/coverage-v8": "~2.1.9",
62
- "archiver": "^8.0.0",
63
- "tsx": "^4.21.0",
64
- "typescript": "^5.6.0",
65
- "vitest": "^2.1.0"
66
- }
2
+ "name": "@wootsup/mcp",
3
+ "version": "0.4.0",
4
+ "type": "module",
5
+ "description": "API Mapper MCP Server — multi-platform (WordPress/Joomla), multi-AI-client, Stripe-style auth, bundled skills",
6
+ "license": "MIT",
7
+ "author": "WootsUp <hello@wootsup.com>",
8
+ "homepage": "https://wootsup.com",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/wootsup/api-mapper.git",
12
+ "directory": "packages/apimapper-mcp"
13
+ },
14
+ "bugs": "https://wootsup.com/contact/",
15
+ "keywords": [
16
+ "mcp",
17
+ "model-context-protocol",
18
+ "api-mapper",
19
+ "wordpress",
20
+ "joomla",
21
+ "claude",
22
+ "chatgpt",
23
+ "cursor"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "bin": {
29
+ "apimapper-mcp": "./dist/index.js"
30
+ },
31
+ "files": [
32
+ "dist/",
33
+ "skills/",
34
+ "docs/",
35
+ "manifest.json",
36
+ "CHANGELOG.md",
37
+ "SECURITY.md"
38
+ ],
39
+ "scripts": {
40
+ "prepublishOnly": "node -e \"if (process.env.RELEASE_PHP_PUBLISH !== '1') { console.error('Manual npm publish is no longer supported. Use: php scripts/release.php release apimapper-mcp <bump> followed by php scripts/release.php push-tag apimapper-mcp <version>, which triggers the GitHub Actions publish workflow (.github/workflows/apimapper-mcp-publish.yml). Set RELEASE_PHP_PUBLISH=1 only if you know you are running from that workflow.'); process.exit(1); }\"",
41
+ "build": "tsc && chmod +x dist/index.js 2>/dev/null || true",
42
+ "export:catalog": "npm run build && node scripts/export-catalog.mjs",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest",
45
+ "test:coverage": "vitest run --coverage",
46
+ "dev": "tsx watch src/index.ts",
47
+ "typecheck": "tsc --noEmit",
48
+ "build:dxt": "node scripts/build-dxt.js",
49
+ "bump": "node scripts/bump-version.mjs",
50
+ "token-baseline": "node scripts/token-baseline.mjs"
51
+ },
52
+ "dependencies": {
53
+ "@clack/prompts": "^0.10.0",
54
+ "@getimo/mcp-toolkit": "^1.1.1",
55
+ "@modelcontextprotocol/sdk": "^1.27.0",
56
+ "@napi-rs/keyring": "^1.3.0",
57
+ "undici": "^7.25.0",
58
+ "zod": "^4.3.6"
59
+ },
60
+ "devDependencies": {
61
+ "@types/archiver": "^7.0.0",
62
+ "@types/node": "^22.0.0",
63
+ "@vitest/coverage-v8": "~2.1.9",
64
+ "ajv": "^8.20.0",
65
+ "archiver": "^8.0.0",
66
+ "esbuild": "^0.28.1",
67
+ "tsx": "^4.21.0",
68
+ "typescript": "^5.6.0",
69
+ "vitest": "^2.1.0"
70
+ }
67
71
  }
@@ -10,7 +10,7 @@ allowed-tools:
10
10
 
11
11
  # API Mapper
12
12
 
13
- Bridge any REST API into YOOtheme via a saved flow (Source → Filter → Transform → Output). One server, one Bearer key, 17-tool gateway surface (77 capabilities behind `apimapper_advanced`).
13
+ Bridge any REST API into YOOtheme via a saved flow (Source → Filter → Transform → Output). One server, one Bearer key, 20-tool surface; 60 more behind `apimapper_advanced`; 80 total.
14
14
 
15
15
  ## Quickstart
16
16
 
@@ -19,8 +19,8 @@ Bridge any REST API into YOOtheme via a saved flow (Source → Filter → Transf
19
19
  npx -y @wootsup/mcp setup
20
20
  ```
21
21
  Interactive wizard: paste your MCP key, pick the AI client(s) to configure
22
- (Claude Desktop, Cursor, Zed, Continue, Cline, Roo Code), wizard writes
23
- the config files for you.
22
+ (Claude Desktop, Claude Code, Cursor, VS Code, Cline, Codex CLI), wizard
23
+ writes the config files for you.
24
24
 
25
25
  2. **Generate a key**
26
26
  - WordPress: click *API Mapper* in the WP admin menu to open the editor.
@@ -36,22 +36,143 @@ Bridge any REST API into YOOtheme via a saved flow (Source → Filter → Transf
36
36
  apimapper_onboarding → lists platform, existing flows
37
37
  ```
38
38
 
39
+ ## Step 1 (mandatory): Check the library FIRST
40
+
41
+ Before reaching for `apimapper_connection_create`, look in the library. Most
42
+ common APIs already have curated templates with OAuth wizard, auto-header
43
+ detection, and a YOOtheme source schema baked in.
44
+
45
+ ```
46
+ apimapper_library_featured() # the top featured templates
47
+ apimapper_library_list({ search: 'sheets' }) # search for any API by name (param is `search`, 3+ chars)
48
+ apimapper_credential_list({}) # ALWAYS check credentials BEFORE activating an auth-protected template
49
+ apimapper_library_activate({ id: '<template-id>', credential_id: '<cred-id>' }) # the canonical activation path
50
+ ```
51
+
52
+ **Auth-protected templates (Google Sheets, Notion, Airtable, Pexels, OpenWeatherMap,
53
+ Calendly, GitHub, …): you MUST link a credential.** The activation tool auto-links
54
+ when exactly ONE matching credential exists for the template's provider; otherwise
55
+ it returns a structured `credential_required` / `credential_ambiguous` error
56
+ listing the candidates. Always call `apimapper_credential_list({})` first, then
57
+ pass the right `credential_id` explicitly to `library_activate`. Missing the
58
+ credential silently lands a broken connection (empty endpoint, no auth) that
59
+ returns empty data and is hard to debug from downstream tool calls.
60
+
61
+ Templates ship today for: **Google Sheets, Calendly, Notion, Airtable, GitHub,
62
+ Pexels, Unsplash, OpenWeatherMap, REST Countries, Google Drive/Docs/Slides/
63
+ Tasks**, and more. Library activations come with:
64
+ - OAuth wizard (no manual app-registration pain)
65
+ - Auto-header detection for spreadsheet sources (Google Sheets!)
66
+ - Curated YOOtheme source schema (no positional-field-name footguns)
67
+ - Pre-configured caching and items_path
68
+
69
+ ## Step 2: Custom connection_create — ONLY when no template fits
70
+
71
+ If `library_list({ search: '<api-name>' })` returns no match, fall back to
72
+ `apimapper_connection_create` for niche or unknown APIs.
73
+
74
+ **Server-enforced (not just prose).** A custom `connection_create` whose
75
+ endpoint host is covered by a curated template is rejected with HTTP 409
76
+ `error_code: library_template_available`; the error message names the template
77
+ and the exact `apimapper_library_activate({ id })` call, and a structured
78
+ `library_suggestion` ({matched_host, templates[], activate_call}) accompanies
79
+ it. When the endpoint is genuinely NOT covered (a niche/write/webhook call on a
80
+ covered host), retry `connection_create` with `acknowledge_no_library: true` —
81
+ the audited override. Always prefer activation.
82
+
39
83
  ## Common Tools
40
84
 
41
85
  | Tool | Use for |
42
86
  |------|---------|
43
- | `apimapper_connection_create` | New REST/OAuth source |
87
+ | `apimapper_library_featured` | **Start here** top templates, mandatory first call |
88
+ | `apimapper_library_list` | Search/browse the library by name or category |
89
+ | `apimapper_library_activate` | Activate a featured template (canonical path) |
90
+ | `apimapper_connection_create` | Custom connection — only when no template matches |
44
91
  | `apimapper_flow_setup_with_sources` | One-shot connection+flow+publish |
45
92
  | `apimapper_flow_compile` | Validate graph before publish |
46
- | `apimapper_library_activate` | Drop a featured template |
47
93
  | `apimapper_diagnose` | Triage 401/404/5xx |
48
94
 
49
95
  For topic docs: `apimapper_get_skill topic="..."` or read `skill://apimapper/<topic>`.
50
96
 
97
+ ## Dynamize an existing designed layout ("keep the design exactly as it is")
98
+
99
+ When the customer points at an EXISTING hand-designed section/page and wants it
100
+ fed from your flow — *"bind these fields onto my section"*, *"the design must
101
+ not change"*, *"use the layout from my X page"* — there is exactly one tool.
102
+
103
+ > **Iron Law:** Dynamize an existing design ⇒ `yootheme_builder_page_dynamize`.
104
+ > NEVER hand-edit layout JSON. NEVER `element_add` a "rebuild".
105
+
106
+ The tool copies the reference subtree byte-identically server-side, swaps only
107
+ the names you map, inserts nothing, rebuilds nothing, and publishes. You supply
108
+ language-level inputs only.
109
+
110
+ ### The 3-step procedure
111
+
112
+ 1. **FIND the section** — `yootheme_builder_template_summary` → `named_sections`.
113
+ The customer's "X page" is often a NAMED SECTION inside another article, not
114
+ its own template. Copy the customer's NAMED section — never a category
115
+ template or a sibling mobile/card variant.
116
+ 2. **READ the flow** — `apimapper_yootheme_binding_for_flow({ flow_id })` →
117
+ the source name (`apiMapperFlow<Id>List`) + the flat field list.
118
+ 3. **DYNAMIZE — ONE call:**
119
+ ```jsonc
120
+ yootheme_builder_page_dynamize({
121
+ template_id, section_path,
122
+ source_name: "apiMapperFlowFcMatchesList", // step 2
123
+ leaf_map: {
124
+ "field.home_team.crest.imagefile": "home_crest", // nested original → flat flow field
125
+ "field.home_team.name": "home_name",
126
+ "field.away_team.name": "away_name",
127
+ "field.match_date": "match_time"
128
+ },
129
+ publish: true
130
+ })
131
+ ```
132
+ FC-Greenfield: the flow emits FLAT records; the original binds NESTED
133
+ relational leaves. That mismatch is NOT a reason to rebuild — `leaf_map` just
134
+ renames leaves. Unmapped leaves are KEPT. Check `summary.unmatched_map_keys`
135
+ in the response.
136
+
137
+ Full recipe (read before executing): `apimapper_get_skill topic="dynamize-existing-layout"`.
138
+
139
+ ### Rationalization table
140
+
141
+ | Excuse | Reality |
142
+ |--------|---------|
143
+ | "My flow is flat but the design binds nested fields — I'll rebuild it" (R1) | Structure + every tuned prop transfer verbatim; `leaf_map` only renames leaves. Rebuilt-by-hand shipped 213px rows vs the 90px original. Copy, don't rebuild. |
144
+ | "I'll add a column/slot for the league position" (R3) | INSERT NOTHING. Extra data goes INTO an existing leaf (compose `"AFC Richmond (3rd)"` in the flow). An inserted column collapsed sibling cells 213px→55px; names wrapped, crests overlapped. |
145
+ | "I'll copy whichever row variant I find first" (R4a) | A section holds a desktop single-line row AND a mobile stacked card. Copying the card ships a 233px two-tier block. Copy the variant VISIBLE at the customer's desktop viewport (read `visibility` props). |
146
+ | "This text field is close enough for the image slot" (R4b) | Binding a TEXT field (competition NAME) to an IMAGE prop renders a broken 0x0 image. Map image leaves to image/path fields only. Never invent an asset path — a guessed path 404s silently. |
147
+ | "I'll copy from the category template, it looks the same" (R5) | The category template is NOT the customer's section — copying it shipped a 2-column masonry with missing date/matchday and 40px crests. Copy ONLY the customer's NAMED section from `template_summary`. |
148
+
149
+ ### Red flags — STOP
150
+
151
+ - About to call `element_add` / `element_clone` while dynamizing → STOP.
152
+ - About to write or hand-patch layout JSON → STOP, use the one call.
153
+ - About to bind a TEXT field to an IMAGE prop → STOP, wrong field type.
154
+ - About to invent an asset/image path you never read → STOP, it will 404.
155
+ - About to copy a category template or mobile-card variant → STOP, copy the customer's named desktop section.
156
+
157
+ All of these mean: one `yootheme_builder_page_dynamize` call against the right section.
158
+
159
+ ### Map COMPLETELY, then self-check
160
+
161
+ Map EVERY bound leaf the section uses (incl. secondary slots like the TIME
162
+ filter on a date leaf — a date-only field renders '0:00 AM'). After the call,
163
+ read `summary`: `unmatched_map_keys` = your typos; `kept_unresolvable` = leaves
164
+ that WILL render empty — extend `leaf_map` and re-run until both are empty.
165
+
166
+ ### Before you declare success
167
+
168
+ Render the published URL and visually compare ONE row against the original at
169
+ the customer's viewport. Same row height, no new wraps, images same size, same
170
+ highlight count. "Similar but looser" = you rebuilt instead of copied — fail.
171
+
51
172
  ## Common Pitfalls
52
173
 
53
174
  - **Source not in YOOtheme Builder** → flow is *saved* but not *published*. Click Publish. Published name (not "API Mapper") appears in YOOtheme Source dropdown.
54
175
  - **Joomla 404 on `/wp-json/...`** → Joomla wraps in `com_ajax`. See `apimapper_get_skill topic="joomla"`.
55
- - **OAuth expired** → use `apimapper_credential_link` to re-auth, NOT delete-and-recreate (loses `refresh_token`).
176
+ - **OAuth expired** → use `apimapper_oauth_authorize_begin` to re-auth (re-runs the OAuth2 authorize flow on the existing credential), NOT delete-and-recreate (loses `refresh_token`). `apimapper_credential_link` only *attaches* a credential to a connection; it does not re-authorize.
56
177
 
57
- Full docs: `wootsup.com/docs/mcp/`
178
+ Full docs: `https://wootsup.com/docs/mcp/api-mapper/`
@@ -0,0 +1,114 @@
1
+ # Conditional Style on Multi-Items
2
+
3
+ How to drive per-row YOOtheme styles (panel, card, text, button, label) from a
4
+ row field, and where the YT-Pro enums trap you. This topic exists because of
5
+ two real Cold-AI sessions that wired customers into infinite loops with
6
+ `text_style: 'primary'` (not a valid enum) and lost an hour debugging "why is
7
+ the style not changing".
8
+
9
+ The short version:
10
+
11
+ - `text_style` does **not** accept `'primary'`. Use `text_color: 'primary'`
12
+ on the `text` element, or wrap it in a `grid_item` / `panel` whose
13
+ `panel_style` is `'primary'`.
14
+ - Conditional values come from JMESPath in the Transform stage and bind to
15
+ the Multi-Items element via the row's expression slot (`${item.foo}`).
16
+
17
+ ## Canonical YT-Pro 4.5 enum table
18
+
19
+ The values below are pinned against YOOtheme Pro 4.5
20
+ `wp-content/themes/yootheme/packages/builder/elements/<element>/element.json`.
21
+
22
+ | Property | Valid values | Where it lives |
23
+ |-----------------|-------------------------------------------------------------------------------|------------------------------------------------------|
24
+ | `text_style` | `""`, `"meta"`, `"lead"`, `"small"`, `"large"` | `text` element |
25
+ | `text_color` | `""`, `"muted"`, `"emphasis"`, `"primary"`, `"secondary"`, `"success"`, `"warning"`, `"danger"` | `text`, `heading`, `subtitle` |
26
+ | `panel_style` | `""`, `"default"`, `"primary"`, `"secondary"` | `grid_item`, `panel`, `card` (legacy) wrappers |
27
+ | `card_style` | `""`, `"default"`, `"primary"`, `"secondary"`, `"hover"` | `card` element |
28
+ | `button_style` | `"default"`, `"primary"`, `"secondary"`, `"danger"`, `"text"`, `"link"` | `button` element |
29
+ | `label_style` | `""`, `"success"`, `"warning"`, `"danger"` | `label` element |
30
+
31
+ ### Trap: `text_style: 'primary'` is not a thing
32
+
33
+ `text_style` is a typographic-role enum (caption, lead paragraph etc.), not a
34
+ color scheme. If you want the body text emphasized in the theme's primary
35
+ color, you have two correct routes:
36
+
37
+ 1. **Cheap route: `text_color: 'primary'` on the `text` element.**
38
+ The text element supports both `text_style` (size/weight) and
39
+ `text_color` (theme color slot). They compose — `text_style: 'lead'` +
40
+ `text_color: 'primary'` is valid.
41
+
42
+ 2. **Heavy route: wrap in `grid_item` with `panel_style: 'primary'`.**
43
+ This gives the whole row a primary-themed panel background and applies
44
+ the YT-Pro panel typography ramp. Use this when the cell needs a colored
45
+ pill/card around it, not just colored text.
46
+
47
+ If you see `requires_confirm: true` looping or you get an unhelpful YT-Pro
48
+ "unknown style" warning in the page render, this is the first thing to check.
49
+
50
+ ## Recipe A — text_color from row field
51
+
52
+ Bind the row field `priority` to the text element's `text_color`. The row's
53
+ JMESPath transform precomputes a valid enum value so the binding is just
54
+ `${item.color}`.
55
+
56
+ ```jmespath
57
+ items[].{
58
+ title: title,
59
+ body: description,
60
+ color: (priority == `high` && `danger`) ||
61
+ (priority == `medium` && `warning`) ||
62
+ `muted`
63
+ }
64
+ ```
65
+
66
+ In the YT-Pro Multi-Items element layout, the `text` child binds:
67
+
68
+ ```
69
+ title → ${item.title}
70
+ content → ${item.body}
71
+ text_color → ${item.color}
72
+ text_style → "lead"
73
+ ```
74
+
75
+ Note `text_style` stays a literal (`"lead"`) — only `text_color` varies per
76
+ row. Both compose cleanly.
77
+
78
+ ## Recipe B — panel_style wrapper from row field
79
+
80
+ Use this when each row needs a colored card or pill background, not just
81
+ colored text. The wrapper is a `grid_item` with its `panel_style` bound to
82
+ the row's color.
83
+
84
+ ```jmespath
85
+ items[].{
86
+ title: title,
87
+ body: description,
88
+ panelKey: (status == `error` && `primary`) ||
89
+ (status == `warn` && `secondary`) ||
90
+ `default`
91
+ }
92
+ ```
93
+
94
+ In the Multi-Items layout, the `grid_item` wrapping the row content binds:
95
+
96
+ ```
97
+ panel_style → ${item.panelKey}
98
+ panel_card → "default" (literal — keeps card chrome consistent)
99
+ ```
100
+
101
+ Inside that grid_item, the `text` child stays color-neutral
102
+ (`text_color: ""`, `text_style: ""`) so the panel's own typography applies.
103
+
104
+ ## Why `text_style: 'primary'` keeps tripping AIs
105
+
106
+ Customers (and AIs) generalize from the `panel_style` / `card_style` /
107
+ `button_style` precedent: those all accept `'primary'`. `text_style` looks
108
+ like the same family, but it isn't — it's a holdover from the original
109
+ UIkit text-role API where roles are `lead`/`meta`/`small`/`large`. YT-Pro's
110
+ "is this row important?" intent belongs in `text_color` or `panel_style`,
111
+ never in `text_style`.
112
+
113
+ When in doubt: prefer Recipe A (cheap, composes with everything), upgrade to
114
+ Recipe B only when the row needs visible chrome.
@@ -0,0 +1,158 @@
1
+ # Dynamize an existing designed layout (keep the design EXACTLY as it is)
2
+
3
+ Customer says: *"bind these fields onto my existing section"*, *"keep the design
4
+ exactly as it is"*, *"use the layout from my X page"*, *"the design must not
5
+ change"*. This topic is THE recipe for that job. It exists because two cold
6
+ agents failed it the same way before it was written: one **rebuilt** the design
7
+ element-by-element, one **inserted** an extra slot into the copied layout — both
8
+ shipped visually broken pages that a human caught instantly.
9
+
10
+ ## The one rule
11
+
12
+ **COPY the reference layout subtree byte-identically. Change ONLY leaf field
13
+ names. Insert nothing. Rebuild nothing.**
14
+
15
+ A designed YOOtheme section carries dozens of tuned props (`title_grid_width`,
16
+ `image_align`, grid breakpoints, column `layout` strings) you cannot guess. Any
17
+ hand-built "equivalent" renders visibly different — measured failures: 213px
18
+ row height vs the original's 90px; one inserted column collapsed sibling cells
19
+ from 213px to 55px so names wrapped and images overlapped text.
20
+
21
+ ## Pick the RIGHT variant before copying (F99)
22
+
23
+ A designed section often contains MULTIPLE sibling variants of the same row —
24
+ typically a compact single-line DESKTOP row AND a taller stacked CARD for
25
+ mobile, switched via visibility props (`visibility: 'hidden@m'` etc.). Copying
26
+ the stacked card produces a 233px two-tier block where the customer expects
27
+ the 90px single-line row (measured failure). Before copying:
28
+
29
+ 1. List the candidate subtrees and read their `visibility` props.
30
+ 2. Copy the variant that is VISIBLE at the customer's desktop viewport —
31
+ usually the one hidden on small screens, not the one hidden on `@m`+.
32
+ 3. If unsure, copy ALL sibling variants together with their visibility props
33
+ intact (they are part of the design) — never just one of them.
34
+
35
+ And when a leaf binds an IMAGE, map it to an image/path field — binding a TEXT
36
+ field (e.g. a competition NAME) to an image prop renders a broken 0x0 image.
37
+
38
+ ## The loop — TWO discovery steps, then ONE call
39
+
40
+ Steps 1-2 stay (you must tell the tool WHICH section and WHICH flow). Steps 3-5
41
+ of the old hand-transform loop collapse into a **single
42
+ `yootheme_builder_page_dynamize` call** — the server runs the deterministic
43
+ copy-swap-write-publish itself. You supply only the language-level inputs (which
44
+ section, which flow, and the leaf→field name map). This is the *hybrid
45
+ execution* path: the recipe is now CODE, not a 100-call hand-edit.
46
+
47
+ ```
48
+ 1. FIND yootheme_builder_template_summary → named_sections
49
+ (the customer's "X page" is often a NAMED SECTION inside another
50
+ article, not its own template; this is where the design lives)
51
+ 2. READ apimapper_yootheme_binding_for_flow({ flow_id }) → the source name
52
+ (source_name_list = apiMapperFlow<Id>List) + the flat field list you
53
+ map leaves onto. (No page_get_layout hand-copy needed — the tool
54
+ reads the layout itself.)
55
+ 3. DYNAMIZE — ONE call:
56
+ yootheme_builder_page_dynamize({
57
+ template_id, section_path,
58
+ source_name: "apiMapperFlow<Id>List",
59
+ leaf_map: { "<original leaf field path>": "<your flat flow field>", … },
60
+ publish: true
61
+ })
62
+ The server copies the section subtree byte-identically, swaps the
63
+ iterating query name + every leaf field name from leaf_map, inserts
64
+ nothing, rebuilds nothing, and publishes. One call replaces the old
65
+ SWAP+WRITE+(manual)PUBLISH steps.
66
+ 4. VERIFY render the public URL and compare ONE row against the original at
67
+ the customer's viewport — wrap/overlap/missing icons are failures
68
+ even when the data is right.
69
+ ```
70
+
71
+ ### Worked example — the FC-Greenfield case
72
+
73
+ The customer hand-designed a "Matches" section: each row is a single-line panel
74
+ with a home crest, the two team names, a league-position meta, a kickoff time,
75
+ and a "next match" highlight row. The flow (`flow_FcMatches`) emits a FLAT
76
+ record — `home_crest`, `home_name`, `away_name`, `match_time`, `next` — while the
77
+ original layout binds NESTED relational leaves
78
+ (`field.home_team.crest.imagefile`, `field.home_team.name`, …). That mismatch is
79
+ NOT a reason to rebuild; `leaf_map` just renames the leaves:
80
+
81
+ ```jsonc
82
+ yootheme_builder_page_dynamize({
83
+ template_id: "tpl_home", // from template_summary
84
+ section_path: ".../section/matches", // the named section that holds the rows
85
+ source_name: "apiMapperFlowFcMatchesList", // binding_for_flow → source_name_list
86
+ leaf_map: {
87
+ // nested ORIGINAL leaf → your FLAT flow field
88
+ "field.home_team.crest.imagefile": "home_crest",
89
+ "field.home_team.name": "home_name",
90
+ "field.away_team.name": "away_name",
91
+ "field.match_date": "match_time",
92
+ "field.is_next": "next" // string '' / 'next' → highlight via _condition '!!'
93
+ },
94
+ publish: true
95
+ })
96
+ ```
97
+
98
+ The tool keeps the `#parent` repeater form and every tuned prop
99
+ (`title_grid_width`, `image_align`, grid breakpoints, column `layout` strings)
100
+ byte-identical; only the names inside `leaf_map` change. A leaf you have no flow
101
+ field for is simply left OUT of `leaf_map` — the tool keeps its original binding
102
+ (default KEEP). Per-row visibility (the one red "next" row) rides on the `next`
103
+ string field via the copied `_condition` with `filters.condition: '!!'`.
104
+
105
+ > **When `page_dynamize` is not available** (older yt-builder host, or you need a
106
+ > shape it doesn't yet author): fall back to the hand-transform — read the
107
+ > subtree with `yootheme_builder_page_get_layout`, apply the swaps in the JSON
108
+ > yourself, and write it with `yootheme_builder_pages_create({ layout })`. The
109
+ > `yootheme-source-to-builder-handoff` topic documents that `#parent`-JSON
110
+ > anatomy in full. The hard rules below are exactly what the tool guarantees by
111
+ > construction; if you hand-edit, you must honour them yourself.
112
+
113
+ ## Hard rules (the tool guarantees these by construction)
114
+
115
+ `yootheme_builder_page_dynamize` enforces every rule in this section
116
+ automatically — it copies the subtree, swaps only the `leaf_map` names, inserts
117
+ nothing, and publishes. They are kept here as **background**: so you understand
118
+ WHAT a design-exact dynamization is, and so that if you ever hand-edit the layout
119
+ instead (the fallback above), you follow them yourself.
120
+
121
+ These rules were learned from real failures:
122
+
123
+ - **"My flow is FLAT but the original binds NESTED/relational fields" is NOT a
124
+ reason to rebuild.** Structure and props transfer verbatim; only leaf field
125
+ names change.
126
+ - **INSERT NOTHING.** Extra data (a league position, a status) goes INTO an
127
+ existing bound leaf — compose it in the flow (`"AFC Richmond (3rd)"` as the
128
+ name value), never as a new element/column.
129
+ - **Leaves you have no flow field for: default KEEP.** A static icon keeps its
130
+ original binding (or gets a flow constant). REMOVE a leaf only when its
131
+ TARGET cannot exist for the new data (a detail-link whose article doesn't
132
+ exist for API rows). Ask: "does this leaf's target still exist for my data?"
133
+ - **Never invent asset paths.** If you didn't read the path from the layout or
134
+ an API response, don't guess one — a guessed path 404s silently.
135
+ - **Per-row visibility (played vs upcoming vs highlight):** prefer emitting
136
+ string flags in the flow (`''`/`'next'`) and `_condition` with
137
+ `filters.condition: '!'`/`'!!'` in the copied JSON. If element-level tools
138
+ fight you, pre-filter in the FLOW (separate published lists per row group) —
139
+ that pattern passed a strict visual judge. The element-side bind/update tools
140
+ currently **cannot** author a filtered `_condition` (F95/F96/F97) — the
141
+ `yootheme-source-to-builder-handoff` topic's "Known tool limits when
142
+ authoring per-row visibility" section has the verified detail and the
143
+ pre-filtered-flows workaround.
144
+ - The flow data work (connections, join, transforms) is unchanged by this
145
+ topic — see `merge-two-sources-on-key` and `jmespath-cookbook`.
146
+
147
+ ## Before you declare success — checklist
148
+
149
+ - [ ] Section located via `template_summary` → `named_sections` (it may live
150
+ inside another article, not its own template)
151
+ - [ ] Source name + flat fields read from `apimapper_yootheme_binding_for_flow`
152
+ - [ ] Dynamization done in ONE `yootheme_builder_page_dynamize` call (or, on the
153
+ hand-edit fallback, the layout JSON came from `page_get_layout` — never
154
+ hand-built — with zero elements added/removed without a documented reason)
155
+ - [ ] `leaf_map` covers every leaf you have a flow field for; unmapped leaves are
156
+ left KEPT (default), removed only with a "target cannot exist" reason
157
+ - [ ] Published + routed (anonymous visitors can load it)
158
+ - [ ] One row visually compared against the original at the target viewport