@neopress/mcp 1.5.0 → 1.6.1

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.
package/dist/http-bin.cjs CHANGED
@@ -21054,6 +21054,10 @@ var EntriesEndpoint = class {
21054
21054
  collectionId
21055
21055
  })).data;
21056
21056
  }
21057
+ /** Copy source content into a linked locale draft, or return its existing translation. */
21058
+ async createTranslation(entryId, locale) {
21059
+ return (await this.client.post(`${this.basePath}/${entryId}/translations`, { locale })).data;
21060
+ }
21057
21061
  async update(entryId, data) {
21058
21062
  const payload = {};
21059
21063
  for (const key of UPDATE_PASSTHROUGH_FIELDS) if (key in data) payload[key] = data[key];
@@ -21331,8 +21335,12 @@ function appendCommonAnalyticsParams(qs, params) {
21331
21335
  }
21332
21336
  var AnalyticsEndpoint = class {
21333
21337
  client;
21338
+ events;
21339
+ funnels;
21334
21340
  constructor(client) {
21335
21341
  this.client = client;
21342
+ this.events = new AnalyticsEventsEndpoint(client);
21343
+ this.funnels = new AnalyticsFunnelsEndpoint(client);
21336
21344
  }
21337
21345
  get basePath() {
21338
21346
  return `${this.client.siteBasePath}/analytics`;
@@ -21389,6 +21397,58 @@ var AnalyticsEndpoint = class {
21389
21397
  if (params.rowLimit) qs.set("row_limit", String(params.rowLimit));
21390
21398
  return (await this.client.get(`${this.basePath}/search-console?${qs.toString()}`)).data;
21391
21399
  }
21400
+ /** Page/form targets that can be referenced by an event definition. */
21401
+ async targets() {
21402
+ return (await this.client.get(`${this.basePath}/targets`)).data;
21403
+ }
21404
+ };
21405
+ var AnalyticsEventsEndpoint = class {
21406
+ client;
21407
+ constructor(client) {
21408
+ this.client = client;
21409
+ }
21410
+ get basePath() {
21411
+ return `${this.client.siteBasePath}/analytics/events`;
21412
+ }
21413
+ async list() {
21414
+ return (await this.client.get(this.basePath)).data;
21415
+ }
21416
+ async get(eventId) {
21417
+ return (await this.client.get(`${this.basePath}/${eventId}`)).data.event;
21418
+ }
21419
+ async create(data) {
21420
+ return (await this.client.post(this.basePath, data)).data.event;
21421
+ }
21422
+ async update(eventId, data) {
21423
+ return (await this.client.patch(`${this.basePath}/${eventId}`, data)).data.event;
21424
+ }
21425
+ async delete(eventId) {
21426
+ await this.client.delete(`${this.basePath}/${eventId}`);
21427
+ }
21428
+ };
21429
+ var AnalyticsFunnelsEndpoint = class {
21430
+ client;
21431
+ constructor(client) {
21432
+ this.client = client;
21433
+ }
21434
+ get basePath() {
21435
+ return `${this.client.siteBasePath}/analytics/funnels`;
21436
+ }
21437
+ async list() {
21438
+ return (await this.client.get(this.basePath)).data;
21439
+ }
21440
+ async get(funnelId) {
21441
+ return (await this.client.get(`${this.basePath}/${funnelId}`)).data.funnel;
21442
+ }
21443
+ async create(data) {
21444
+ return (await this.client.post(this.basePath, data)).data.funnel;
21445
+ }
21446
+ async update(funnelId, data) {
21447
+ return (await this.client.patch(`${this.basePath}/${funnelId}`, data)).data.funnel;
21448
+ }
21449
+ async delete(funnelId) {
21450
+ await this.client.delete(`${this.basePath}/${funnelId}`);
21451
+ }
21392
21452
  };
21393
21453
  //#endregion
21394
21454
  //#region ../sdk/src/endpoints/templates.ts
@@ -21744,9 +21804,8 @@ Use these rules before generating pages, collection schemas, entries, forms, or
21744
21804
 
21745
21805
  ## Operating boundary
21746
21806
 
21747
- - Authentication is OAuth bearer token based. Use \`NEOPRESS_ACCESS_TOKEN\` or the OAuth session created by \`neopress login\`.
21748
- - API keys and API-key based public integrations are not part of this surface.
21749
- - MCP is available as local stdio and remote Streamable HTTP over the first-party SDK/API.
21807
+ - Connect the remote MCP server and sign in with your Neopress account through OAuth.
21808
+ - MCP is available on Launch and above. Your site role controls which actions you can perform.
21750
21809
  - MCP is a Neopress contract adapter, not a reference crawler or managed page generator.
21751
21810
  - The host coding agent should perform \`r.jina.ai\` markdown fetches, browser screenshots, DOM/CSS/SVG/background-image extraction, reference analysis, and TSX authoring with its own tools and tokens.
21752
21811
  - Use MCP to read/write Neopress resources, upload or register assets, compile, publish, and verify. Do not expect MCP to spend Neopress server-side LLM tokens for reference analysis or page generation.
@@ -21760,9 +21819,9 @@ Use these rules before generating pages, collection schemas, entries, forms, or
21760
21819
 
21761
21820
  ## Cache
21762
21821
 
21763
- - Site publish invalidates site/layout/collection reader cache tags in the API environment that handled the request.
21822
+ - Site publish invalidates site/layout/collection reader cache tags in the environment connected to MCP.
21764
21823
  - Entry publish/unpublish invalidates entry reader cache when production visibility changes.
21765
- - A localhost publish does not invalidate production reader cache. For production validation, publish through the production API and verify the live URL.
21824
+ - A localhost publish does not invalidate production reader cache. For production validation, publish through the production MCP server and verify the live URL.
21766
21825
 
21767
21826
  ## Analytics and autopilot
21768
21827
 
@@ -21775,7 +21834,7 @@ Use these rules before generating pages, collection schemas, entries, forms, or
21775
21834
 
21776
21835
  - Layout owns header, footer, global CSS, and 404. Page TSX starts at page content.
21777
21836
  - Generated page TSX is sandboxed. Do not import Supabase, create API clients, read env vars, use filesystem/process APIs, or assume arbitrary npm packages.
21778
- - Use allowed runtime packages only: React/Next basics, react-dom (createPortal), shadcn UI imports, lucide-react, @base-ui/react/drawer, @base-ui/react/dialog, @base-ui/react/accordion, @base-ui/react/switch, @base-ui/react/tabs, @base-ui/react/select, @neopress/youtube, @neopress/map, clsx, class-variance-authority, tailwind-merge, motion/react (never \`framer-motion\` — old package name, not on the allowlist), motion-plus/react (Motion+ premium components: Ticker, Carousel, Cursor, AnimateNumber, AnimateText, Typewriter, ScrambleText, Curtains), @paper-design/shaders-react, gsap (including \`gsap/ScrollTrigger\`, driven via \`@gsap/react\`'s \`useGSAP\`), react-hook-form, zod, @hookform/resolvers, @hookform/resolvers/zod, @neopress/content, @neopress/routing, @neopress/i18n, and @neopress/forms.
21837
+ - Use allowed runtime packages only: React/Next basics, react-dom (createPortal), shadcn UI imports, lucide-react, @base-ui/react/drawer, @base-ui/react/dialog, @base-ui/react/accordion, @base-ui/react/switch, @base-ui/react/tabs, @base-ui/react/select, @neopress/youtube, @neopress/map, clsx, class-variance-authority, tailwind-merge, motion/react (never \`framer-motion\` — old package name, not on the allowlist), motion-plus/react (Motion+ premium components: Ticker, Carousel, Cursor, AnimateNumber, AnimateText, Typewriter, ScrambleText, Curtains), @paper-design/shaders-react, gsap (including \`gsap/ScrollTrigger\`, driven via \`@gsap/react\`'s \`useGSAP\`), react-hook-form, zod, @hookform/resolvers, @hookform/resolvers/zod, @neopress/content, @neopress/routing, @neopress/i18n, @neopress/responsive (MobileOnly with deferred desktop/mobile callbacks), and @neopress/forms.
21779
21838
  - For dates, render an existing \`displayDate\` field directly when available. For raw timestamps, use \`Intl.DateTimeFormat\` or \`Date#toLocaleDateString\` with an explicit locale and \`timeZone\` so server rendering and hydration agree; use the site's required zone, or \`UTC\` when none is specified.
21780
21839
  - @base-ui/react/select is an accessible listbox/dropdown (replaces a native \`<select>\`, whose popup is unstyled OS chrome). Compose its parts directly in page TSX; a \`<Select.Item value>\` is a string or the documented \`null\` sentinel (unlike Radix, an empty string is not banned); the \`Select.Popup\` portals to \`document.body\`, so give it an explicit z-index and an opaque background; match the trigger width with \`min-w-[var(--anchor-width)]\` (from \`Select.Positioner\`), cap the list with \`max-h-[var(--available-height)]\`, and set \`alignItemWithTrigger={false}\` for standard below-trigger placement. @base-ui/react/drawer is the gesture mobile sheet; @base-ui/react/dialog the non-gesture modal — both portal to \`document.body\`, need an opaque Popup background + z-index, and take \`modal\` for focus-trap.
21781
21840
  - react-dom is for \`createPortal\` — portal full-viewport overlays (mobile drawer, modal) to \`document.body\` so a \`backdrop-filter\`/\`transform\` ancestor can't trap their \`position: fixed\` inside its box. ALWAYS render the portal behind an open-state guard (\`{open && createPortal(…, document.body)}\`): an unguarded \`document\` reference throws during SSR.
@@ -21797,7 +21856,9 @@ Use these rules before generating pages, collection schemas, entries, forms, or
21797
21856
  - \`queryConfig\` is stored in page \`draftMeta.queryConfig\`.
21798
21857
  - Query labels become React props. Entry query results are flattened, so use \`post.body\` instead of \`post.data.body\`.
21799
21858
  - A query may include \`collectionWindow\` on an \`entries\` query for a dynamic detail route. Its label receives \`{ before, after, items }\`; each is a flattened-entry array around the current entry, and \`items\` combines both directions. Windows use \`published_at DESC, id DESC\`, allow 0–20 entries per direction, and may only declare that exact order. Use the exact configured label, never a guessed prop name. Optional \`matchFields\` compare raw anchor-snapshot fields only when the source field has a value.
21800
- - Initial CMS data arrives through those injected query props. \`entryClient\`, when injected in reader/preview for a site handle, is only a client-side auxiliary for load-more/filter/get/create interactions after the initial render; do not use it for initial render or server data loading.
21859
+ - Initial CMS data arrives through those injected query props. \`entryClient\`, when injected in reader/preview for a site handle, is only a client-side auxiliary for load-more/filter/get/create interactions after the initial render; do not use it for initial render or server data loading. Its entries come back flattened like injected props, ordered \`published_at DESC, id DESC\`.
21860
+ - Server-side binding is the default because injected props are server-rendered and crawlable; \`entryClient\` is for after-hydration needs only (large-collection load-more, a read that depends on visitor input such as search text or a chosen filter, or an explicit client-side request).
21861
+ - Load-more pagination: give the list query a \`limit\` (first page injected server-side), then append \`entryClient.more(propLabel, { offset: shown.length, filters? })\` — the server re-runs that page's stored binding query from the offset, so the page restates nothing; \`filters\` is equality on scalar schema fields (\`{ category: "TECH" }\`), applied to the same published data. \`entryClient.list({ collectionId, offset, limit, filters? })\` is only for reads whose query depends on visitor input or that are too large to inject; a light, fixed set revealed on click is a bound query instead. Judge "light" by what the query injects into the page, not by how much the UI shows: a query without a \`limit\` ships every entry with every field. Without a \`limit\` a list query injects every entry, so paginate by slicing the prop instead.
21801
21862
  - Use numeric \`collection_id\` filters. Runtime controls site, status, locale, deleted state, and published snapshots.
21802
21863
 
21803
21864
  ## Collections and entries
@@ -21811,7 +21872,7 @@ Use these rules before generating pages, collection schemas, entries, forms, or
21811
21872
 
21812
21873
  - Reference fields require a schema field with \`type: "references"\` and target \`collection_id\`.
21813
21874
  - Links live in \`entry_references\`, not only in raw entry \`data\`.
21814
- - \`includeReferences\` fetches arrays keyed by the includeReferences alias or field key, and parent entry \`id\` must be selected. With an alias, the alias prop remains an array of referenced entries. Without an alias, if the field key matches a raw numeric reference field in \`entry.data\`, flattening unwraps that array to one flattened object or null.
21875
+ - \`includeReferences\` fetches arrays keyed by the includeReferences alias or field key, and parent entry \`id\` must be selected. With an alias, the alias prop remains an array of referenced entries. Without an alias, if the field key matches a raw numeric reference field in \`entry.data\`, flattening unwraps that array to one object or null; references linked only through \`entry_references\` stay arrays. Every referenced entry is flattened like the parent (\`ref.<fieldId>\`; \`ref.data\` kept for backward compatibility), one level deep.
21815
21876
  - After changing references, publish the source entry again.
21816
21877
 
21817
21878
  ## Forms
@@ -21824,7 +21885,7 @@ Use these rules before generating pages, collection schemas, entries, forms, or
21824
21885
  ## Unsupported visitor features
21825
21886
 
21826
21887
  Generated Neopress pages do not support visitor login/accounts, member areas, carts, checkout, payments, subscriptions, or order management.`;
21827
- const SERVER_INSTRUCTIONS = `Use Neopress MCP tools to manage a selected Neopress site through the first-party OAuth API.
21888
+ const SERVER_INSTRUCTIONS = `Use Neopress MCP tools to manage a selected Neopress site through an authenticated MCP connection.
21828
21889
 
21829
21890
  Before generating TSX or schema-backed pages, call neopress_get_runtime_contract or read neopress://runtime-contract.
21830
21891
  Use numeric IDs returned by tools. Publish entries separately from site publish. Use read-only analytics tools before autopilot recommendations. Verify production URLs after production publish.
@@ -22114,6 +22175,11 @@ const READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
22114
22175
  "neopress_analytics_traffic_sources",
22115
22176
  "neopress_analytics_forms",
22116
22177
  "neopress_analytics_crawler_summary",
22178
+ "neopress_analytics_events_list",
22179
+ "neopress_analytics_event_get",
22180
+ "neopress_analytics_funnels_list",
22181
+ "neopress_analytics_funnel_get",
22182
+ "neopress_analytics_targets_list",
22117
22183
  "neopress_search_console_query",
22118
22184
  "neopress_assets_list",
22119
22185
  "neopress_asset_get",
@@ -22137,7 +22203,13 @@ const DESTRUCTIVE_TOOLS = /* @__PURE__ */ new Set([
22137
22203
  "neopress_form_delete",
22138
22204
  "neopress_asset_delete",
22139
22205
  "neopress_template_mark",
22140
- "neopress_publish_site"
22206
+ "neopress_publish_site",
22207
+ "neopress_analytics_event_create",
22208
+ "neopress_analytics_event_update",
22209
+ "neopress_analytics_event_delete",
22210
+ "neopress_analytics_funnel_create",
22211
+ "neopress_analytics_funnel_update",
22212
+ "neopress_analytics_funnel_delete"
22141
22213
  ]);
22142
22214
  const PUBLICATION_TOOLS = /* @__PURE__ */ new Set([
22143
22215
  "neopress_entry_publish",
@@ -22178,6 +22250,12 @@ function asOptionalNumber(input, key) {
22178
22250
  function asOptionalString(input, key) {
22179
22251
  return input[key];
22180
22252
  }
22253
+ function asOptionalBoolean(input, key) {
22254
+ return input[key];
22255
+ }
22256
+ function omitUndefined(value) {
22257
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
22258
+ }
22181
22259
  function siteId(input) {
22182
22260
  return asOptionalNumber(input, "siteId");
22183
22261
  }
@@ -22230,7 +22308,7 @@ const toolDefinitions = [
22230
22308
  {
22231
22309
  name: "neopress_site_select",
22232
22310
  title: "Select Active Site",
22233
- description: "Verify access to a site and store it as the active site for future MCP/CLI operations.",
22311
+ description: "Verify access to a site and store it as the active site for future MCP operations.",
22234
22312
  inputSchema: { siteId: number().int().positive() },
22235
22313
  handler: async (input, context, extra) => {
22236
22314
  const selectedSiteId = asNumber(input, "siteId");
@@ -22404,10 +22482,11 @@ const toolDefinitions = [
22404
22482
  title: string().optional(),
22405
22483
  type: _enum(["static", "dynamic"]).optional(),
22406
22484
  status: _enum(["draft", "published"]).optional(),
22485
+ visibility: _enum(["public", "hidden"]).optional(),
22407
22486
  collectionId: number().int().positive().optional(),
22408
22487
  draftTsx: string().optional(),
22409
22488
  draftI18n: optionalJsonSchema,
22410
- draftPublicLocales: array(string()).nullable().optional(),
22489
+ draftPublicLocales: array(string()).nullable().optional().describe("Draft public languages. null means default language only; [] means none. Publish to apply."),
22411
22490
  draftMeta: optionalJsonSchema,
22412
22491
  queryConfig: optionalJsonSchema,
22413
22492
  metadata: optionalJsonSchema
@@ -22418,6 +22497,7 @@ const toolDefinitions = [
22418
22497
  title: asOptionalString(input, "title"),
22419
22498
  type: input.type,
22420
22499
  status: input.status,
22500
+ visibility: input.visibility,
22421
22501
  collectionId: asOptionalNumber(input, "collectionId"),
22422
22502
  draftTsx: asOptionalString(input, "draftTsx"),
22423
22503
  draftI18n: input.draftI18n,
@@ -22438,27 +22518,29 @@ const toolDefinitions = [
22438
22518
  title: string().optional(),
22439
22519
  type: _enum(["static", "dynamic"]).optional(),
22440
22520
  status: string().optional(),
22521
+ visibility: _enum(["public", "hidden"]).optional(),
22441
22522
  collectionId: number().int().positive().optional(),
22442
22523
  draftTsx: string().optional(),
22443
22524
  draftI18n: optionalJsonSchema,
22444
- draftPublicLocales: array(string()).nullable().optional(),
22525
+ draftPublicLocales: array(string()).nullable().optional().describe("Draft public languages. null means default language only; [] means none. Publish to apply."),
22445
22526
  draftMeta: optionalJsonSchema,
22446
22527
  queryConfig: optionalJsonSchema,
22447
22528
  metadata: optionalJsonSchema
22448
22529
  },
22449
22530
  handler: async (input, context, extra) => {
22450
- return (await context.createClient({ siteId: siteId(input) }, extra)).pages.update(asNumber(input, "pageId"), {
22531
+ return (await context.createClient({ siteId: siteId(input) }, extra)).pages.update(asNumber(input, "pageId"), omitUndefined({
22451
22532
  path: asOptionalString(input, "path"),
22452
22533
  title: asOptionalString(input, "title"),
22453
22534
  type: asOptionalString(input, "type"),
22454
22535
  status: asOptionalString(input, "status"),
22536
+ visibility: input.visibility,
22455
22537
  collectionId: asOptionalNumber(input, "collectionId"),
22456
22538
  draftTsx: asOptionalString(input, "draftTsx"),
22457
22539
  draftI18n: input.draftI18n,
22458
22540
  draftPublicLocales: input.draftPublicLocales,
22459
22541
  draftMeta: mergeDraftMeta(input.draftMeta, input.queryConfig),
22460
22542
  metadata: input.metadata
22461
- });
22543
+ }));
22462
22544
  }
22463
22545
  },
22464
22546
  {
@@ -22614,6 +22696,19 @@ const toolDefinitions = [
22614
22696
  return entry;
22615
22697
  }
22616
22698
  },
22699
+ {
22700
+ name: "neopress_entry_create_translation",
22701
+ title: "Create Entry Translation Draft",
22702
+ description: "Create or return a linked locale row in the source entry's translation group. New drafts copy source content; translate the returned entry with entry_update, then publish it. Existing translations are preserved.",
22703
+ inputSchema: {
22704
+ ...siteIdShape,
22705
+ entryId: number().int().positive(),
22706
+ locale: string().min(1)
22707
+ },
22708
+ handler: async (input, context, extra) => {
22709
+ return (await context.createClient({ siteId: siteId(input) }, extra)).entries.createTranslation(asNumber(input, "entryId"), asString(input, "locale"));
22710
+ }
22711
+ },
22617
22712
  {
22618
22713
  name: "neopress_entry_update",
22619
22714
  title: "Update Entry",
@@ -22630,14 +22725,14 @@ const toolDefinitions = [
22630
22725
  },
22631
22726
  handler: async (input, context, extra) => {
22632
22727
  const client = await context.createClient({ siteId: siteId(input) }, extra);
22633
- const entry = await client.entries.update(asNumber(input, "entryId"), {
22728
+ const entry = await client.entries.update(asNumber(input, "entryId"), omitUndefined({
22634
22729
  slug: asOptionalString(input, "slug"),
22635
22730
  title: asOptionalString(input, "title"),
22636
22731
  locale: asOptionalString(input, "locale"),
22637
22732
  data: input.data,
22638
22733
  metadata: input.metadata,
22639
22734
  status: asOptionalString(input, "status")
22640
- });
22735
+ }));
22641
22736
  await client.entries.revalidateReaderCache(asNumber(input, "entryId"));
22642
22737
  return entry;
22643
22738
  }
@@ -22802,14 +22897,14 @@ const toolDefinitions = [
22802
22897
  settings: optionalJsonSchema
22803
22898
  },
22804
22899
  handler: async (input, context, extra) => {
22805
- return (await context.createClient({ siteId: siteId(input) }, extra)).forms.update(asNumber(input, "formId"), {
22900
+ return (await context.createClient({ siteId: siteId(input) }, extra)).forms.update(asNumber(input, "formId"), omitUndefined({
22806
22901
  name: asOptionalString(input, "name"),
22807
- draftSchema: input.draftSchema ?? input.schema,
22902
+ draftSchema: input.draftSchema !== void 0 ? input.draftSchema : input.schema,
22808
22903
  draftLayout: input.draftLayout,
22809
22904
  draftStyle: input.draftStyle,
22810
22905
  draftI18n: input.draftI18n,
22811
22906
  settings: input.settings
22812
- });
22907
+ }));
22813
22908
  }
22814
22909
  },
22815
22910
  {
@@ -22853,6 +22948,235 @@ const toolDefinitions = [
22853
22948
  });
22854
22949
  }
22855
22950
  },
22951
+ {
22952
+ name: "neopress_analytics_events_list",
22953
+ title: "List Analytics Events",
22954
+ description: "List saved analytics event definitions for the active site.",
22955
+ inputSchema: siteIdShape,
22956
+ handler: async (input, context, extra) => {
22957
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.events.list();
22958
+ }
22959
+ },
22960
+ {
22961
+ name: "neopress_analytics_event_get",
22962
+ title: "Get Analytics Event",
22963
+ description: "Get a saved analytics event definition by UUID.",
22964
+ inputSchema: {
22965
+ ...siteIdShape,
22966
+ eventId: string().uuid()
22967
+ },
22968
+ handler: async (input, context, extra) => {
22969
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.events.get(asString(input, "eventId"));
22970
+ }
22971
+ },
22972
+ {
22973
+ name: "neopress_analytics_event_create",
22974
+ title: "Create Analytics Event",
22975
+ description: "Create a saved analytics event definition over raw analytics signals. Discover page and form IDs with neopress_analytics_targets_list. Click targets use an observed href:<absolute-url> or selector:<fingerprint> key; outbound counts external-link clicks site-wide. Built-in events start collecting without editing or republishing page TSX. Optional id is a client UUID: reuse the same id and payload after an uncertain response.",
22976
+ inputSchema: {
22977
+ ...siteIdShape,
22978
+ id: string().uuid().optional(),
22979
+ name: string(),
22980
+ eventType: _enum([
22981
+ "custom",
22982
+ "page",
22983
+ "click",
22984
+ "form",
22985
+ "scroll",
22986
+ "dwell",
22987
+ "outbound"
22988
+ ]),
22989
+ eventName: string().optional(),
22990
+ path: string().optional(),
22991
+ selector: string().optional(),
22992
+ formId: string().optional(),
22993
+ scrollDepth: number().int().optional(),
22994
+ dwellSeconds: number().int().optional()
22995
+ },
22996
+ handler: async (input, context, extra) => {
22997
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.events.create({
22998
+ id: asOptionalString(input, "id"),
22999
+ name: asString(input, "name"),
23000
+ eventType: input.eventType,
23001
+ eventName: asOptionalString(input, "eventName"),
23002
+ path: asOptionalString(input, "path"),
23003
+ selector: asOptionalString(input, "selector"),
23004
+ formId: asOptionalString(input, "formId"),
23005
+ scrollDepth: asOptionalNumber(input, "scrollDepth"),
23006
+ dwellSeconds: asOptionalNumber(input, "dwellSeconds")
23007
+ });
23008
+ }
23009
+ },
23010
+ {
23011
+ name: "neopress_analytics_event_update",
23012
+ title: "Update Analytics Event",
23013
+ description: "Rename an analytics event with {name} while preserving tracking behavior, or update its display settings separately. Tracking identity cannot be changed after creation.",
23014
+ inputSchema: {
23015
+ ...siteIdShape,
23016
+ eventId: string().uuid(),
23017
+ name: string().optional(),
23018
+ eventType: _enum([
23019
+ "custom",
23020
+ "page",
23021
+ "click",
23022
+ "form",
23023
+ "scroll",
23024
+ "dwell",
23025
+ "outbound"
23026
+ ]).optional(),
23027
+ eventName: string().optional(),
23028
+ path: string().optional(),
23029
+ selector: string().optional(),
23030
+ formId: string().optional(),
23031
+ scrollDepth: number().int().optional(),
23032
+ dwellSeconds: number().int().optional(),
23033
+ chartVisible: boolean().optional(),
23034
+ pinned: boolean().optional(),
23035
+ chartColor: string().optional()
23036
+ },
23037
+ handler: async (input, context, extra) => {
23038
+ const client = await context.createClient({ siteId: siteId(input) }, extra);
23039
+ if ([
23040
+ "chartVisible",
23041
+ "pinned",
23042
+ "chartColor"
23043
+ ].some((key) => key in input)) {
23044
+ if ([
23045
+ "name",
23046
+ "eventType",
23047
+ "eventName",
23048
+ "path",
23049
+ "selector",
23050
+ "formId",
23051
+ "scrollDepth",
23052
+ "dwellSeconds"
23053
+ ].some((key) => input[key] !== void 0)) throw new Error("Update the definition and display settings in separate calls.");
23054
+ return client.analytics.events.update(asString(input, "eventId"), { display: {
23055
+ chartVisible: asOptionalBoolean(input, "chartVisible"),
23056
+ pinned: asOptionalBoolean(input, "pinned"),
23057
+ chartColor: asOptionalString(input, "chartColor")
23058
+ } });
23059
+ }
23060
+ const definition = {
23061
+ name: asString(input, "name"),
23062
+ ...input.eventType !== void 0 ? { eventType: input.eventType } : {},
23063
+ eventName: asOptionalString(input, "eventName"),
23064
+ path: asOptionalString(input, "path"),
23065
+ selector: asOptionalString(input, "selector"),
23066
+ formId: asOptionalString(input, "formId"),
23067
+ scrollDepth: asOptionalNumber(input, "scrollDepth"),
23068
+ dwellSeconds: asOptionalNumber(input, "dwellSeconds")
23069
+ };
23070
+ return client.analytics.events.update(asString(input, "eventId"), definition);
23071
+ }
23072
+ },
23073
+ {
23074
+ name: "neopress_analytics_event_delete",
23075
+ title: "Delete Analytics Event",
23076
+ description: "Permanently delete a saved analytics event. Events referenced by a funnel cannot be deleted.",
23077
+ inputSchema: {
23078
+ ...siteIdShape,
23079
+ eventId: string().uuid()
23080
+ },
23081
+ handler: async (input, context, extra) => {
23082
+ await (await context.createClient({ siteId: siteId(input) }, extra)).analytics.events.delete(asString(input, "eventId"));
23083
+ return { ok: true };
23084
+ }
23085
+ },
23086
+ {
23087
+ name: "neopress_analytics_funnels_list",
23088
+ title: "List Analytics Funnels",
23089
+ description: "List saved analytics funnel definitions for the active site.",
23090
+ inputSchema: siteIdShape,
23091
+ handler: async (input, context, extra) => {
23092
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.list();
23093
+ }
23094
+ },
23095
+ {
23096
+ name: "neopress_analytics_funnel_get",
23097
+ title: "Get Analytics Funnel",
23098
+ description: "Get a saved analytics funnel definition by UUID.",
23099
+ inputSchema: {
23100
+ ...siteIdShape,
23101
+ funnelId: string().uuid()
23102
+ },
23103
+ handler: async (input, context, extra) => {
23104
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.get(asString(input, "funnelId"));
23105
+ }
23106
+ },
23107
+ {
23108
+ name: "neopress_analytics_funnel_create",
23109
+ title: "Create Analytics Funnel",
23110
+ description: "Create a funnel with 2 to 5 ordered saved event UUIDs. Optional id is a client UUID; reuse it and the same payload after an uncertain response. Step filter is {version:1,root:{kind:'group',op:'and'|'or',children:[groups or {kind:'condition',field,operator,value?,key?}]}}. Fields: event.path/url/referrer/referrer_source/property; session.referrer/referrer_source/traffic_category/utm_source/utm_medium/utm_campaign/utm_content/utm_term. event.property requires key. Operators: eq,neq,contains,not_contains,starts_with,ends_with,exists,not_exists; property also supports gt,gte,lt,lte. Server validates bounds and grouping.",
23111
+ inputSchema: {
23112
+ ...siteIdShape,
23113
+ id: string().uuid().optional(),
23114
+ name: string(),
23115
+ steps: array(object$1({
23116
+ eventId: string().uuid(),
23117
+ path: string().optional(),
23118
+ filter: unknown().optional()
23119
+ })),
23120
+ windowMode: _enum(["session", "time"]),
23121
+ windowHours: number().int().nonnegative().optional()
23122
+ },
23123
+ handler: async (input, context, extra) => {
23124
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.create({
23125
+ id: asOptionalString(input, "id"),
23126
+ name: asString(input, "name"),
23127
+ steps: input.steps,
23128
+ windowMode: input.windowMode,
23129
+ windowHours: asOptionalNumber(input, "windowHours")
23130
+ });
23131
+ }
23132
+ },
23133
+ {
23134
+ name: "neopress_analytics_funnel_update",
23135
+ title: "Update Analytics Funnel",
23136
+ description: "Replace a saved funnel definition with its ordered event steps and window semantics.",
23137
+ inputSchema: {
23138
+ ...siteIdShape,
23139
+ funnelId: string().uuid(),
23140
+ name: string(),
23141
+ steps: array(object$1({
23142
+ eventId: string().uuid(),
23143
+ path: string().optional(),
23144
+ filter: unknown().optional()
23145
+ })),
23146
+ windowMode: _enum(["session", "time"]),
23147
+ windowHours: number().int().nonnegative().optional()
23148
+ },
23149
+ handler: async (input, context, extra) => {
23150
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.update(asString(input, "funnelId"), {
23151
+ name: asString(input, "name"),
23152
+ steps: input.steps,
23153
+ windowMode: input.windowMode,
23154
+ windowHours: asOptionalNumber(input, "windowHours")
23155
+ });
23156
+ }
23157
+ },
23158
+ {
23159
+ name: "neopress_analytics_funnel_delete",
23160
+ title: "Delete Analytics Funnel",
23161
+ description: "Permanently delete a saved analytics funnel.",
23162
+ inputSchema: {
23163
+ ...siteIdShape,
23164
+ funnelId: string().uuid()
23165
+ },
23166
+ handler: async (input, context, extra) => {
23167
+ await (await context.createClient({ siteId: siteId(input) }, extra)).analytics.funnels.delete(asString(input, "funnelId"));
23168
+ return { ok: true };
23169
+ }
23170
+ },
23171
+ {
23172
+ name: "neopress_analytics_targets_list",
23173
+ title: "List Analytics Event Targets",
23174
+ description: "List site pages and forms available as analytics event targets.",
23175
+ inputSchema: siteIdShape,
23176
+ handler: async (input, context, extra) => {
23177
+ return (await context.createClient({ siteId: siteId(input) }, extra)).analytics.targets();
23178
+ }
23179
+ },
22856
23180
  {
22857
23181
  name: "neopress_analytics_query",
22858
23182
  title: "Query Analytics Pipe",