strapi-plugin-ai-sdk 0.7.4 → 0.7.9

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 (34) hide show
  1. package/dist/_chunks/{App-CEEsJsKL.js → App-DTbAnq7K.js} +712 -53
  2. package/dist/_chunks/{App-DCV7o6Hc.mjs → App-DZVBmpvs.mjs} +714 -55
  3. package/dist/_chunks/{index-Cw2aiQ8K.js → index-C3xn2IND.js} +1 -1
  4. package/dist/_chunks/{index-BMrDQVQl.mjs → index-Ca2F2wBp.mjs} +1 -1
  5. package/dist/admin/index.js +1 -1
  6. package/dist/admin/index.mjs +1 -1
  7. package/dist/admin/src/components/NotePanel.d.ts +15 -0
  8. package/dist/admin/src/hooks/useNotes.d.ts +22 -0
  9. package/dist/admin/src/pages/NoteStorePage.d.ts +2 -0
  10. package/dist/admin/src/utils/notes-api.d.ts +28 -0
  11. package/dist/server/index.js +603 -171
  12. package/dist/server/index.mjs +603 -171
  13. package/dist/server/src/content-types/index.d.ts +46 -0
  14. package/dist/server/src/content-types/note/index.d.ts +47 -0
  15. package/dist/server/src/controllers/index.d.ts +9 -0
  16. package/dist/server/src/controllers/note.d.ts +12 -0
  17. package/dist/server/src/index.d.ts +55 -0
  18. package/dist/server/src/mcp/resources/tool-guide.d.ts +5 -0
  19. package/dist/server/src/tool-logic/aggregate-content.d.ts +1 -1
  20. package/dist/server/src/tool-logic/create-content.d.ts +27 -0
  21. package/dist/server/src/tool-logic/find-one-content.d.ts +1 -1
  22. package/dist/server/src/tool-logic/index.d.ts +8 -2
  23. package/dist/server/src/tool-logic/list-content-types.d.ts +1 -1
  24. package/dist/server/src/tool-logic/manage-task.d.ts +1 -1
  25. package/dist/server/src/tool-logic/recall-notes.d.ts +22 -0
  26. package/dist/server/src/tool-logic/save-note.d.ts +17 -0
  27. package/dist/server/src/tool-logic/update-content.d.ts +29 -0
  28. package/dist/server/src/tool-logic/upload-media.d.ts +1 -1
  29. package/dist/server/src/tools/definitions/create-content.d.ts +2 -0
  30. package/dist/server/src/tools/definitions/{write-content.d.ts → recall-notes.d.ts} +1 -1
  31. package/dist/server/src/tools/definitions/save-note.d.ts +2 -0
  32. package/dist/server/src/tools/definitions/update-content.d.ts +2 -0
  33. package/package.json +1 -1
  34. package/dist/server/src/tool-logic/write-content.d.ts +0 -34
@@ -30,6 +30,90 @@ function _interopNamespace(e) {
30
30
  }
31
31
  const fs__namespace = /* @__PURE__ */ _interopNamespace(fs$1);
32
32
  const path__namespace = /* @__PURE__ */ _interopNamespace(path$1);
33
+ function toSnakeCase$1(str) {
34
+ return str.replace(/:/g, "__").replace(/-/g, "_").replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
35
+ }
36
+ function extractType(field) {
37
+ const def = field?._zod?.def;
38
+ if (!def) return "unknown";
39
+ switch (def.type) {
40
+ case "string":
41
+ return "string";
42
+ case "number":
43
+ return "number";
44
+ case "boolean":
45
+ return "boolean";
46
+ case "enum":
47
+ return Object.keys(def.entries).join(" | ");
48
+ case "optional":
49
+ return extractType({ _zod: { def: def.innerType } });
50
+ case "default":
51
+ return extractType({ _zod: { def: def.innerType } });
52
+ case "record":
53
+ return "object";
54
+ case "array":
55
+ return "array";
56
+ case "union":
57
+ return "string | array | object";
58
+ default:
59
+ return def.type || "unknown";
60
+ }
61
+ }
62
+ function formatParams(schema2) {
63
+ const shape = schema2.shape;
64
+ if (!shape || Object.keys(shape).length === 0) {
65
+ return "_No parameters._\n";
66
+ }
67
+ const rows = [];
68
+ rows.push("| Parameter | Type | Required | Description |");
69
+ rows.push("|-----------|------|----------|-------------|");
70
+ for (const [name, field] of Object.entries(shape)) {
71
+ const required = !field.isOptional?.();
72
+ const type = extractType(field);
73
+ const desc = (field.description || "").replace(/\|/g, "\\|");
74
+ rows.push(`| ${name} | ${type} | ${required ? "Yes" : "No"} | ${desc} |`);
75
+ }
76
+ return rows.join("\n") + "\n";
77
+ }
78
+ function generateToolGuide(registry) {
79
+ const tools = registry.getPublic();
80
+ const sources = registry.getToolSources();
81
+ const sections = [];
82
+ sections.push("# Strapi AI Tools Guide\n");
83
+ sections.push("## Getting Started\n");
84
+ sections.push(
85
+ "Start with `list_content_types` to discover your content types and their fields, then use `search_content` to query them.\n"
86
+ );
87
+ for (const source of sources) {
88
+ const heading = source.id === "built-in" ? "Built-in Tools" : source.label;
89
+ sections.push(`## ${heading}
90
+ `);
91
+ for (const toolName of source.tools) {
92
+ const def = tools.get(toolName);
93
+ if (!def) continue;
94
+ const mcpName = toSnakeCase$1(toolName);
95
+ sections.push(`### ${mcpName}
96
+ `);
97
+ sections.push(`${def.description}
98
+ `);
99
+ sections.push(formatParams(def.schema));
100
+ }
101
+ }
102
+ sections.push("## Common Workflows\n");
103
+ sections.push("### Create a blog post\n");
104
+ sections.push(
105
+ "1. Call `list_content_types` to discover the article/blog content type and its fields\n2. (Optional) Call `upload_media` with a URL to upload a cover image\n3. Call `create_content` with the content type UID and field data\n"
106
+ );
107
+ sections.push("### Find and update content\n");
108
+ sections.push(
109
+ "1. Call `search_content` with filters or a query to find the document\n2. Note the `documentId` from the results\n3. Call `update_content` with the content type, documentId, and fields to change\n"
110
+ );
111
+ sections.push("### Content analytics\n");
112
+ sections.push(
113
+ "1. Call `aggregate_content` with operation `count` for totals\n2. Use `countByField` with `groupByField` to see distribution (e.g. articles per category)\n3. Use `countByDateRange` with `granularity` to see trends over time\n"
114
+ );
115
+ return sections.join("\n");
116
+ }
33
117
  function toSnakeCase(str) {
34
118
  return str.replace(/:/g, "__").replace(/-/g, "_").replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
35
119
  }
@@ -46,7 +130,8 @@ function createMcpServer(strapi) {
46
130
  },
47
131
  {
48
132
  capabilities: {
49
- tools: {}
133
+ tools: {},
134
+ resources: {}
50
135
  }
51
136
  }
52
137
  );
@@ -74,6 +159,24 @@ function createMcpServer(strapi) {
74
159
  }
75
160
  );
76
161
  }
162
+ const guideMarkdown = generateToolGuide(registry);
163
+ server.registerResource(
164
+ "Tool Guide",
165
+ "strapi://tools/guide",
166
+ {
167
+ description: "Complete guide to all available Strapi AI tools with parameters and usage examples",
168
+ mimeType: "text/markdown"
169
+ },
170
+ async () => ({
171
+ contents: [
172
+ {
173
+ uri: "strapi://tools/guide",
174
+ mimeType: "text/markdown",
175
+ text: guideMarkdown
176
+ }
177
+ ]
178
+ })
179
+ );
77
180
  strapi.log.info("[ai-sdk:mcp] MCP server created with tools:", { tools: toolNames });
78
181
  return server;
79
182
  }
@@ -239,7 +342,7 @@ class ToolRegistry {
239
342
  }
240
343
  }
241
344
  const listContentTypesSchema = zod.z.object({});
242
- const listContentTypesDescription = "List all Strapi content types and components with their fields, relations, and structure.";
345
+ const listContentTypesDescription = 'List all Strapi content types and components with their fields, relations, and structure. This is the starting point for any content operation — call it first to discover content type UIDs (e.g. "api::article.article"), field names, relation targets, and components. No parameters required. Results are cached.';
243
346
  const INTERNAL_FIELDS = /* @__PURE__ */ new Set([
244
347
  "createdAt",
245
348
  "updatedAt",
@@ -387,33 +490,41 @@ async function searchContent(strapi, params) {
387
490
  }
388
491
  };
389
492
  }
390
- const writeContentSchema = zod.z.object({
493
+ const createContentSchema = zod.z.object({
391
494
  contentType: zod.z.string().describe('Content type UID, e.g. "api::article.article"'),
392
- action: zod.z.enum(["create", "update"]).describe("Whether to create a new document or update an existing one"),
393
- documentId: zod.z.string().optional().describe("Required for update — the document ID to update"),
394
495
  data: zod.z.record(zod.z.string(), zod.z.unknown()).describe("The field values to set. Must match the content type schema."),
395
496
  status: zod.z.enum(["draft", "published"]).optional().describe("Document status. Defaults to draft."),
396
497
  locale: zod.z.string().optional().describe('Locale code for i18n content, e.g. "en" or "fr"')
397
498
  });
398
- const writeContentDescription = "Create or update a document in any Strapi content type. Use listContentTypes first to discover the schema, and searchContent to find existing documents for updates.";
399
- async function writeContent(strapi, params) {
400
- const { contentType, action, documentId, data, status, locale } = params;
499
+ const createContentDescription = "Create a new document in any Strapi content type. Requires contentType (UID) and data (field values). Call listContentTypes first to discover UIDs and required fields. To attach media, upload it first with uploadMedia, then pass the file ID in data. Returns the created document with all fields populated.";
500
+ async function createContent(strapi, params) {
501
+ const { contentType, data, status, locale } = params;
401
502
  if (!strapi.contentTypes[contentType]) {
402
503
  throw new Error(`Content type "${contentType}" does not exist.`);
403
504
  }
404
- if (action === "update" && !documentId) {
405
- throw new Error("documentId is required for update actions.");
406
- }
407
505
  const docs = strapi.documents(contentType);
408
- if (action === "create") {
409
- const document2 = await docs.create({
410
- data,
411
- ...status ? { status } : {},
412
- ...locale ? { locale } : {},
413
- populate: "*"
414
- });
415
- return { action: "create", document: document2 };
506
+ const document = await docs.create({
507
+ data,
508
+ ...status ? { status } : {},
509
+ ...locale ? { locale } : {},
510
+ populate: "*"
511
+ });
512
+ return { action: "create", document };
513
+ }
514
+ const updateContentSchema = zod.z.object({
515
+ contentType: zod.z.string().describe('Content type UID, e.g. "api::article.article"'),
516
+ documentId: zod.z.string().describe("The document ID to update"),
517
+ data: zod.z.record(zod.z.string(), zod.z.unknown()).describe("The field values to set. Must match the content type schema."),
518
+ status: zod.z.enum(["draft", "published"]).optional().describe("Document status. Defaults to draft."),
519
+ locale: zod.z.string().optional().describe('Locale code for i18n content, e.g. "en" or "fr"')
520
+ });
521
+ const updateContentDescription = "Update an existing document in any Strapi content type. Requires contentType, documentId, and data (fields to change — only include fields you want to modify). Typical workflow: searchContent to find the documentId, then updateContent to modify it. Returns the updated document.";
522
+ async function updateContent(strapi, params) {
523
+ const { contentType, documentId, data, status, locale } = params;
524
+ if (!strapi.contentTypes[contentType]) {
525
+ throw new Error(`Content type "${contentType}" does not exist.`);
416
526
  }
527
+ const docs = strapi.documents(contentType);
417
528
  const existing = await docs.findOne({
418
529
  documentId,
419
530
  ...locale ? { locale } : {}
@@ -480,7 +591,7 @@ async function sendEmail(strapi, params) {
480
591
  subject: params.subject
481
592
  };
482
593
  }
483
- const CONTENT_TYPE$7 = "plugin::ai-sdk.memory";
594
+ const CONTENT_TYPE$a = "plugin::ai-sdk.memory";
484
595
  const saveMemorySchema = zod.z.object({
485
596
  content: zod.z.string().describe('A short, factual statement to remember about the user (e.g. "User prefers dark mode", "Company name is Acme Corp")'),
486
597
  category: zod.z.string().optional().describe('Category for the memory (e.g. "preference", "project", "personal", "general"). Defaults to "general".')
@@ -491,7 +602,7 @@ async function saveMemory(strapi, params, context) {
491
602
  return { success: false, message: "Cannot save memory: user context not available." };
492
603
  }
493
604
  try {
494
- await strapi.documents(CONTENT_TYPE$7).create({
605
+ await strapi.documents(CONTENT_TYPE$a).create({
495
606
  data: {
496
607
  content: params.content,
497
608
  category: params.category || "general",
@@ -504,7 +615,7 @@ async function saveMemory(strapi, params, context) {
504
615
  return { success: false, message: `Failed to save memory: ${detail}` };
505
616
  }
506
617
  }
507
- const CONTENT_TYPE$6 = "plugin::ai-sdk.memory";
618
+ const CONTENT_TYPE$9 = "plugin::ai-sdk.memory";
508
619
  const recallMemoriesSchema = zod.z.object({
509
620
  query: zod.z.string().optional().describe("Optional search term to filter memories by content"),
510
621
  category: zod.z.string().optional().describe('Optional category to filter by (e.g. "preference", "project", "personal")')
@@ -522,7 +633,7 @@ async function recallMemories(strapi, params, context) {
522
633
  if (params.query) {
523
634
  filters.content = { $containsi: params.query };
524
635
  }
525
- const memories = await strapi.documents(CONTENT_TYPE$6).findMany({
636
+ const memories = await strapi.documents(CONTENT_TYPE$9).findMany({
526
637
  filters,
527
638
  fields: ["content", "category"],
528
639
  sort: { createdAt: "desc" }
@@ -548,7 +659,7 @@ const findOneContentSchema = zod.z.object({
548
659
  status: zod.z.enum(["draft", "published"]).optional().describe("Document status filter."),
549
660
  locale: zod.z.string().optional().describe('Locale code for i18n content, e.g. "en" or "fr"')
550
661
  });
551
- const findOneContentDescription = "Fetch a single document by its documentId from any Strapi content type. Returns the full document with all fields and populated relations. Use searchContent first to discover document IDs.";
662
+ const findOneContentDescription = "Fetch a single document by its exact documentId. Use this when you already have a documentId and need full details with populated relations. If you need to find documents by title, field values, or full-text search, use searchContent instead. Requires contentType and documentId.";
552
663
  async function findOneContent(strapi, params) {
553
664
  const { contentType, documentId, populate = "*", fields, status, locale } = params;
554
665
  if (!strapi.contentTypes[contentType]) {
@@ -574,7 +685,7 @@ const uploadMediaSchema = zod.z.object({
574
685
  caption: zod.z.string().optional().describe("Caption for the media file"),
575
686
  alternativeText: zod.z.string().optional().describe("Alternative text for accessibility")
576
687
  });
577
- const uploadMediaDescription = "Upload a media file from a URL to the Strapi media library. Returns the uploaded file data. To link media to a content type field, use writeContent with the file ID.";
688
+ const uploadMediaDescription = "Upload a media file from a URL to the Strapi media library. This is step 1 of a two-step process: (1) upload the file with this tool to get a file ID, then (2) link it to a content entry using createContent or updateContent with { fieldName: fileId }. Requires url. Optional: name, caption, alternativeText.";
578
689
  async function uploadMedia(strapi, params) {
579
690
  const { url, name, caption, alternativeText } = params;
580
691
  let parsedUrl;
@@ -642,10 +753,10 @@ async function uploadMedia(strapi, params) {
642
753
  return {
643
754
  file: uploadedFile,
644
755
  message: `File "${uploadedFile.name}" uploaded successfully (ID: ${uploadedFile.id}).`,
645
- usage: `To link this file to a content type field, use writeContent with: { "fieldName": ${uploadedFile.id} }`
756
+ usage: `To link this file to a content type field, use createContent or updateContent with: { "fieldName": ${uploadedFile.id} }`
646
757
  };
647
758
  }
648
- const CONTENT_TYPE$5 = "plugin::ai-sdk.public-memory";
759
+ const CONTENT_TYPE$8 = "plugin::ai-sdk.public-memory";
649
760
  const recallPublicMemoriesSchema = zod.z.object({
650
761
  query: zod.z.string().optional().describe("Optional search term to filter public memories by content"),
651
762
  category: zod.z.string().optional().describe('Optional category to filter by (e.g. "faq", "product", "policy")')
@@ -656,7 +767,7 @@ async function recallPublicMemories(strapi, params) {
656
767
  const filters = {};
657
768
  if (params.category) filters.category = params.category;
658
769
  if (params.query) filters.content = { $containsi: params.query };
659
- const memories = await strapi.documents(CONTENT_TYPE$5).findMany({
770
+ const memories = await strapi.documents(CONTENT_TYPE$8).findMany({
660
771
  filters,
661
772
  fields: ["content", "category"],
662
773
  sort: { createdAt: "desc" }
@@ -738,7 +849,7 @@ const aggregateContentSchema = zod.z.object({
738
849
  status: zod.z.enum(["draft", "published"]).optional().describe("Filter by document status"),
739
850
  locale: zod.z.string().optional().describe("Locale code for i18n content")
740
851
  });
741
- const aggregateContentDescription = 'Aggregate and count content entries. Use for analytics questions like "how many articles per category", "content trends by month", or total counts with filters. Prefer this over searchContent for counting and grouping.';
852
+ const aggregateContentDescription = 'Count and group content entries for analytics. Answers questions like "how many articles?", "posts per category", "content published by month". Three operations: count (total), countByField (group by a field), countByDateRange (bucket by day/week/month). Prefer this over searchContent when you need counts or distributions, not the documents themselves.';
742
853
  function buildBaseQuery(params) {
743
854
  const { filters, status, locale, dateFrom, dateTo, dateField = "createdAt" } = params;
744
855
  const query = {};
@@ -890,7 +1001,7 @@ async function aggregateContent(strapi, params) {
890
1001
  throw new Error(`Unknown operation: ${operation}`);
891
1002
  }
892
1003
  }
893
- const CONTENT_TYPE$4 = "plugin::ai-sdk.task";
1004
+ const CONTENT_TYPE$7 = "plugin::ai-sdk.task";
894
1005
  const manageTaskSchema = zod.z.object({
895
1006
  action: zod.z.enum(["create", "update", "complete", "list", "summary"]).describe(
896
1007
  "Action to perform: create a new task, update an existing task, complete (mark done), list open tasks, or get a summary."
@@ -906,14 +1017,14 @@ const manageTaskSchema = zod.z.object({
906
1017
  done: zod.z.boolean().optional().describe("Set done status explicitly (for update action)."),
907
1018
  filters: zod.z.record(zod.z.string(), zod.z.unknown()).optional().describe("Additional Strapi filters for list action.")
908
1019
  });
909
- const manageTaskDescription = "Manage the user's task list. Create, update, complete, list, or summarize tasks. Tasks are scored by consequence × impact to help prioritize what matters most.";
1020
+ const manageTaskDescription = "Manage the user's task list. Actions: create (new task), update (modify fields), complete (mark done), list (open tasks sorted by priority score), summary (overview with overdue/urgent counts). Tasks are scored by consequence × impact to prioritize what matters most. For create: title is required; consequence and impact are collected via UI form if omitted.";
910
1021
  async function resolveTask(strapi, adminUserId, documentId, title) {
911
1022
  if (documentId) {
912
- const task2 = await strapi.documents(CONTENT_TYPE$4).findOne({ documentId });
1023
+ const task2 = await strapi.documents(CONTENT_TYPE$7).findOne({ documentId });
913
1024
  if (task2 && task2.adminUserId === adminUserId) return task2;
914
1025
  }
915
1026
  if (title) {
916
- const tasks = await strapi.documents(CONTENT_TYPE$4).findMany({
1027
+ const tasks = await strapi.documents(CONTENT_TYPE$7).findMany({
917
1028
  filters: {
918
1029
  adminUserId,
919
1030
  title: { $containsi: title }
@@ -966,7 +1077,7 @@ async function manageTask(strapi, params, context) {
966
1077
  if (params.consequence !== void 0) data.consequence = params.consequence;
967
1078
  if (params.impact !== void 0) data.impact = params.impact;
968
1079
  if (params.dueDate !== void 0) data.dueDate = params.dueDate;
969
- const updated = await strapi.documents(CONTENT_TYPE$4).update({
1080
+ const updated = await strapi.documents(CONTENT_TYPE$7).update({
970
1081
  documentId: duplicate.documentId,
971
1082
  data
972
1083
  });
@@ -977,7 +1088,7 @@ async function manageTask(strapi, params, context) {
977
1088
  data: updated
978
1089
  };
979
1090
  }
980
- const task2 = await strapi.documents(CONTENT_TYPE$4).create({
1091
+ const task2 = await strapi.documents(CONTENT_TYPE$7).create({
981
1092
  data: {
982
1093
  title: params.title,
983
1094
  description: params.description,
@@ -1006,7 +1117,7 @@ async function manageTask(strapi, params, context) {
1006
1117
  for (const key of ["title", "description", "content", "priority", "consequence", "impact", "dueDate", "done"]) {
1007
1118
  if (params[key] !== void 0) data[key] = params[key];
1008
1119
  }
1009
- const updated = await strapi.documents(CONTENT_TYPE$4).update({
1120
+ const updated = await strapi.documents(CONTENT_TYPE$7).update({
1010
1121
  documentId: existing.documentId,
1011
1122
  data
1012
1123
  });
@@ -1017,7 +1128,7 @@ async function manageTask(strapi, params, context) {
1017
1128
  if (!toComplete) {
1018
1129
  return { success: false, message: "Task not found. Provide a documentId or a title to search by." };
1019
1130
  }
1020
- await strapi.documents(CONTENT_TYPE$4).update({
1131
+ await strapi.documents(CONTENT_TYPE$7).update({
1021
1132
  documentId: toComplete.documentId,
1022
1133
  data: { done: true }
1023
1134
  });
@@ -1029,7 +1140,7 @@ async function manageTask(strapi, params, context) {
1029
1140
  done: false,
1030
1141
  ...params.filters
1031
1142
  };
1032
- const tasks = await strapi.documents(CONTENT_TYPE$4).findMany({
1143
+ const tasks = await strapi.documents(CONTENT_TYPE$7).findMany({
1033
1144
  filters
1034
1145
  });
1035
1146
  const sorted = tasks.sort((a, b) => {
@@ -1058,7 +1169,7 @@ async function manageTask(strapi, params, context) {
1058
1169
  };
1059
1170
  }
1060
1171
  case "summary": {
1061
- const allOpen = await strapi.documents(CONTENT_TYPE$4).findMany({
1172
+ const allOpen = await strapi.documents(CONTENT_TYPE$7).findMany({
1062
1173
  filters: { adminUserId, done: false }
1063
1174
  });
1064
1175
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -1095,6 +1206,82 @@ async function manageTask(strapi, params, context) {
1095
1206
  return { success: false, message: `Task operation failed: ${detail}` };
1096
1207
  }
1097
1208
  }
1209
+ const CONTENT_TYPE$6 = "plugin::ai-sdk.note";
1210
+ const saveNoteSchema = zod.z.object({
1211
+ title: zod.z.string().optional().describe("A short title or label for the note"),
1212
+ content: zod.z.string().describe("The note content in markdown format. Can include code blocks, lists, links, etc."),
1213
+ category: zod.z.string().optional().describe('Category for the note: "research", "snippet", "idea", or "reference". Defaults to "research".'),
1214
+ tags: zod.z.string().optional().describe('Comma-separated tags for filtering (e.g. "strapi, api, tutorial")'),
1215
+ source: zod.z.string().optional().describe('Where this note came from (e.g. "conversation", "web research", a URL)')
1216
+ });
1217
+ const saveNoteDescription = "Save a research note, code snippet, idea, or reference for the user. Use this when the user wants to save part of a conversation, research findings, code examples, or any content they may want to use later for writing blog posts or articles. Content is saved as markdown.";
1218
+ async function saveNote(strapi, params, context) {
1219
+ if (!context?.adminUserId) {
1220
+ return { success: false, message: "Cannot save note: user context not available." };
1221
+ }
1222
+ try {
1223
+ await strapi.documents(CONTENT_TYPE$6).create({
1224
+ data: {
1225
+ title: params.title || "",
1226
+ content: params.content,
1227
+ category: params.category || "research",
1228
+ tags: params.tags || "",
1229
+ source: params.source || "conversation",
1230
+ adminUserId: context.adminUserId
1231
+ }
1232
+ });
1233
+ const label = params.title ? `"${params.title}"` : "Note";
1234
+ return { success: true, message: `${label} saved to research notes.` };
1235
+ } catch (error) {
1236
+ const detail = error instanceof Error ? error.message : String(error);
1237
+ return { success: false, message: `Failed to save note: ${detail}` };
1238
+ }
1239
+ }
1240
+ const CONTENT_TYPE$5 = "plugin::ai-sdk.note";
1241
+ const recallNotesSchema = zod.z.object({
1242
+ query: zod.z.string().optional().describe("Optional search term to filter notes by title or content"),
1243
+ category: zod.z.string().optional().describe('Optional category to filter by: "research", "snippet", "idea", "reference"'),
1244
+ tag: zod.z.string().optional().describe("Optional tag to filter by (matches within comma-separated tags)")
1245
+ });
1246
+ const recallNotesDescription = "Recall saved research notes, snippets, ideas, and references. Use this to look up previously saved notes. Without parameters, returns all notes.";
1247
+ async function recallNotes(strapi, params, context) {
1248
+ if (!context?.adminUserId) {
1249
+ return { success: false, notes: [], count: 0 };
1250
+ }
1251
+ try {
1252
+ const filters = { adminUserId: context.adminUserId };
1253
+ if (params.category) {
1254
+ filters.category = params.category;
1255
+ }
1256
+ if (params.query) {
1257
+ filters.$or = [
1258
+ { title: { $containsi: params.query } },
1259
+ { content: { $containsi: params.query } }
1260
+ ];
1261
+ }
1262
+ if (params.tag) {
1263
+ filters.tags = { $containsi: params.tag };
1264
+ }
1265
+ const notes = await strapi.documents(CONTENT_TYPE$5).findMany({
1266
+ filters,
1267
+ fields: ["title", "content", "category", "tags", "source"],
1268
+ sort: { createdAt: "desc" }
1269
+ });
1270
+ return {
1271
+ success: true,
1272
+ notes: notes.map((n) => ({
1273
+ title: n.title || "",
1274
+ content: n.content,
1275
+ category: n.category,
1276
+ tags: n.tags || "",
1277
+ source: n.source || ""
1278
+ })),
1279
+ count: notes.length
1280
+ };
1281
+ } catch (error) {
1282
+ return { success: false, notes: [], count: 0 };
1283
+ }
1284
+ }
1098
1285
  const listContentTypesTool = {
1099
1286
  name: "listContentTypes",
1100
1287
  description: listContentTypesDescription,
@@ -1143,13 +1330,24 @@ async function sanitizeInput(strapi, uid, data, auth) {
1143
1330
  throw error;
1144
1331
  }
1145
1332
  }
1146
- const writeContentTool = {
1147
- name: "writeContent",
1148
- description: writeContentDescription,
1149
- schema: writeContentSchema,
1333
+ const createContentTool = {
1334
+ name: "createContent",
1335
+ description: createContentDescription,
1336
+ schema: createContentSchema,
1337
+ execute: async (args, strapi) => {
1338
+ const sanitizedData = await sanitizeInput(strapi, args.contentType, args.data);
1339
+ const result = await createContent(strapi, { ...args, data: sanitizedData });
1340
+ const sanitizedDoc = await sanitizeOutput(strapi, args.contentType, result.document);
1341
+ return { ...result, document: sanitizedDoc };
1342
+ }
1343
+ };
1344
+ const updateContentTool = {
1345
+ name: "updateContent",
1346
+ description: updateContentDescription,
1347
+ schema: updateContentSchema,
1150
1348
  execute: async (args, strapi) => {
1151
1349
  const sanitizedData = await sanitizeInput(strapi, args.contentType, args.data);
1152
- const result = await writeContent(strapi, { ...args, data: sanitizedData });
1350
+ const result = await updateContent(strapi, { ...args, data: sanitizedData });
1153
1351
  const sanitizedDoc = await sanitizeOutput(strapi, args.contentType, result.document);
1154
1352
  return { ...result, document: sanitizedDoc };
1155
1353
  }
@@ -1214,10 +1412,25 @@ const manageTaskTool = {
1214
1412
  execute: async (args, strapi, context) => manageTask(strapi, args, context),
1215
1413
  internal: true
1216
1414
  };
1415
+ const saveNoteTool = {
1416
+ name: "saveNote",
1417
+ description: saveNoteDescription,
1418
+ schema: saveNoteSchema,
1419
+ execute: async (args, strapi, context) => saveNote(strapi, args, context),
1420
+ internal: true
1421
+ };
1422
+ const recallNotesTool = {
1423
+ name: "recallNotes",
1424
+ description: recallNotesDescription,
1425
+ schema: recallNotesSchema,
1426
+ execute: async (args, strapi, context) => recallNotes(strapi, args, context),
1427
+ internal: true
1428
+ };
1217
1429
  const builtInTools = [
1218
1430
  listContentTypesTool,
1219
1431
  searchContentTool,
1220
- writeContentTool,
1432
+ createContentTool,
1433
+ updateContentTool,
1221
1434
  findOneContentTool,
1222
1435
  uploadMediaTool,
1223
1436
  sendEmailTool,
@@ -1225,12 +1438,22 @@ const builtInTools = [
1225
1438
  recallMemoriesTool,
1226
1439
  recallPublicMemoriesTool,
1227
1440
  aggregateContentTool,
1228
- manageTaskTool
1441
+ manageTaskTool,
1442
+ saveNoteTool,
1443
+ recallNotesTool
1229
1444
  ];
1230
1445
  const PLUGIN_ID$2 = "ai-sdk";
1231
1446
  const bootstrap = ({ strapi }) => {
1232
1447
  const plugin = strapi.plugin(PLUGIN_ID$2);
1233
1448
  const config2 = strapi.config.get(`plugin::${PLUGIN_ID$2}`);
1449
+ initializeProvider(strapi, plugin, config2);
1450
+ const registry = initializeToolRegistry(plugin);
1451
+ discoverPluginTools(strapi, registry);
1452
+ plugin.createMcpServer = () => createMcpServer(strapi);
1453
+ plugin.mcpSessions = /* @__PURE__ */ new Map();
1454
+ strapi.log.info(`[${PLUGIN_ID$2}] MCP endpoint available at: /api/${PLUGIN_ID$2}/mcp`);
1455
+ };
1456
+ function initializeProvider(strapi, plugin, config2) {
1234
1457
  AIProvider.registerProvider("anthropic", ({ apiKey, baseURL }) => {
1235
1458
  const provider = anthropic.createAnthropic({ apiKey, baseURL });
1236
1459
  return (modelId) => provider(modelId);
@@ -1243,27 +1466,22 @@ const bootstrap = ({ strapi }) => {
1243
1466
  } else {
1244
1467
  strapi.log.warn(`[${PLUGIN_ID$2}] anthropicApiKey not configured, AI provider will not be available`);
1245
1468
  }
1469
+ }
1470
+ function initializeToolRegistry(plugin) {
1246
1471
  const toolRegistry = new ToolRegistry();
1247
1472
  for (const tool of builtInTools) {
1248
1473
  toolRegistry.register(tool);
1249
1474
  }
1250
1475
  plugin.toolRegistry = toolRegistry;
1476
+ return toolRegistry;
1477
+ }
1478
+ function discoverPluginTools(strapi, registry) {
1251
1479
  const pluginNames = Object.keys(strapi.plugins).filter((n) => n !== PLUGIN_ID$2);
1252
1480
  strapi.log.info(`[${PLUGIN_ID$2}] Scanning ${pluginNames.length} plugins for ai-tools: [${pluginNames.join(", ")}]`);
1253
1481
  for (const [pluginName, pluginInstance] of Object.entries(strapi.plugins)) {
1254
1482
  if (pluginName === PLUGIN_ID$2) continue;
1255
1483
  try {
1256
- let aiToolsService = null;
1257
- try {
1258
- aiToolsService = strapi.plugin(pluginName)?.service?.("ai-tools");
1259
- } catch {
1260
- }
1261
- if (!aiToolsService) {
1262
- try {
1263
- aiToolsService = pluginInstance.service?.("ai-tools");
1264
- } catch {
1265
- }
1266
- }
1484
+ const aiToolsService = resolveAiToolsService(strapi, pluginName, pluginInstance);
1267
1485
  if (!aiToolsService?.getTools) {
1268
1486
  strapi.log.debug(`[${PLUGIN_ID$2}] No ai-tools service on plugin: ${pluginName}`);
1269
1487
  continue;
@@ -1271,21 +1489,7 @@ const bootstrap = ({ strapi }) => {
1271
1489
  strapi.log.info(`[${PLUGIN_ID$2}] Found ai-tools service on plugin: ${pluginName}`);
1272
1490
  const contributed = aiToolsService.getTools();
1273
1491
  if (!Array.isArray(contributed)) continue;
1274
- let count = 0;
1275
- for (const tool of contributed) {
1276
- if (!tool.name || !tool.execute || !tool.schema) {
1277
- strapi.log.warn(`[${PLUGIN_ID$2}] Invalid tool from ${pluginName}: ${tool.name || "unnamed"}`);
1278
- continue;
1279
- }
1280
- const safeName = pluginName.replace(/[^a-zA-Z0-9_-]/g, "_");
1281
- const namespacedName = `${safeName}__${tool.name}`;
1282
- if (toolRegistry.has(namespacedName)) {
1283
- strapi.log.warn(`[${PLUGIN_ID$2}] Duplicate tool: ${namespacedName}`);
1284
- continue;
1285
- }
1286
- toolRegistry.register({ ...tool, name: namespacedName });
1287
- count++;
1288
- }
1492
+ const count = registerContributedTools(strapi, registry, pluginName, contributed);
1289
1493
  if (count > 0) {
1290
1494
  strapi.log.info(`[${PLUGIN_ID$2}] Registered ${count} tools from plugin: ${pluginName}`);
1291
1495
  }
@@ -1293,10 +1497,38 @@ const bootstrap = ({ strapi }) => {
1293
1497
  strapi.log.warn(`[${PLUGIN_ID$2}] Tool discovery failed for ${pluginName}: ${err}`);
1294
1498
  }
1295
1499
  }
1296
- plugin.createMcpServer = () => createMcpServer(strapi);
1297
- plugin.mcpSessions = /* @__PURE__ */ new Map();
1298
- strapi.log.info(`[${PLUGIN_ID$2}] MCP endpoint available at: /api/${PLUGIN_ID$2}/mcp`);
1299
- };
1500
+ }
1501
+ function registerContributedTools(strapi, registry, pluginName, tools) {
1502
+ const safeName = pluginName.replace(/[^a-zA-Z0-9_-]/g, "_");
1503
+ let count = 0;
1504
+ for (const tool of tools) {
1505
+ if (!tool.name || !tool.execute || !tool.schema) {
1506
+ strapi.log.warn(`[${PLUGIN_ID$2}] Invalid tool from ${pluginName}: ${tool.name || "unnamed"}`);
1507
+ continue;
1508
+ }
1509
+ const namespacedName = `${safeName}__${tool.name}`;
1510
+ if (registry.has(namespacedName)) {
1511
+ strapi.log.warn(`[${PLUGIN_ID$2}] Duplicate tool: ${namespacedName}`);
1512
+ continue;
1513
+ }
1514
+ registry.register({ ...tool, name: namespacedName });
1515
+ count++;
1516
+ }
1517
+ return count;
1518
+ }
1519
+ function resolveAiToolsService(strapi, pluginName, pluginInstance) {
1520
+ try {
1521
+ const svc = strapi.plugin(pluginName)?.service?.("ai-tools");
1522
+ if (svc) return svc;
1523
+ } catch {
1524
+ }
1525
+ try {
1526
+ const svc = pluginInstance.service?.("ai-tools");
1527
+ if (svc) return svc;
1528
+ } catch {
1529
+ }
1530
+ return null;
1531
+ }
1300
1532
  const PLUGIN_ID$1 = "ai-sdk";
1301
1533
  async function closeSession(strapi, sessionId, session) {
1302
1534
  try {
@@ -1379,17 +1611,17 @@ const config = {
1379
1611
  }
1380
1612
  }
1381
1613
  };
1382
- const kind$3 = "collectionType";
1383
- const collectionName$3 = "ai_sdk_conversations";
1384
- const info$3 = {
1614
+ const kind$4 = "collectionType";
1615
+ const collectionName$4 = "ai_sdk_conversations";
1616
+ const info$4 = {
1385
1617
  singularName: "conversation",
1386
1618
  pluralName: "conversations",
1387
1619
  displayName: "AI Conversation"
1388
1620
  };
1389
- const options$3 = {
1621
+ const options$4 = {
1390
1622
  draftAndPublish: false
1391
1623
  };
1392
- const pluginOptions$3 = {
1624
+ const pluginOptions$4 = {
1393
1625
  "content-manager": {
1394
1626
  visible: false
1395
1627
  },
@@ -1397,7 +1629,7 @@ const pluginOptions$3 = {
1397
1629
  visible: false
1398
1630
  }
1399
1631
  };
1400
- const attributes$3 = {
1632
+ const attributes$4 = {
1401
1633
  title: {
1402
1634
  type: "string",
1403
1635
  required: true,
@@ -1412,26 +1644,26 @@ const attributes$3 = {
1412
1644
  required: true
1413
1645
  }
1414
1646
  };
1415
- const schema$3 = {
1416
- kind: kind$3,
1417
- collectionName: collectionName$3,
1418
- info: info$3,
1419
- options: options$3,
1420
- pluginOptions: pluginOptions$3,
1421
- attributes: attributes$3
1647
+ const schema$4 = {
1648
+ kind: kind$4,
1649
+ collectionName: collectionName$4,
1650
+ info: info$4,
1651
+ options: options$4,
1652
+ pluginOptions: pluginOptions$4,
1653
+ attributes: attributes$4
1422
1654
  };
1423
- const conversation = { schema: schema$3 };
1424
- const kind$2 = "collectionType";
1425
- const collectionName$2 = "ai_sdk_memories";
1426
- const info$2 = {
1655
+ const conversation = { schema: schema$4 };
1656
+ const kind$3 = "collectionType";
1657
+ const collectionName$3 = "ai_sdk_memories";
1658
+ const info$3 = {
1427
1659
  singularName: "memory",
1428
1660
  pluralName: "memories",
1429
1661
  displayName: "AI Memory"
1430
1662
  };
1431
- const options$2 = {
1663
+ const options$3 = {
1432
1664
  draftAndPublish: false
1433
1665
  };
1434
- const pluginOptions$2 = {
1666
+ const pluginOptions$3 = {
1435
1667
  "content-manager": {
1436
1668
  visible: false
1437
1669
  },
@@ -1439,9 +1671,9 @@ const pluginOptions$2 = {
1439
1671
  visible: false
1440
1672
  }
1441
1673
  };
1442
- const attributes$2 = {
1674
+ const attributes$3 = {
1443
1675
  content: {
1444
- type: "text",
1676
+ type: "richtext",
1445
1677
  required: true
1446
1678
  },
1447
1679
  category: {
@@ -1459,26 +1691,26 @@ const attributes$2 = {
1459
1691
  required: true
1460
1692
  }
1461
1693
  };
1462
- const schema$2 = {
1463
- kind: kind$2,
1464
- collectionName: collectionName$2,
1465
- info: info$2,
1466
- options: options$2,
1467
- pluginOptions: pluginOptions$2,
1468
- attributes: attributes$2
1694
+ const schema$3 = {
1695
+ kind: kind$3,
1696
+ collectionName: collectionName$3,
1697
+ info: info$3,
1698
+ options: options$3,
1699
+ pluginOptions: pluginOptions$3,
1700
+ attributes: attributes$3
1469
1701
  };
1470
- const memory = { schema: schema$2 };
1471
- const kind$1 = "collectionType";
1472
- const collectionName$1 = "ai_sdk_public_memories";
1473
- const info$1 = {
1702
+ const memory = { schema: schema$3 };
1703
+ const kind$2 = "collectionType";
1704
+ const collectionName$2 = "ai_sdk_public_memories";
1705
+ const info$2 = {
1474
1706
  singularName: "public-memory",
1475
1707
  pluralName: "public-memories",
1476
1708
  displayName: "AI Public Memory"
1477
1709
  };
1478
- const options$1 = {
1710
+ const options$2 = {
1479
1711
  draftAndPublish: false
1480
1712
  };
1481
- const pluginOptions$1 = {
1713
+ const pluginOptions$2 = {
1482
1714
  "content-manager": {
1483
1715
  visible: false
1484
1716
  },
@@ -1486,7 +1718,7 @@ const pluginOptions$1 = {
1486
1718
  visible: false
1487
1719
  }
1488
1720
  };
1489
- const attributes$1 = {
1721
+ const attributes$2 = {
1490
1722
  content: {
1491
1723
  type: "text",
1492
1724
  required: true
@@ -1502,26 +1734,26 @@ const attributes$1 = {
1502
1734
  "default": "general"
1503
1735
  }
1504
1736
  };
1505
- const schema$1 = {
1506
- kind: kind$1,
1507
- collectionName: collectionName$1,
1508
- info: info$1,
1509
- options: options$1,
1510
- pluginOptions: pluginOptions$1,
1511
- attributes: attributes$1
1737
+ const schema$2 = {
1738
+ kind: kind$2,
1739
+ collectionName: collectionName$2,
1740
+ info: info$2,
1741
+ options: options$2,
1742
+ pluginOptions: pluginOptions$2,
1743
+ attributes: attributes$2
1512
1744
  };
1513
- const publicMemory = { schema: schema$1 };
1514
- const kind = "collectionType";
1515
- const collectionName = "ai_sdk_tasks";
1516
- const info = {
1745
+ const publicMemory = { schema: schema$2 };
1746
+ const kind$1 = "collectionType";
1747
+ const collectionName$1 = "ai_sdk_tasks";
1748
+ const info$1 = {
1517
1749
  singularName: "task",
1518
1750
  pluralName: "tasks",
1519
1751
  displayName: "AI Task"
1520
1752
  };
1521
- const options = {
1753
+ const options$1 = {
1522
1754
  draftAndPublish: false
1523
1755
  };
1524
- const pluginOptions = {
1756
+ const pluginOptions$1 = {
1525
1757
  "content-manager": {
1526
1758
  visible: true
1527
1759
  },
@@ -1529,7 +1761,7 @@ const pluginOptions = {
1529
1761
  visible: false
1530
1762
  }
1531
1763
  };
1532
- const attributes = {
1764
+ const attributes$1 = {
1533
1765
  title: {
1534
1766
  type: "string",
1535
1767
  required: true
@@ -1574,6 +1806,62 @@ const attributes = {
1574
1806
  required: true
1575
1807
  }
1576
1808
  };
1809
+ const schema$1 = {
1810
+ kind: kind$1,
1811
+ collectionName: collectionName$1,
1812
+ info: info$1,
1813
+ options: options$1,
1814
+ pluginOptions: pluginOptions$1,
1815
+ attributes: attributes$1
1816
+ };
1817
+ const task = { schema: schema$1 };
1818
+ const kind = "collectionType";
1819
+ const collectionName = "ai_sdk_notes";
1820
+ const info = {
1821
+ singularName: "note",
1822
+ pluralName: "notes",
1823
+ displayName: "AI Note"
1824
+ };
1825
+ const options = {
1826
+ draftAndPublish: false
1827
+ };
1828
+ const pluginOptions = {
1829
+ "content-manager": {
1830
+ visible: false
1831
+ },
1832
+ "content-type-builder": {
1833
+ visible: false
1834
+ }
1835
+ };
1836
+ const attributes = {
1837
+ title: {
1838
+ type: "string"
1839
+ },
1840
+ content: {
1841
+ type: "richtext",
1842
+ required: true
1843
+ },
1844
+ category: {
1845
+ type: "enumeration",
1846
+ "enum": [
1847
+ "research",
1848
+ "snippet",
1849
+ "idea",
1850
+ "reference"
1851
+ ],
1852
+ "default": "research"
1853
+ },
1854
+ tags: {
1855
+ type: "text"
1856
+ },
1857
+ source: {
1858
+ type: "string"
1859
+ },
1860
+ adminUserId: {
1861
+ type: "integer",
1862
+ required: true
1863
+ }
1864
+ };
1577
1865
  const schema = {
1578
1866
  kind,
1579
1867
  collectionName,
@@ -1582,8 +1870,8 @@ const schema = {
1582
1870
  pluginOptions,
1583
1871
  attributes
1584
1872
  };
1585
- const task = { schema };
1586
- const contentTypes = { conversation, memory, "public-memory": publicMemory, task };
1873
+ const note = { schema };
1874
+ const contentTypes = { conversation, memory, "public-memory": publicMemory, task, note };
1587
1875
  function getService(strapi, ctx) {
1588
1876
  const service2 = strapi.plugin("ai-sdk").service("service");
1589
1877
  if (!service2.isInitialized()) {
@@ -1899,20 +2187,20 @@ const mcpController = ({ strapi }) => {
1899
2187
  }
1900
2188
  };
1901
2189
  };
1902
- const CONTENT_TYPE$3 = "plugin::ai-sdk.conversation";
1903
- function getAdminUserId$2(ctx) {
2190
+ const CONTENT_TYPE$4 = "plugin::ai-sdk.conversation";
2191
+ function getAdminUserId$3(ctx) {
1904
2192
  const id = ctx.state?.user?.id;
1905
2193
  return typeof id === "number" ? id : null;
1906
2194
  }
1907
2195
  const conversationController = ({ strapi }) => ({
1908
2196
  async find(ctx) {
1909
- const adminUserId = getAdminUserId$2(ctx);
2197
+ const adminUserId = getAdminUserId$3(ctx);
1910
2198
  if (!adminUserId) {
1911
2199
  ctx.status = 401;
1912
2200
  ctx.body = { error: "Unauthorized" };
1913
2201
  return;
1914
2202
  }
1915
- const conversations = await strapi.documents(CONTENT_TYPE$3).findMany({
2203
+ const conversations = await strapi.documents(CONTENT_TYPE$4).findMany({
1916
2204
  filters: { adminUserId },
1917
2205
  fields: ["title", "createdAt", "updatedAt"],
1918
2206
  sort: { updatedAt: "desc" }
@@ -1920,14 +2208,14 @@ const conversationController = ({ strapi }) => ({
1920
2208
  ctx.body = { data: conversations };
1921
2209
  },
1922
2210
  async findOne(ctx) {
1923
- const adminUserId = getAdminUserId$2(ctx);
2211
+ const adminUserId = getAdminUserId$3(ctx);
1924
2212
  if (!adminUserId) {
1925
2213
  ctx.status = 401;
1926
2214
  ctx.body = { error: "Unauthorized" };
1927
2215
  return;
1928
2216
  }
1929
2217
  const { id } = ctx.params;
1930
- const conversation2 = await strapi.documents(CONTENT_TYPE$3).findOne({
2218
+ const conversation2 = await strapi.documents(CONTENT_TYPE$4).findOne({
1931
2219
  documentId: id
1932
2220
  });
1933
2221
  if (!conversation2 || conversation2.adminUserId !== adminUserId) {
@@ -1938,14 +2226,14 @@ const conversationController = ({ strapi }) => ({
1938
2226
  ctx.body = { data: conversation2 };
1939
2227
  },
1940
2228
  async create(ctx) {
1941
- const adminUserId = getAdminUserId$2(ctx);
2229
+ const adminUserId = getAdminUserId$3(ctx);
1942
2230
  if (!adminUserId) {
1943
2231
  ctx.status = 401;
1944
2232
  ctx.body = { error: "Unauthorized" };
1945
2233
  return;
1946
2234
  }
1947
2235
  const { title, messages } = ctx.request.body;
1948
- const conversation2 = await strapi.documents(CONTENT_TYPE$3).create({
2236
+ const conversation2 = await strapi.documents(CONTENT_TYPE$4).create({
1949
2237
  data: {
1950
2238
  title: title || "New conversation",
1951
2239
  messages: messages || [],
@@ -1956,14 +2244,14 @@ const conversationController = ({ strapi }) => ({
1956
2244
  ctx.body = { data: conversation2 };
1957
2245
  },
1958
2246
  async update(ctx) {
1959
- const adminUserId = getAdminUserId$2(ctx);
2247
+ const adminUserId = getAdminUserId$3(ctx);
1960
2248
  if (!adminUserId) {
1961
2249
  ctx.status = 401;
1962
2250
  ctx.body = { error: "Unauthorized" };
1963
2251
  return;
1964
2252
  }
1965
2253
  const { id } = ctx.params;
1966
- const existing = await strapi.documents(CONTENT_TYPE$3).findOne({
2254
+ const existing = await strapi.documents(CONTENT_TYPE$4).findOne({
1967
2255
  documentId: id
1968
2256
  });
1969
2257
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -1975,21 +2263,21 @@ const conversationController = ({ strapi }) => ({
1975
2263
  const data = {};
1976
2264
  if (title !== void 0) data.title = title;
1977
2265
  if (messages !== void 0) data.messages = messages;
1978
- const conversation2 = await strapi.documents(CONTENT_TYPE$3).update({
2266
+ const conversation2 = await strapi.documents(CONTENT_TYPE$4).update({
1979
2267
  documentId: id,
1980
2268
  data
1981
2269
  });
1982
2270
  ctx.body = { data: conversation2 };
1983
2271
  },
1984
2272
  async delete(ctx) {
1985
- const adminUserId = getAdminUserId$2(ctx);
2273
+ const adminUserId = getAdminUserId$3(ctx);
1986
2274
  if (!adminUserId) {
1987
2275
  ctx.status = 401;
1988
2276
  ctx.body = { error: "Unauthorized" };
1989
2277
  return;
1990
2278
  }
1991
2279
  const { id } = ctx.params;
1992
- const existing = await strapi.documents(CONTENT_TYPE$3).findOne({
2280
+ const existing = await strapi.documents(CONTENT_TYPE$4).findOne({
1993
2281
  documentId: id
1994
2282
  });
1995
2283
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -1997,25 +2285,25 @@ const conversationController = ({ strapi }) => ({
1997
2285
  ctx.body = { error: "Conversation not found" };
1998
2286
  return;
1999
2287
  }
2000
- await strapi.documents(CONTENT_TYPE$3).delete({ documentId: id });
2288
+ await strapi.documents(CONTENT_TYPE$4).delete({ documentId: id });
2001
2289
  ctx.status = 200;
2002
2290
  ctx.body = { data: { documentId: id } };
2003
2291
  }
2004
2292
  });
2005
- const CONTENT_TYPE$2 = "plugin::ai-sdk.memory";
2006
- function getAdminUserId$1(ctx) {
2293
+ const CONTENT_TYPE$3 = "plugin::ai-sdk.memory";
2294
+ function getAdminUserId$2(ctx) {
2007
2295
  const id = ctx.state?.user?.id;
2008
2296
  return typeof id === "number" ? id : null;
2009
2297
  }
2010
2298
  const memoryController = ({ strapi }) => ({
2011
2299
  async find(ctx) {
2012
- const adminUserId = getAdminUserId$1(ctx);
2300
+ const adminUserId = getAdminUserId$2(ctx);
2013
2301
  if (!adminUserId) {
2014
2302
  ctx.status = 401;
2015
2303
  ctx.body = { error: "Unauthorized" };
2016
2304
  return;
2017
2305
  }
2018
- const memories = await strapi.documents(CONTENT_TYPE$2).findMany({
2306
+ const memories = await strapi.documents(CONTENT_TYPE$3).findMany({
2019
2307
  filters: { adminUserId },
2020
2308
  fields: ["content", "category", "createdAt"],
2021
2309
  sort: { createdAt: "desc" }
@@ -2023,7 +2311,7 @@ const memoryController = ({ strapi }) => ({
2023
2311
  ctx.body = { data: memories };
2024
2312
  },
2025
2313
  async create(ctx) {
2026
- const adminUserId = getAdminUserId$1(ctx);
2314
+ const adminUserId = getAdminUserId$2(ctx);
2027
2315
  if (!adminUserId) {
2028
2316
  ctx.status = 401;
2029
2317
  ctx.body = { error: "Unauthorized" };
@@ -2035,7 +2323,7 @@ const memoryController = ({ strapi }) => ({
2035
2323
  ctx.body = { error: "content is required" };
2036
2324
  return;
2037
2325
  }
2038
- const memory2 = await strapi.documents(CONTENT_TYPE$2).create({
2326
+ const memory2 = await strapi.documents(CONTENT_TYPE$3).create({
2039
2327
  data: {
2040
2328
  content,
2041
2329
  category: category || "general",
@@ -2046,14 +2334,14 @@ const memoryController = ({ strapi }) => ({
2046
2334
  ctx.body = { data: memory2 };
2047
2335
  },
2048
2336
  async update(ctx) {
2049
- const adminUserId = getAdminUserId$1(ctx);
2337
+ const adminUserId = getAdminUserId$2(ctx);
2050
2338
  if (!adminUserId) {
2051
2339
  ctx.status = 401;
2052
2340
  ctx.body = { error: "Unauthorized" };
2053
2341
  return;
2054
2342
  }
2055
2343
  const { id } = ctx.params;
2056
- const existing = await strapi.documents(CONTENT_TYPE$2).findOne({
2344
+ const existing = await strapi.documents(CONTENT_TYPE$3).findOne({
2057
2345
  documentId: id
2058
2346
  });
2059
2347
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -2065,21 +2353,21 @@ const memoryController = ({ strapi }) => ({
2065
2353
  const data = {};
2066
2354
  if (content !== void 0) data.content = content;
2067
2355
  if (category !== void 0) data.category = category;
2068
- const memory2 = await strapi.documents(CONTENT_TYPE$2).update({
2356
+ const memory2 = await strapi.documents(CONTENT_TYPE$3).update({
2069
2357
  documentId: id,
2070
2358
  data
2071
2359
  });
2072
2360
  ctx.body = { data: memory2 };
2073
2361
  },
2074
2362
  async delete(ctx) {
2075
- const adminUserId = getAdminUserId$1(ctx);
2363
+ const adminUserId = getAdminUserId$2(ctx);
2076
2364
  if (!adminUserId) {
2077
2365
  ctx.status = 401;
2078
2366
  ctx.body = { error: "Unauthorized" };
2079
2367
  return;
2080
2368
  }
2081
2369
  const { id } = ctx.params;
2082
- const existing = await strapi.documents(CONTENT_TYPE$2).findOne({
2370
+ const existing = await strapi.documents(CONTENT_TYPE$3).findOne({
2083
2371
  documentId: id
2084
2372
  });
2085
2373
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -2087,15 +2375,15 @@ const memoryController = ({ strapi }) => ({
2087
2375
  ctx.body = { error: "Memory not found" };
2088
2376
  return;
2089
2377
  }
2090
- await strapi.documents(CONTENT_TYPE$2).delete({ documentId: id });
2378
+ await strapi.documents(CONTENT_TYPE$3).delete({ documentId: id });
2091
2379
  ctx.status = 200;
2092
2380
  ctx.body = { data: { documentId: id } };
2093
2381
  }
2094
2382
  });
2095
- const CONTENT_TYPE$1 = "plugin::ai-sdk.public-memory";
2383
+ const CONTENT_TYPE$2 = "plugin::ai-sdk.public-memory";
2096
2384
  const publicMemoryController = ({ strapi }) => ({
2097
2385
  async find(ctx) {
2098
- const memories = await strapi.documents(CONTENT_TYPE$1).findMany({
2386
+ const memories = await strapi.documents(CONTENT_TYPE$2).findMany({
2099
2387
  fields: ["content", "category", "createdAt"],
2100
2388
  sort: { createdAt: "desc" }
2101
2389
  });
@@ -2108,7 +2396,7 @@ const publicMemoryController = ({ strapi }) => ({
2108
2396
  ctx.body = { error: "content is required" };
2109
2397
  return;
2110
2398
  }
2111
- const memory2 = await strapi.documents(CONTENT_TYPE$1).create({
2399
+ const memory2 = await strapi.documents(CONTENT_TYPE$2).create({
2112
2400
  data: { content, category: category || "general" }
2113
2401
  });
2114
2402
  ctx.status = 201;
@@ -2116,7 +2404,7 @@ const publicMemoryController = ({ strapi }) => ({
2116
2404
  },
2117
2405
  async update(ctx) {
2118
2406
  const { id } = ctx.params;
2119
- const existing = await strapi.documents(CONTENT_TYPE$1).findOne({ documentId: id });
2407
+ const existing = await strapi.documents(CONTENT_TYPE$2).findOne({ documentId: id });
2120
2408
  if (!existing) {
2121
2409
  ctx.status = 404;
2122
2410
  ctx.body = { error: "Public memory not found" };
@@ -2126,7 +2414,7 @@ const publicMemoryController = ({ strapi }) => ({
2126
2414
  const data = {};
2127
2415
  if (content !== void 0) data.content = content;
2128
2416
  if (category !== void 0) data.category = category;
2129
- const memory2 = await strapi.documents(CONTENT_TYPE$1).update({
2417
+ const memory2 = await strapi.documents(CONTENT_TYPE$2).update({
2130
2418
  documentId: id,
2131
2419
  data
2132
2420
  });
@@ -2134,45 +2422,45 @@ const publicMemoryController = ({ strapi }) => ({
2134
2422
  },
2135
2423
  async delete(ctx) {
2136
2424
  const { id } = ctx.params;
2137
- const existing = await strapi.documents(CONTENT_TYPE$1).findOne({ documentId: id });
2425
+ const existing = await strapi.documents(CONTENT_TYPE$2).findOne({ documentId: id });
2138
2426
  if (!existing) {
2139
2427
  ctx.status = 404;
2140
2428
  ctx.body = { error: "Public memory not found" };
2141
2429
  return;
2142
2430
  }
2143
- await strapi.documents(CONTENT_TYPE$1).delete({ documentId: id });
2431
+ await strapi.documents(CONTENT_TYPE$2).delete({ documentId: id });
2144
2432
  ctx.status = 200;
2145
2433
  ctx.body = { data: { documentId: id } };
2146
2434
  }
2147
2435
  });
2148
- const CONTENT_TYPE = "plugin::ai-sdk.task";
2149
- function getAdminUserId(ctx) {
2436
+ const CONTENT_TYPE$1 = "plugin::ai-sdk.task";
2437
+ function getAdminUserId$1(ctx) {
2150
2438
  const id = ctx.state?.user?.id;
2151
2439
  return typeof id === "number" ? id : null;
2152
2440
  }
2153
2441
  const taskController = ({ strapi }) => ({
2154
2442
  async find(ctx) {
2155
- const adminUserId = getAdminUserId(ctx);
2443
+ const adminUserId = getAdminUserId$1(ctx);
2156
2444
  if (!adminUserId) {
2157
2445
  ctx.status = 401;
2158
2446
  ctx.body = { error: "Unauthorized" };
2159
2447
  return;
2160
2448
  }
2161
- const tasks = await strapi.documents(CONTENT_TYPE).findMany({
2449
+ const tasks = await strapi.documents(CONTENT_TYPE$1).findMany({
2162
2450
  filters: { adminUserId },
2163
2451
  sort: { createdAt: "desc" }
2164
2452
  });
2165
2453
  ctx.body = { data: tasks };
2166
2454
  },
2167
2455
  async create(ctx) {
2168
- const adminUserId = getAdminUserId(ctx);
2456
+ const adminUserId = getAdminUserId$1(ctx);
2169
2457
  if (!adminUserId) {
2170
2458
  ctx.status = 401;
2171
2459
  ctx.body = { error: "Unauthorized" };
2172
2460
  return;
2173
2461
  }
2174
2462
  const body = ctx.request.body;
2175
- const task2 = await strapi.documents(CONTENT_TYPE).create({
2463
+ const task2 = await strapi.documents(CONTENT_TYPE$1).create({
2176
2464
  data: {
2177
2465
  title: body.title,
2178
2466
  description: body.description,
@@ -2189,14 +2477,14 @@ const taskController = ({ strapi }) => ({
2189
2477
  ctx.body = { data: task2 };
2190
2478
  },
2191
2479
  async update(ctx) {
2192
- const adminUserId = getAdminUserId(ctx);
2480
+ const adminUserId = getAdminUserId$1(ctx);
2193
2481
  if (!adminUserId) {
2194
2482
  ctx.status = 401;
2195
2483
  ctx.body = { error: "Unauthorized" };
2196
2484
  return;
2197
2485
  }
2198
2486
  const { id } = ctx.params;
2199
- const existing = await strapi.documents(CONTENT_TYPE).findOne({
2487
+ const existing = await strapi.documents(CONTENT_TYPE$1).findOne({
2200
2488
  documentId: id
2201
2489
  });
2202
2490
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -2209,13 +2497,80 @@ const taskController = ({ strapi }) => ({
2209
2497
  for (const key of ["title", "description", "content", "done", "priority", "consequence", "impact", "dueDate"]) {
2210
2498
  if (body[key] !== void 0) data[key] = body[key];
2211
2499
  }
2212
- const task2 = await strapi.documents(CONTENT_TYPE).update({
2500
+ const task2 = await strapi.documents(CONTENT_TYPE$1).update({
2213
2501
  documentId: id,
2214
2502
  data
2215
2503
  });
2216
2504
  ctx.body = { data: task2 };
2217
2505
  },
2218
2506
  async delete(ctx) {
2507
+ const adminUserId = getAdminUserId$1(ctx);
2508
+ if (!adminUserId) {
2509
+ ctx.status = 401;
2510
+ ctx.body = { error: "Unauthorized" };
2511
+ return;
2512
+ }
2513
+ const { id } = ctx.params;
2514
+ const existing = await strapi.documents(CONTENT_TYPE$1).findOne({
2515
+ documentId: id
2516
+ });
2517
+ if (!existing || existing.adminUserId !== adminUserId) {
2518
+ ctx.status = 404;
2519
+ ctx.body = { error: "Task not found" };
2520
+ return;
2521
+ }
2522
+ await strapi.documents(CONTENT_TYPE$1).delete({ documentId: id });
2523
+ ctx.status = 200;
2524
+ ctx.body = { data: { documentId: id } };
2525
+ }
2526
+ });
2527
+ const CONTENT_TYPE = "plugin::ai-sdk.note";
2528
+ function getAdminUserId(ctx) {
2529
+ const id = ctx.state?.user?.id;
2530
+ return typeof id === "number" ? id : null;
2531
+ }
2532
+ const noteController = ({ strapi }) => ({
2533
+ async find(ctx) {
2534
+ const adminUserId = getAdminUserId(ctx);
2535
+ if (!adminUserId) {
2536
+ ctx.status = 401;
2537
+ ctx.body = { error: "Unauthorized" };
2538
+ return;
2539
+ }
2540
+ const notes = await strapi.documents(CONTENT_TYPE).findMany({
2541
+ filters: { adminUserId },
2542
+ fields: ["title", "content", "category", "tags", "source", "createdAt"],
2543
+ sort: { createdAt: "desc" }
2544
+ });
2545
+ ctx.body = { data: notes };
2546
+ },
2547
+ async create(ctx) {
2548
+ const adminUserId = getAdminUserId(ctx);
2549
+ if (!adminUserId) {
2550
+ ctx.status = 401;
2551
+ ctx.body = { error: "Unauthorized" };
2552
+ return;
2553
+ }
2554
+ const { title, content, category, tags, source } = ctx.request.body;
2555
+ if (!content || typeof content !== "string") {
2556
+ ctx.status = 400;
2557
+ ctx.body = { error: "content is required" };
2558
+ return;
2559
+ }
2560
+ const note2 = await strapi.documents(CONTENT_TYPE).create({
2561
+ data: {
2562
+ title: title || "",
2563
+ content,
2564
+ category: category || "research",
2565
+ tags: tags || "",
2566
+ source: source || "",
2567
+ adminUserId
2568
+ }
2569
+ });
2570
+ ctx.status = 201;
2571
+ ctx.body = { data: note2 };
2572
+ },
2573
+ async update(ctx) {
2219
2574
  const adminUserId = getAdminUserId(ctx);
2220
2575
  if (!adminUserId) {
2221
2576
  ctx.status = 401;
@@ -2228,12 +2583,58 @@ const taskController = ({ strapi }) => ({
2228
2583
  });
2229
2584
  if (!existing || existing.adminUserId !== adminUserId) {
2230
2585
  ctx.status = 404;
2231
- ctx.body = { error: "Task not found" };
2586
+ ctx.body = { error: "Note not found" };
2587
+ return;
2588
+ }
2589
+ const { title, content, category, tags, source } = ctx.request.body;
2590
+ const data = {};
2591
+ if (title !== void 0) data.title = title;
2592
+ if (content !== void 0) data.content = content;
2593
+ if (category !== void 0) data.category = category;
2594
+ if (tags !== void 0) data.tags = tags;
2595
+ if (source !== void 0) data.source = source;
2596
+ const note2 = await strapi.documents(CONTENT_TYPE).update({
2597
+ documentId: id,
2598
+ data
2599
+ });
2600
+ ctx.body = { data: note2 };
2601
+ },
2602
+ async delete(ctx) {
2603
+ const adminUserId = getAdminUserId(ctx);
2604
+ if (!adminUserId) {
2605
+ ctx.status = 401;
2606
+ ctx.body = { error: "Unauthorized" };
2607
+ return;
2608
+ }
2609
+ const { id } = ctx.params;
2610
+ const existing = await strapi.documents(CONTENT_TYPE).findOne({
2611
+ documentId: id
2612
+ });
2613
+ if (!existing || existing.adminUserId !== adminUserId) {
2614
+ ctx.status = 404;
2615
+ ctx.body = { error: "Note not found" };
2232
2616
  return;
2233
2617
  }
2234
2618
  await strapi.documents(CONTENT_TYPE).delete({ documentId: id });
2235
2619
  ctx.status = 200;
2236
2620
  ctx.body = { data: { documentId: id } };
2621
+ },
2622
+ async clearAll(ctx) {
2623
+ const adminUserId = getAdminUserId(ctx);
2624
+ if (!adminUserId) {
2625
+ ctx.status = 401;
2626
+ ctx.body = { error: "Unauthorized" };
2627
+ return;
2628
+ }
2629
+ const notes = await strapi.documents(CONTENT_TYPE).findMany({
2630
+ filters: { adminUserId },
2631
+ fields: ["documentId"]
2632
+ });
2633
+ for (const note2 of notes) {
2634
+ await strapi.documents(CONTENT_TYPE).delete({ documentId: note2.documentId });
2635
+ }
2636
+ ctx.status = 200;
2637
+ ctx.body = { data: { deleted: notes.length } };
2237
2638
  }
2238
2639
  });
2239
2640
  const controllers = {
@@ -2242,7 +2643,8 @@ const controllers = {
2242
2643
  conversation: conversationController,
2243
2644
  memory: memoryController,
2244
2645
  "public-memory": publicMemoryController,
2245
- task: taskController
2646
+ task: taskController,
2647
+ note: noteController
2246
2648
  };
2247
2649
  const promptInjection = [
2248
2650
  "ignore (all |any )?(previous|prior|above) (instructions|prompts|rules)",
@@ -2612,6 +3014,36 @@ const adminAPIRoutes = {
2612
3014
  path: "/tasks/:id",
2613
3015
  handler: "task.delete",
2614
3016
  config: { policies: [] }
3017
+ },
3018
+ {
3019
+ method: "GET",
3020
+ path: "/notes",
3021
+ handler: "note.find",
3022
+ config: { policies: [] }
3023
+ },
3024
+ {
3025
+ method: "POST",
3026
+ path: "/notes",
3027
+ handler: "note.create",
3028
+ config: { policies: [] }
3029
+ },
3030
+ {
3031
+ method: "PUT",
3032
+ path: "/notes/:id",
3033
+ handler: "note.update",
3034
+ config: { policies: [] }
3035
+ },
3036
+ {
3037
+ method: "DELETE",
3038
+ path: "/notes/clear",
3039
+ handler: "note.clearAll",
3040
+ config: { policies: [] }
3041
+ },
3042
+ {
3043
+ method: "DELETE",
3044
+ path: "/notes/:id",
3045
+ handler: "note.delete",
3046
+ config: { policies: [] }
2615
3047
  }
2616
3048
  ]
2617
3049
  };