@diviops/mcp-server 1.5.10 → 1.5.11

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.
@@ -0,0 +1,70 @@
1
+ /**
2
+ * `$variable()` color-token helpers for the preset-emitter CLI.
3
+ *
4
+ * Divi 5.5.x color bindings use the token grammar:
5
+ *
6
+ * $variable({"type":"color","value":{"name":"gcid-heading-color","settings":{}}})$
7
+ *
8
+ * Two load-bearing rules (both from VB-verified memory):
9
+ * - The token MUST end with `)$` — a missing trailing `$` causes silent
10
+ * render failure (`feedback_variable_trailing_dollar`).
11
+ * - The `value` payload MUST be an object `{name, settings}`, not a flat
12
+ * `gcid-*` string — a flat string crashes PHP 8 in Divi's DynamicData
13
+ * resolver (`feedback_variable_color_value_object_shape`).
14
+ *
15
+ * The CLI accepts a color param that is EITHER a literal (a hex string,
16
+ * or an already-formed `$variable(...)$` token) OR a bare `gcid-*` /
17
+ * `gvid-*` token name, which this module wraps into the canonical shape.
18
+ */
19
+ /** A bare Divi variable token name (gcid-* or gvid-*). */
20
+ const BARE_TOKEN_RE = /^(gcid|gvid)-[A-Za-z0-9_-]+$/;
21
+ /** An already-formed `$variable(...)$` token. */
22
+ export function isVariableToken(value) {
23
+ return value.startsWith("$variable(") && value.endsWith(")$");
24
+ }
25
+ /** A bare token name like `gcid-primary-color`. */
26
+ export function isBareTokenName(value) {
27
+ return BARE_TOKEN_RE.test(value);
28
+ }
29
+ /**
30
+ * Build a canonical `$variable()` color token from a bare token name.
31
+ *
32
+ * The inner JSON uses single-escape `\"` when the whole preset is later
33
+ * `JSON.stringify`-d — that is handled by JSON.stringify itself; here we
34
+ * return the raw string value (un-stringified), which is what belongs in
35
+ * the in-memory preset object.
36
+ */
37
+ export function buildColorVariableToken(tokenName) {
38
+ if (!isBareTokenName(tokenName)) {
39
+ throw new Error(`Invalid variable token name "${tokenName}". Expected a bare gcid-*/gvid-* name ` +
40
+ `(e.g. "gcid-primary-color"), a hex literal, or an already-formed $variable(...)$ token.`);
41
+ }
42
+ const payload = JSON.stringify({
43
+ type: "color",
44
+ value: { name: tokenName, settings: {} },
45
+ });
46
+ return `$variable(${payload})$`;
47
+ }
48
+ /**
49
+ * Normalize a CLI color param to the value that belongs in the preset.
50
+ *
51
+ * - An already-formed `$variable(...)$` token → returned verbatim (the
52
+ * caller is responsible for its correctness; the trailing `)$` is
53
+ * asserted).
54
+ * - A bare `gcid-*`/`gvid-*` name → wrapped into the canonical token.
55
+ * - Anything else (a hex string, `rgba(...)`, etc.) → returned verbatim
56
+ * as a literal color value.
57
+ */
58
+ export function normalizeColorValue(value) {
59
+ if (isVariableToken(value)) {
60
+ return value;
61
+ }
62
+ if (value.startsWith("$variable(") && !value.endsWith(")$")) {
63
+ throw new Error(`Malformed $variable() token "${value}" — must end with ")$" ` +
64
+ `(missing trailing "$" causes silent render failure).`);
65
+ }
66
+ if (isBareTokenName(value)) {
67
+ return buildColorVariableToken(value);
68
+ }
69
+ return value;
70
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Apply-mode write path for the preset-emitter CLI.
3
+ *
4
+ * Reuses the existing server WP-client conventions:
5
+ * - `WP_URL` / `WP_USER` / `WP_APP_PASSWORD` env vars.
6
+ * - `WPClient` from `wp-client.ts` for the HTTP + Basic-Auth machinery.
7
+ * - `WPClient.handshake()` for the plugin capability map.
8
+ *
9
+ * Before any write the CLI verifies the plugin handshake reports the
10
+ * `storage_multipath_probe_v1` capability (the storage-path capability
11
+ * contract, shipped in plugin 1.4.9). Absent → fail fast, before composing
12
+ * a write.
13
+ *
14
+ * `--dry-run` never reaches this module: it requires no credentials, no
15
+ * handshake, and no network.
16
+ */
17
+ import { WPClient } from "../wp-client.js";
18
+ import type { HandshakeResult } from "../compatibility.js";
19
+ import type { DiviopsResponse } from "../envelope.js";
20
+ import type { ButtonPresetEntry } from "./button-emitter.js";
21
+ /** The plugin capability the storage-path capability contract ships. */
22
+ export declare const STORAGE_CAPABILITY = "storage_multipath_probe_v1";
23
+ /** The REST route the CLI posts to — same route `diviops_preset_create` uses. */
24
+ export declare const PRESET_CREATE_ROUTE = "/preset/create";
25
+ /** Minimal client surface the write path needs — eased for mocking in tests. */
26
+ export interface PresetWriteClient {
27
+ handshake(serverVersion: string): Promise<HandshakeResult>;
28
+ requestEnveloped<T = unknown>(endpoint: string, options?: {
29
+ method?: string;
30
+ body?: Record<string, unknown>;
31
+ params?: Record<string, string>;
32
+ }): Promise<DiviopsResponse<T>>;
33
+ }
34
+ export declare class CredentialsMissingError extends Error {
35
+ constructor(missing: string[]);
36
+ }
37
+ export declare class CapabilityMissingError extends Error {
38
+ readonly capability: string;
39
+ readonly pluginVersion: string;
40
+ constructor(capability: string, pluginVersion: string);
41
+ }
42
+ /** Build a `WPClient` from the standard env vars, or throw if any are absent. */
43
+ export declare function buildClientFromEnv(env?: NodeJS.ProcessEnv): WPClient;
44
+ /**
45
+ * Verify the plugin handshake reports `storage_multipath_probe_v1`.
46
+ * Throws `CapabilityMissingError` if absent. Returns the handshake result
47
+ * so callers can surface the plugin version.
48
+ */
49
+ export declare function assertStorageCapability(client: PresetWriteClient, serverVersion: string): Promise<HandshakeResult>;
50
+ /**
51
+ * Apply a button preset: capability-gate, then POST to `/preset/create`.
52
+ *
53
+ * The capability check runs BEFORE the write. The write goes through the
54
+ * existing storage-routed route — no plugin route is added here.
55
+ */
56
+ export declare function applyButtonPreset(client: PresetWriteClient, entry: ButtonPresetEntry, opts: {
57
+ serverVersion: string;
58
+ dry_run?: boolean;
59
+ }): Promise<DiviopsResponse<unknown>>;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Apply-mode write path for the preset-emitter CLI.
3
+ *
4
+ * Reuses the existing server WP-client conventions:
5
+ * - `WP_URL` / `WP_USER` / `WP_APP_PASSWORD` env vars.
6
+ * - `WPClient` from `wp-client.ts` for the HTTP + Basic-Auth machinery.
7
+ * - `WPClient.handshake()` for the plugin capability map.
8
+ *
9
+ * Before any write the CLI verifies the plugin handshake reports the
10
+ * `storage_multipath_probe_v1` capability (the storage-path capability
11
+ * contract, shipped in plugin 1.4.9). Absent → fail fast, before composing
12
+ * a write.
13
+ *
14
+ * `--dry-run` never reaches this module: it requires no credentials, no
15
+ * handshake, and no network.
16
+ */
17
+ import { WPClient } from "../wp-client.js";
18
+ import { buildPresetCreateBody } from "./button-emitter.js";
19
+ /** The plugin capability the storage-path capability contract ships. */
20
+ export const STORAGE_CAPABILITY = "storage_multipath_probe_v1";
21
+ /** The REST route the CLI posts to — same route `diviops_preset_create` uses. */
22
+ export const PRESET_CREATE_ROUTE = "/preset/create";
23
+ export class CredentialsMissingError extends Error {
24
+ constructor(missing) {
25
+ super(`Apply mode requires WordPress credentials. Missing: ${missing.join(", ")}. ` +
26
+ `Set WP_URL / WP_USER / WP_APP_PASSWORD (the same env vars the MCP server uses). ` +
27
+ `Use --dry-run to compose preset JSON without credentials.`);
28
+ this.name = "CredentialsMissingError";
29
+ }
30
+ }
31
+ export class CapabilityMissingError extends Error {
32
+ capability;
33
+ pluginVersion;
34
+ constructor(capability, pluginVersion) {
35
+ super(`The active diviops-agent plugin (version ${pluginVersion}) does not report ` +
36
+ `the "${capability}" capability required for storage-routed preset writes. ` +
37
+ `Upgrade diviops-agent to a version that ships the "${capability}" capability ` +
38
+ `(plugin 1.4.9 or later).`);
39
+ this.capability = capability;
40
+ this.pluginVersion = pluginVersion;
41
+ this.name = "CapabilityMissingError";
42
+ }
43
+ }
44
+ /** Build a `WPClient` from the standard env vars, or throw if any are absent. */
45
+ export function buildClientFromEnv(env = process.env) {
46
+ const url = env.WP_URL ?? "";
47
+ const user = env.WP_USER ?? "";
48
+ const pass = env.WP_APP_PASSWORD ?? "";
49
+ const missing = [];
50
+ if (!url)
51
+ missing.push("WP_URL");
52
+ if (!user)
53
+ missing.push("WP_USER");
54
+ if (!pass)
55
+ missing.push("WP_APP_PASSWORD");
56
+ if (missing.length > 0)
57
+ throw new CredentialsMissingError(missing);
58
+ return new WPClient({
59
+ siteUrl: url,
60
+ username: user,
61
+ applicationPassword: pass,
62
+ });
63
+ }
64
+ /**
65
+ * Verify the plugin handshake reports `storage_multipath_probe_v1`.
66
+ * Throws `CapabilityMissingError` if absent. Returns the handshake result
67
+ * so callers can surface the plugin version.
68
+ */
69
+ export async function assertStorageCapability(client, serverVersion) {
70
+ const hs = await client.handshake(serverVersion);
71
+ if (!hs.capabilities || hs.capabilities[STORAGE_CAPABILITY] !== true) {
72
+ throw new CapabilityMissingError(STORAGE_CAPABILITY, hs.plugin_version ?? "unknown");
73
+ }
74
+ return hs;
75
+ }
76
+ /**
77
+ * Apply a button preset: capability-gate, then POST to `/preset/create`.
78
+ *
79
+ * The capability check runs BEFORE the write. The write goes through the
80
+ * existing storage-routed route — no plugin route is added here.
81
+ */
82
+ export async function applyButtonPreset(client, entry, opts) {
83
+ await assertStorageCapability(client, opts.serverVersion);
84
+ const body = buildPresetCreateBody(entry, { dry_run: opts.dry_run });
85
+ return client.requestEnveloped(PRESET_CREATE_ROUTE, {
86
+ method: "POST",
87
+ body,
88
+ });
89
+ }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@diviops/mcp-server",
3
- "version": "1.5.10",
3
+ "version": "1.5.11",
4
4
  "description": "MCP server exposing Divi 5 Visual Builder as tools for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
- "diviops-mcp": "dist/index.js"
8
+ "diviops-mcp": "dist/index.js",
9
+ "diviops-preset": "dist/preset-cli/bin.js"
9
10
  },
10
11
  "files": [
11
12
  "dist",
@@ -18,6 +19,7 @@
18
19
  "build": "tsc",
19
20
  "start": "node dist/index.js",
20
21
  "dev": "tsc --watch",
22
+ "test": "npm run build && node --test \"dist/preset-cli/__tests__/*.test.js\"",
21
23
  "prepublishOnly": "npm run build",
22
24
  "regen:skill": "node scripts/regen-module-formats.mjs"
23
25
  },