@sentientui/mcp 0.17.5 → 0.17.7

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.
@@ -44,10 +44,9 @@ var ApiClient = class {
44
44
  var _a, _b;
45
45
  const res = await fetch(`${this.baseUrl}/v1/mgmt${path}`, {
46
46
  method,
47
- headers: {
48
- authorization: `Bearer ${this.apiKey}`,
49
- "content-type": "application/json"
50
- },
47
+ headers: __spreadValues({
48
+ authorization: `Bearer ${this.apiKey}`
49
+ }, body !== void 0 ? { "content-type": "application/json" } : {}),
51
50
  body: body !== void 0 ? JSON.stringify(body) : void 0
52
51
  });
53
52
  if (res.ok) return res.json();
@@ -942,7 +941,7 @@ function registerGoalTools(server, client) {
942
941
  "get_goal_funnel",
943
942
  {
944
943
  title: "Goal funnel",
945
- description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown (default window: last 30 calendar days). This is a flat per-goal list \u2014 for multi-step funnel drop-off, use get_funnel_report." + UNTRUSTED_FIELDS_NOTE,
944
+ description: "Get goal hit counts and unique-session conversion rates for a window (default: last 30 calendar days), plus a per-variant breakdown. NOTE: the per-variant completionRate values are ALL-TIME (bounded only by plan retention), NOT windowed \u2014 a variant completion rate needs the full assignment history as its denominator, so it does not change when you narrow `range`, and retired components keep appearing in it. Only hits/uniqueSessions/conversionRate/revenue respect the window. This is a flat per-goal list \u2014 for multi-step funnel drop-off, use get_funnel_report." + UNTRUSTED_FIELDS_NOTE,
946
945
  inputSchema: __spreadValues({ projectId: projectIdSchema }, rangeInputSchema("30d")),
947
946
  _meta: uiMeta("goal-funnel"),
948
947
  outputSchema: {
@@ -960,9 +959,13 @@ function registerGoalTools(server, client) {
960
959
  z6.object({
961
960
  componentId: z6.string(),
962
961
  variantId: z6.string(),
963
- completionRate: z6.number().describe("Completion rate per assigned session (0-1)")
962
+ completionRate: z6.number().describe(
963
+ "ALL-TIME completion rate per assigned session (0-1). Not windowed: unaffected by `range`/`from`/`to`, and bounded only by plan retention."
964
+ )
964
965
  })
965
- ).describe("Per-variant breakdown")
966
+ ).describe(
967
+ "Per-variant breakdown. ALL-TIME, not windowed \u2014 do not compare these against the windowed hits/conversionRate above, and expect retired components here."
968
+ )
966
969
  })
967
970
  ).describe("Configured goals (empty if none)"),
968
971
  window: windowOutputSchema
@@ -1011,11 +1014,18 @@ function registerGoalTools(server, client) {
1011
1014
  var _a2;
1012
1015
  return [
1013
1016
  `${untrusted(g.goalName)}: ${g.hits} hits, ${g.uniqueSessions} unique sessions, ${(g.pct * 100).toFixed(1)}% conversion` + (g.revenue != null ? `, ${g.revenue.toFixed(2)} ${currency} revenue (${((_a2 = g.avgOrderValue) != null ? _a2 : 0).toFixed(2)} avg order)` : ""),
1014
- ...g.variants.map((v) => ` ${untrusted(v.componentId)}/${untrusted(v.variantId)}: ${(v.completionRate * 100).toFixed(1)}% per assigned session`),
1017
+ // The per-variant rates come from an all-time query (analytics.ts
1018
+ // query 2, deliberately unwindowed so the denominator is the full
1019
+ // assignment history). The dashboard labels them; this text had not,
1020
+ // so a narrowed `range` returned identical variant rates next to a
1021
+ // windowed headline and read as a windowed comparison.
1022
+ ...g.variants.map((v) => ` ${untrusted(v.componentId)}/${untrusted(v.variantId)}: ${(v.completionRate * 100).toFixed(1)}% per assigned session (all-time)`),
1015
1023
  ""
1016
1024
  ];
1017
1025
  });
1018
- const text = (win ? [win, "", ...lines] : lines).join("\n").trim();
1026
+ const hasVariants = data.goals.some((g) => g.variants.length > 0);
1027
+ const caveat = hasVariants ? ["", "Note: goal hits/conversion respect the window above; per-variant rates marked (all-time) do not."] : [];
1028
+ const text = (win ? [win, "", ...lines, ...caveat] : [...lines, ...caveat]).join("\n").trim();
1019
1029
  return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("goal-funnel") };
1020
1030
  })
1021
1031
  );
@@ -1247,8 +1257,11 @@ function registerGuardrailTools(server, client) {
1247
1257
  "list_guardrail_events",
1248
1258
  {
1249
1259
  title: "List guardrail events",
1250
- description: "List variants currently paused by the guardrail in the last 24 hours." + UNTRUSTED_FIELDS_NOTE,
1251
- inputSchema: { projectId: projectIdSchema },
1260
+ description: "List every variant currently paused by a guardrail (a paused variant stops serving until unpaused, however long ago the pause fired). Pass `days` to narrow to pauses fired within the last N days." + UNTRUSTED_FIELDS_NOTE,
1261
+ inputSchema: {
1262
+ projectId: projectIdSchema,
1263
+ days: z8.number().int().min(1).max(365).optional().describe("Only pauses fired within the last N days. Omit for every still-paused variant.")
1264
+ },
1252
1265
  outputSchema: {
1253
1266
  events: z8.array(
1254
1267
  z8.object({
@@ -1257,7 +1270,7 @@ function registerGuardrailTools(server, client) {
1257
1270
  pausedAt: z8.string().nullable().describe("ISO timestamp the pause fired, or null"),
1258
1271
  funnelId: z8.string().nullable().describe("Set when the pause came from a funnel guardrail")
1259
1272
  })
1260
- ).describe("Guardrail events in the last 24h (empty if none)")
1273
+ ).describe("Currently paused variants (empty if none)")
1261
1274
  },
1262
1275
  annotations: {
1263
1276
  readOnlyHint: true,
@@ -1265,9 +1278,10 @@ function registerGuardrailTools(server, client) {
1265
1278
  openWorldHint: false
1266
1279
  }
1267
1280
  },
1268
- withApiErrorGuidance(async ({ projectId }) => {
1281
+ withApiErrorGuidance(async ({ projectId, days }) => {
1269
1282
  const id = encodeURIComponent(projectId);
1270
- const data = await client.get(`/projects/${id}/guardrail-events`);
1283
+ const query = days != null ? `?days=${days}` : "";
1284
+ const data = await client.get(`/projects/${id}/guardrail-events${query}`);
1271
1285
  const structuredContent = {
1272
1286
  events: data.guardrailEvents.map((e) => {
1273
1287
  var _a;
@@ -1280,8 +1294,9 @@ function registerGuardrailTools(server, client) {
1280
1294
  })
1281
1295
  };
1282
1296
  if (!data.guardrailEvents.length) {
1297
+ const scope = days != null ? `paused by a guardrail in the last ${days} days` : "currently paused by a guardrail";
1283
1298
  return {
1284
- content: [{ type: "text", text: "No active guardrail events in the last 24 hours." }],
1299
+ content: [{ type: "text", text: `No variants ${scope}.` }],
1285
1300
  structuredContent
1286
1301
  };
1287
1302
  }
@@ -2003,7 +2018,7 @@ function registerAgentTrafficTools(server, client) {
2003
2018
  }
2004
2019
 
2005
2020
  // src/server.ts
2006
- var PKG_VERSION = true ? "0.17.5" : "0.0.0-dev";
2021
+ var PKG_VERSION = true ? "0.17.7" : "0.0.0-dev";
2007
2022
  function createMcpServer(client) {
2008
2023
  const server = new McpServer(
2009
2024
  {
package/dist/index.cjs CHANGED
@@ -48,10 +48,9 @@ var ApiClient = class {
48
48
  var _a2, _b;
49
49
  const res = await fetch(`${this.baseUrl}/v1/mgmt${path}`, {
50
50
  method,
51
- headers: {
52
- authorization: `Bearer ${this.apiKey}`,
53
- "content-type": "application/json"
54
- },
51
+ headers: __spreadValues({
52
+ authorization: `Bearer ${this.apiKey}`
53
+ }, body !== void 0 ? { "content-type": "application/json" } : {}),
55
54
  body: body !== void 0 ? JSON.stringify(body) : void 0
56
55
  });
57
56
  if (res.ok) return res.json();
@@ -948,7 +947,7 @@ function registerGoalTools(server, client) {
948
947
  "get_goal_funnel",
949
948
  {
950
949
  title: "Goal funnel",
951
- description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown (default window: last 30 calendar days). This is a flat per-goal list \u2014 for multi-step funnel drop-off, use get_funnel_report." + UNTRUSTED_FIELDS_NOTE,
950
+ description: "Get goal hit counts and unique-session conversion rates for a window (default: last 30 calendar days), plus a per-variant breakdown. NOTE: the per-variant completionRate values are ALL-TIME (bounded only by plan retention), NOT windowed \u2014 a variant completion rate needs the full assignment history as its denominator, so it does not change when you narrow `range`, and retired components keep appearing in it. Only hits/uniqueSessions/conversionRate/revenue respect the window. This is a flat per-goal list \u2014 for multi-step funnel drop-off, use get_funnel_report." + UNTRUSTED_FIELDS_NOTE,
952
951
  inputSchema: __spreadValues({ projectId: projectIdSchema }, rangeInputSchema("30d")),
953
952
  _meta: uiMeta("goal-funnel"),
954
953
  outputSchema: {
@@ -966,9 +965,13 @@ function registerGoalTools(server, client) {
966
965
  import_zod6.z.object({
967
966
  componentId: import_zod6.z.string(),
968
967
  variantId: import_zod6.z.string(),
969
- completionRate: import_zod6.z.number().describe("Completion rate per assigned session (0-1)")
968
+ completionRate: import_zod6.z.number().describe(
969
+ "ALL-TIME completion rate per assigned session (0-1). Not windowed: unaffected by `range`/`from`/`to`, and bounded only by plan retention."
970
+ )
970
971
  })
971
- ).describe("Per-variant breakdown")
972
+ ).describe(
973
+ "Per-variant breakdown. ALL-TIME, not windowed \u2014 do not compare these against the windowed hits/conversionRate above, and expect retired components here."
974
+ )
972
975
  })
973
976
  ).describe("Configured goals (empty if none)"),
974
977
  window: windowOutputSchema
@@ -1017,11 +1020,18 @@ function registerGoalTools(server, client) {
1017
1020
  var _a3;
1018
1021
  return [
1019
1022
  `${untrusted(g.goalName)}: ${g.hits} hits, ${g.uniqueSessions} unique sessions, ${(g.pct * 100).toFixed(1)}% conversion` + (g.revenue != null ? `, ${g.revenue.toFixed(2)} ${currency} revenue (${((_a3 = g.avgOrderValue) != null ? _a3 : 0).toFixed(2)} avg order)` : ""),
1020
- ...g.variants.map((v) => ` ${untrusted(v.componentId)}/${untrusted(v.variantId)}: ${(v.completionRate * 100).toFixed(1)}% per assigned session`),
1023
+ // The per-variant rates come from an all-time query (analytics.ts
1024
+ // query 2, deliberately unwindowed so the denominator is the full
1025
+ // assignment history). The dashboard labels them; this text had not,
1026
+ // so a narrowed `range` returned identical variant rates next to a
1027
+ // windowed headline and read as a windowed comparison.
1028
+ ...g.variants.map((v) => ` ${untrusted(v.componentId)}/${untrusted(v.variantId)}: ${(v.completionRate * 100).toFixed(1)}% per assigned session (all-time)`),
1021
1029
  ""
1022
1030
  ];
1023
1031
  });
1024
- const text = (win ? [win, "", ...lines] : lines).join("\n").trim();
1032
+ const hasVariants = data.goals.some((g) => g.variants.length > 0);
1033
+ const caveat = hasVariants ? ["", "Note: goal hits/conversion respect the window above; per-variant rates marked (all-time) do not."] : [];
1034
+ const text = (win ? [win, "", ...lines, ...caveat] : [...lines, ...caveat]).join("\n").trim();
1025
1035
  return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("goal-funnel") };
1026
1036
  })
1027
1037
  );
@@ -1253,8 +1263,11 @@ function registerGuardrailTools(server, client) {
1253
1263
  "list_guardrail_events",
1254
1264
  {
1255
1265
  title: "List guardrail events",
1256
- description: "List variants currently paused by the guardrail in the last 24 hours." + UNTRUSTED_FIELDS_NOTE,
1257
- inputSchema: { projectId: projectIdSchema },
1266
+ description: "List every variant currently paused by a guardrail (a paused variant stops serving until unpaused, however long ago the pause fired). Pass `days` to narrow to pauses fired within the last N days." + UNTRUSTED_FIELDS_NOTE,
1267
+ inputSchema: {
1268
+ projectId: projectIdSchema,
1269
+ days: import_zod8.z.number().int().min(1).max(365).optional().describe("Only pauses fired within the last N days. Omit for every still-paused variant.")
1270
+ },
1258
1271
  outputSchema: {
1259
1272
  events: import_zod8.z.array(
1260
1273
  import_zod8.z.object({
@@ -1263,7 +1276,7 @@ function registerGuardrailTools(server, client) {
1263
1276
  pausedAt: import_zod8.z.string().nullable().describe("ISO timestamp the pause fired, or null"),
1264
1277
  funnelId: import_zod8.z.string().nullable().describe("Set when the pause came from a funnel guardrail")
1265
1278
  })
1266
- ).describe("Guardrail events in the last 24h (empty if none)")
1279
+ ).describe("Currently paused variants (empty if none)")
1267
1280
  },
1268
1281
  annotations: {
1269
1282
  readOnlyHint: true,
@@ -1271,9 +1284,10 @@ function registerGuardrailTools(server, client) {
1271
1284
  openWorldHint: false
1272
1285
  }
1273
1286
  },
1274
- withApiErrorGuidance(async ({ projectId }) => {
1287
+ withApiErrorGuidance(async ({ projectId, days }) => {
1275
1288
  const id = encodeURIComponent(projectId);
1276
- const data = await client.get(`/projects/${id}/guardrail-events`);
1289
+ const query = days != null ? `?days=${days}` : "";
1290
+ const data = await client.get(`/projects/${id}/guardrail-events${query}`);
1277
1291
  const structuredContent = {
1278
1292
  events: data.guardrailEvents.map((e) => {
1279
1293
  var _a2;
@@ -1286,8 +1300,9 @@ function registerGuardrailTools(server, client) {
1286
1300
  })
1287
1301
  };
1288
1302
  if (!data.guardrailEvents.length) {
1303
+ const scope = days != null ? `paused by a guardrail in the last ${days} days` : "currently paused by a guardrail";
1289
1304
  return {
1290
- content: [{ type: "text", text: "No active guardrail events in the last 24 hours." }],
1305
+ content: [{ type: "text", text: `No variants ${scope}.` }],
1291
1306
  structuredContent
1292
1307
  };
1293
1308
  }
@@ -2009,7 +2024,7 @@ function registerAgentTrafficTools(server, client) {
2009
2024
  }
2010
2025
 
2011
2026
  // src/server.ts
2012
- var PKG_VERSION = true ? "0.17.5" : "0.0.0-dev";
2027
+ var PKG_VERSION = true ? "0.17.7" : "0.0.0-dev";
2013
2028
  function createMcpServer(client) {
2014
2029
  const server = new import_mcp.McpServer(
2015
2030
  {
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ApiClient,
4
4
  createMcpServer
5
- } from "./chunk-H763K2O3.js";
5
+ } from "./chunk-5PR2LCNN.js";
6
6
 
7
7
  // src/index.ts
8
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/dist/lib.cjs CHANGED
@@ -79,10 +79,9 @@ var ApiClient = class {
79
79
  var _a, _b;
80
80
  const res = await fetch(`${this.baseUrl}/v1/mgmt${path}`, {
81
81
  method,
82
- headers: {
83
- authorization: `Bearer ${this.apiKey}`,
84
- "content-type": "application/json"
85
- },
82
+ headers: __spreadValues({
83
+ authorization: `Bearer ${this.apiKey}`
84
+ }, body !== void 0 ? { "content-type": "application/json" } : {}),
86
85
  body: body !== void 0 ? JSON.stringify(body) : void 0
87
86
  });
88
87
  if (res.ok) return res.json();
@@ -972,7 +971,7 @@ function registerGoalTools(server, client) {
972
971
  "get_goal_funnel",
973
972
  {
974
973
  title: "Goal funnel",
975
- description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown (default window: last 30 calendar days). This is a flat per-goal list \u2014 for multi-step funnel drop-off, use get_funnel_report." + UNTRUSTED_FIELDS_NOTE,
974
+ description: "Get goal hit counts and unique-session conversion rates for a window (default: last 30 calendar days), plus a per-variant breakdown. NOTE: the per-variant completionRate values are ALL-TIME (bounded only by plan retention), NOT windowed \u2014 a variant completion rate needs the full assignment history as its denominator, so it does not change when you narrow `range`, and retired components keep appearing in it. Only hits/uniqueSessions/conversionRate/revenue respect the window. This is a flat per-goal list \u2014 for multi-step funnel drop-off, use get_funnel_report." + UNTRUSTED_FIELDS_NOTE,
976
975
  inputSchema: __spreadValues({ projectId: projectIdSchema }, rangeInputSchema("30d")),
977
976
  _meta: uiMeta("goal-funnel"),
978
977
  outputSchema: {
@@ -990,9 +989,13 @@ function registerGoalTools(server, client) {
990
989
  import_zod6.z.object({
991
990
  componentId: import_zod6.z.string(),
992
991
  variantId: import_zod6.z.string(),
993
- completionRate: import_zod6.z.number().describe("Completion rate per assigned session (0-1)")
992
+ completionRate: import_zod6.z.number().describe(
993
+ "ALL-TIME completion rate per assigned session (0-1). Not windowed: unaffected by `range`/`from`/`to`, and bounded only by plan retention."
994
+ )
994
995
  })
995
- ).describe("Per-variant breakdown")
996
+ ).describe(
997
+ "Per-variant breakdown. ALL-TIME, not windowed \u2014 do not compare these against the windowed hits/conversionRate above, and expect retired components here."
998
+ )
996
999
  })
997
1000
  ).describe("Configured goals (empty if none)"),
998
1001
  window: windowOutputSchema
@@ -1041,11 +1044,18 @@ function registerGoalTools(server, client) {
1041
1044
  var _a2;
1042
1045
  return [
1043
1046
  `${untrusted(g.goalName)}: ${g.hits} hits, ${g.uniqueSessions} unique sessions, ${(g.pct * 100).toFixed(1)}% conversion` + (g.revenue != null ? `, ${g.revenue.toFixed(2)} ${currency} revenue (${((_a2 = g.avgOrderValue) != null ? _a2 : 0).toFixed(2)} avg order)` : ""),
1044
- ...g.variants.map((v) => ` ${untrusted(v.componentId)}/${untrusted(v.variantId)}: ${(v.completionRate * 100).toFixed(1)}% per assigned session`),
1047
+ // The per-variant rates come from an all-time query (analytics.ts
1048
+ // query 2, deliberately unwindowed so the denominator is the full
1049
+ // assignment history). The dashboard labels them; this text had not,
1050
+ // so a narrowed `range` returned identical variant rates next to a
1051
+ // windowed headline and read as a windowed comparison.
1052
+ ...g.variants.map((v) => ` ${untrusted(v.componentId)}/${untrusted(v.variantId)}: ${(v.completionRate * 100).toFixed(1)}% per assigned session (all-time)`),
1045
1053
  ""
1046
1054
  ];
1047
1055
  });
1048
- const text = (win ? [win, "", ...lines] : lines).join("\n").trim();
1056
+ const hasVariants = data.goals.some((g) => g.variants.length > 0);
1057
+ const caveat = hasVariants ? ["", "Note: goal hits/conversion respect the window above; per-variant rates marked (all-time) do not."] : [];
1058
+ const text = (win ? [win, "", ...lines, ...caveat] : [...lines, ...caveat]).join("\n").trim();
1049
1059
  return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("goal-funnel") };
1050
1060
  })
1051
1061
  );
@@ -1277,8 +1287,11 @@ function registerGuardrailTools(server, client) {
1277
1287
  "list_guardrail_events",
1278
1288
  {
1279
1289
  title: "List guardrail events",
1280
- description: "List variants currently paused by the guardrail in the last 24 hours." + UNTRUSTED_FIELDS_NOTE,
1281
- inputSchema: { projectId: projectIdSchema },
1290
+ description: "List every variant currently paused by a guardrail (a paused variant stops serving until unpaused, however long ago the pause fired). Pass `days` to narrow to pauses fired within the last N days." + UNTRUSTED_FIELDS_NOTE,
1291
+ inputSchema: {
1292
+ projectId: projectIdSchema,
1293
+ days: import_zod8.z.number().int().min(1).max(365).optional().describe("Only pauses fired within the last N days. Omit for every still-paused variant.")
1294
+ },
1282
1295
  outputSchema: {
1283
1296
  events: import_zod8.z.array(
1284
1297
  import_zod8.z.object({
@@ -1287,7 +1300,7 @@ function registerGuardrailTools(server, client) {
1287
1300
  pausedAt: import_zod8.z.string().nullable().describe("ISO timestamp the pause fired, or null"),
1288
1301
  funnelId: import_zod8.z.string().nullable().describe("Set when the pause came from a funnel guardrail")
1289
1302
  })
1290
- ).describe("Guardrail events in the last 24h (empty if none)")
1303
+ ).describe("Currently paused variants (empty if none)")
1291
1304
  },
1292
1305
  annotations: {
1293
1306
  readOnlyHint: true,
@@ -1295,9 +1308,10 @@ function registerGuardrailTools(server, client) {
1295
1308
  openWorldHint: false
1296
1309
  }
1297
1310
  },
1298
- withApiErrorGuidance(async ({ projectId }) => {
1311
+ withApiErrorGuidance(async ({ projectId, days }) => {
1299
1312
  const id = encodeURIComponent(projectId);
1300
- const data = await client.get(`/projects/${id}/guardrail-events`);
1313
+ const query = days != null ? `?days=${days}` : "";
1314
+ const data = await client.get(`/projects/${id}/guardrail-events${query}`);
1301
1315
  const structuredContent = {
1302
1316
  events: data.guardrailEvents.map((e) => {
1303
1317
  var _a;
@@ -1310,8 +1324,9 @@ function registerGuardrailTools(server, client) {
1310
1324
  })
1311
1325
  };
1312
1326
  if (!data.guardrailEvents.length) {
1327
+ const scope = days != null ? `paused by a guardrail in the last ${days} days` : "currently paused by a guardrail";
1313
1328
  return {
1314
- content: [{ type: "text", text: "No active guardrail events in the last 24 hours." }],
1329
+ content: [{ type: "text", text: `No variants ${scope}.` }],
1315
1330
  structuredContent
1316
1331
  };
1317
1332
  }
@@ -2033,7 +2048,7 @@ function registerAgentTrafficTools(server, client) {
2033
2048
  }
2034
2049
 
2035
2050
  // src/server.ts
2036
- var PKG_VERSION = true ? "0.17.5" : "0.0.0-dev";
2051
+ var PKG_VERSION = true ? "0.17.7" : "0.0.0-dev";
2037
2052
  function createMcpServer(client) {
2038
2053
  const server = new import_mcp.McpServer(
2039
2054
  {
package/dist/lib.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  ApiError,
5
5
  PUBLIC_UI_RESOURCE_URIS,
6
6
  createMcpServer
7
- } from "./chunk-H763K2O3.js";
7
+ } from "./chunk-5PR2LCNN.js";
8
8
  export {
9
9
  ApiClient,
10
10
  ApiError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentientui/mcp",
3
- "version": "0.17.5",
3
+ "version": "0.17.7",
4
4
  "description": "MCP server for SentientUI — exposes project data and actions to AI agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://sentient-ui.com",