@sanity/context 0.0.4 → 0.0.5
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/studio.d.ts +19 -0
- package/dist/studio.js +290 -51
- package/dist/studio.js.map +1 -1
- package/package.json +18 -4
- package/scripts/install-skill.mjs +121 -0
- package/scripts/uninstall-skill.mjs +111 -0
- package/skills/SKILL.md +73 -0
- package/src/studio/context-plugin/{AgentDocumentInput.tsx → AgentContextDocumentInput.tsx} +1 -1
- package/src/studio/context-plugin/agentContextSchema.ts +15 -2
- package/src/studio/context-plugin/groq-filter-input/GroqFilterInput.tsx +369 -0
- package/src/studio/context-plugin/groq-filter-input/groqUtils.test.ts +69 -0
- package/src/studio/context-plugin/groq-filter-input/groqUtils.ts +48 -0
- package/src/studio/context-plugin/groq-filter-input/useComposedRefs.ts +17 -0
- package/src/studio/context-plugin/plugin.tsx +0 -34
- package/src/studio/index.ts +5 -0
package/dist/studio.d.ts
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
|
+
import {DocumentDefinition} from 'sanity'
|
|
1
2
|
import {Plugin as Plugin_2} from 'sanity'
|
|
3
|
+
import {PreviewConfig} from 'sanity'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The name of the agent context schema type.
|
|
7
|
+
* @beta
|
|
8
|
+
*/
|
|
9
|
+
export declare const AGENT_CONTEXT_SCHEMA_TYPE_NAME = 'sanity.agentContext'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The schema for the agent context document.
|
|
13
|
+
* @beta
|
|
14
|
+
*/
|
|
15
|
+
export declare const agentContextSchema: {
|
|
16
|
+
type: 'document'
|
|
17
|
+
name: 'sanity.agentContext'
|
|
18
|
+
} & Omit<DocumentDefinition, 'preview'> & {
|
|
19
|
+
preview?: PreviewConfig<Record<string, string>, Record<never, any>> | undefined
|
|
20
|
+
}
|
|
2
21
|
|
|
3
22
|
/**
|
|
4
23
|
* The plugin for the agent context.
|
package/dist/studio.js
CHANGED
|
@@ -1,35 +1,10 @@
|
|
|
1
|
+
import { CopyIcon, ListIcon, GroqIcon, ChevronDownIcon, CheckmarkIcon, CloseIcon, ErrorOutlineIcon, DatabaseIcon } from "@sanity/icons";
|
|
2
|
+
import { useDataset, useProjectId, getValueAtPath, useSchema, CommandList, SanityDefaultPreview, set, unset, defineType, defineField, definePlugin } from "sanity";
|
|
1
3
|
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
const AGENT_CONTEXT_SCHEMA_TYPE_NAME = "sanity.agentContext", AGENT_CONTEXT_SCHEMA_TITLE = "Agent Context", agentContextSchema = defineType({
|
|
7
|
-
name: AGENT_CONTEXT_SCHEMA_TYPE_NAME,
|
|
8
|
-
title: AGENT_CONTEXT_SCHEMA_TITLE,
|
|
9
|
-
type: "document",
|
|
10
|
-
icon: DatabaseIcon,
|
|
11
|
-
fields: [
|
|
12
|
-
defineField({
|
|
13
|
-
name: "name",
|
|
14
|
-
title: "Name",
|
|
15
|
-
type: "string"
|
|
16
|
-
}),
|
|
17
|
-
defineField({
|
|
18
|
-
name: "slug",
|
|
19
|
-
title: "Slug",
|
|
20
|
-
type: "slug",
|
|
21
|
-
options: {
|
|
22
|
-
source: "name"
|
|
23
|
-
}
|
|
24
|
-
}),
|
|
25
|
-
defineField({
|
|
26
|
-
name: "groqFilter",
|
|
27
|
-
title: "GROQ filter",
|
|
28
|
-
type: "text"
|
|
29
|
-
})
|
|
30
|
-
]
|
|
31
|
-
});
|
|
32
|
-
function AgentDocumentInput(props) {
|
|
4
|
+
import { useToast, Stack, Card, Text, Flex, Button, Box, useClickOutsideEvent, TabList, Tooltip, Tab, TabPanel, Popover, TextInput, TextArea } from "@sanity/ui";
|
|
5
|
+
import { useCallback, useState, useRef, useMemo } from "react";
|
|
6
|
+
import { parse } from "groq-js";
|
|
7
|
+
function AgentContextDocumentInput(props) {
|
|
33
8
|
const dataset = useDataset(), projectId = useProjectId(), toast = useToast(), slug = getValueAtPath(props.value, ["slug"]), currentSlug = slug && typeof slug == "object" && "current" in slug ? slug.current : "", MCP_URL = `https://context-mcp.sanity.io/mcp/${projectId}/${dataset}/${currentSlug}`;
|
|
34
9
|
return /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
|
|
35
10
|
/* @__PURE__ */ jsx(Card, { shadow: 1, padding: 4, paddingLeft: 4, radius: 2, tone: "primary", children: /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
|
|
@@ -58,7 +33,287 @@ function AgentDocumentInput(props) {
|
|
|
58
33
|
props.renderDefault(props)
|
|
59
34
|
] });
|
|
60
35
|
}
|
|
61
|
-
const
|
|
36
|
+
const listToQuery = (types) => `_type in [${types.map((t) => `"${t}"`).join(", ")}]`, queryToList = (query) => {
|
|
37
|
+
const match = query.match(/_type\s+in\s+\[([^\]]*)\]/);
|
|
38
|
+
return match?.[1] ? match[1].split(",").map((s) => s.trim().replace(/"/g, "")).filter(Boolean) : [];
|
|
39
|
+
}, isSimpleTypeQuery = (query) => query ? /^_type\s+in\s+\[[^\]]*\]$/.test(query.trim()) : !0, validateGroq = (query) => {
|
|
40
|
+
if (!query) return { valid: !0 };
|
|
41
|
+
try {
|
|
42
|
+
return parse(query), { valid: !0 };
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return {
|
|
45
|
+
valid: !1,
|
|
46
|
+
error: e instanceof Error ? e.message : "Invalid GROQ syntax"
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
function useComposedRefs(...refs) {
|
|
51
|
+
return useCallback(
|
|
52
|
+
(node) => {
|
|
53
|
+
for (const ref of refs)
|
|
54
|
+
typeof ref == "function" ? ref(node) : ref && (ref.current = node);
|
|
55
|
+
},
|
|
56
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
57
|
+
refs
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const TAB_IDS = {
|
|
61
|
+
TYPES_TAB: "types-tab",
|
|
62
|
+
TYPES_PANEL: "types-panel",
|
|
63
|
+
GROQ_TAB: "groq-tab",
|
|
64
|
+
GROQ_PANEL: "groq-panel"
|
|
65
|
+
}, ITEM_HEIGHT = 43;
|
|
66
|
+
function GroqFilterInput(props) {
|
|
67
|
+
const { value, onChange, elementProps } = props, { ref: refProp, ...restElementProps } = elementProps || {}, [open, setOpen] = useState(!1), [inputElement, setInputElement] = useState(null), inputRef = useRef(null), popoverRef = useRef(null), openListButtonRef = useRef(null), [searchQuery, setSearchQuery] = useState(null), schema = useSchema(), setInputRef = useCallback(
|
|
68
|
+
(node) => {
|
|
69
|
+
inputRef.current = node, setInputElement(node);
|
|
70
|
+
},
|
|
71
|
+
[inputRef]
|
|
72
|
+
), composedRef = useComposedRefs(setInputRef, refProp), isSimple = useMemo(() => isSimpleTypeQuery(value), [value]), validation = useMemo(() => validateGroq(value), [value]), [panel, setPanel] = useState(
|
|
73
|
+
() => isSimpleTypeQuery(value) ? "types" : "groq"
|
|
74
|
+
), selectedTypes = useMemo(() => value ? queryToList(value) : [], [value]), filteredTypeNames = useMemo(() => {
|
|
75
|
+
const typeNames = (schema._original?.types || []).map((type) => type.name).filter((name) => !name.startsWith("sanity."));
|
|
76
|
+
return searchQuery ? typeNames.filter((name) => name.toLowerCase().includes(searchQuery.toLowerCase())) : typeNames;
|
|
77
|
+
}, [searchQuery, schema._original?.types]), handleDocumentTypeItemClick = (item) => {
|
|
78
|
+
const nextValue = selectedTypes.includes(item) ? selectedTypes.filter((t) => t !== item) : [...selectedTypes, item];
|
|
79
|
+
onChange(nextValue.length > 0 ? set(listToQuery(nextValue)) : unset());
|
|
80
|
+
}, closeList = useCallback(() => {
|
|
81
|
+
setOpen(!1), setSearchQuery(null);
|
|
82
|
+
}, []), handleToggleList = useCallback(() => {
|
|
83
|
+
open ? closeList() : (setOpen(!0), inputRef.current?.focus());
|
|
84
|
+
}, [open, closeList]), handleTextInputKeyDown = useCallback(
|
|
85
|
+
(event) => {
|
|
86
|
+
event.key === "Escape" && closeList();
|
|
87
|
+
},
|
|
88
|
+
[closeList]
|
|
89
|
+
), getItemSelected = useCallback(
|
|
90
|
+
(index) => {
|
|
91
|
+
const item = filteredTypeNames[index];
|
|
92
|
+
return item ? selectedTypes.includes(item) : !1;
|
|
93
|
+
},
|
|
94
|
+
[filteredTypeNames, selectedTypes]
|
|
95
|
+
);
|
|
96
|
+
useClickOutsideEvent(
|
|
97
|
+
() => {
|
|
98
|
+
closeList();
|
|
99
|
+
},
|
|
100
|
+
() => [popoverRef.current, inputRef.current, openListButtonRef.current]
|
|
101
|
+
);
|
|
102
|
+
const isListOpen = open || searchQuery !== null, effectivePanel = !isSimple || !validation.valid ? "groq" : panel;
|
|
103
|
+
return /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
104
|
+
/* @__PURE__ */ jsxs(TabList, { space: 1, children: [
|
|
105
|
+
/* @__PURE__ */ jsx(
|
|
106
|
+
Tooltip,
|
|
107
|
+
{
|
|
108
|
+
animate: !0,
|
|
109
|
+
disabled: isSimple && validation.valid,
|
|
110
|
+
content: /* @__PURE__ */ jsx(Box, { padding: 1, style: { maxWidth: "200px" }, children: /* @__PURE__ */ jsx(Text, { size: 1, children: validation.valid ? "The current filter is too complex to edit here. Use the GROQ tab to edit it." : "The current filter has a syntax error that needs to be fixed in the GROQ tab." }) }),
|
|
111
|
+
delay: { open: 200, close: 0 },
|
|
112
|
+
placement: "bottom",
|
|
113
|
+
portal: !0,
|
|
114
|
+
children: /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
115
|
+
Tab,
|
|
116
|
+
{
|
|
117
|
+
"aria-controls": TAB_IDS.TYPES_PANEL,
|
|
118
|
+
disabled: !isSimple || !validation.valid,
|
|
119
|
+
icon: ListIcon,
|
|
120
|
+
id: TAB_IDS.TYPES_TAB,
|
|
121
|
+
label: "Types",
|
|
122
|
+
onClick: () => isSimple && validation.valid && setPanel("types"),
|
|
123
|
+
padding: 3,
|
|
124
|
+
selected: effectivePanel === "types"
|
|
125
|
+
}
|
|
126
|
+
) })
|
|
127
|
+
}
|
|
128
|
+
),
|
|
129
|
+
/* @__PURE__ */ jsx(
|
|
130
|
+
Tab,
|
|
131
|
+
{
|
|
132
|
+
"aria-controls": TAB_IDS.GROQ_PANEL,
|
|
133
|
+
icon: GroqIcon,
|
|
134
|
+
id: TAB_IDS.GROQ_TAB,
|
|
135
|
+
label: "GROQ",
|
|
136
|
+
onClick: () => setPanel("groq"),
|
|
137
|
+
padding: 3,
|
|
138
|
+
selected: effectivePanel === "groq"
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
] }),
|
|
142
|
+
/* @__PURE__ */ jsx(
|
|
143
|
+
TabPanel,
|
|
144
|
+
{
|
|
145
|
+
"aria-labelledby": TAB_IDS.TYPES_TAB,
|
|
146
|
+
hidden: effectivePanel !== "types",
|
|
147
|
+
id: TAB_IDS.TYPES_PANEL,
|
|
148
|
+
tabIndex: -1,
|
|
149
|
+
children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
150
|
+
/* @__PURE__ */ jsx(
|
|
151
|
+
Popover,
|
|
152
|
+
{
|
|
153
|
+
animate: !0,
|
|
154
|
+
constrainSize: !0,
|
|
155
|
+
fallbackPlacements: ["bottom", "top"],
|
|
156
|
+
matchReferenceWidth: !0,
|
|
157
|
+
open: isListOpen,
|
|
158
|
+
placement: "bottom",
|
|
159
|
+
portal: !0,
|
|
160
|
+
ref: popoverRef,
|
|
161
|
+
content: /* @__PURE__ */ jsxs(Flex, { direction: "column", height: "fill", children: [
|
|
162
|
+
filteredTypeNames.length === 0 && /* @__PURE__ */ jsx(Flex, { direction: "column", overflow: "hidden", flex: 1, padding: 5, children: /* @__PURE__ */ jsxs(Text, { align: "center", size: 1, children: [
|
|
163
|
+
"No document types found matching ",
|
|
164
|
+
/* @__PURE__ */ jsx("b", { children: `"${searchQuery}"` })
|
|
165
|
+
] }) }),
|
|
166
|
+
filteredTypeNames.length > 0 && /* @__PURE__ */ jsx(Flex, { direction: "column", overflow: "hidden", flex: 1, children: /* @__PURE__ */ jsx(
|
|
167
|
+
CommandList,
|
|
168
|
+
{
|
|
169
|
+
activeItemDataAttr: "data-hovered",
|
|
170
|
+
ariaLabel: "Document Types",
|
|
171
|
+
ariaMultiselectable: !0,
|
|
172
|
+
fixedHeight: !0,
|
|
173
|
+
getItemKey: (item) => item,
|
|
174
|
+
getItemSelected,
|
|
175
|
+
inputElement,
|
|
176
|
+
itemHeight: ITEM_HEIGHT,
|
|
177
|
+
padding: 1,
|
|
178
|
+
items: filteredTypeNames,
|
|
179
|
+
renderItem: (documentTypeName) => {
|
|
180
|
+
const isSelected = selectedTypes.includes(documentTypeName), schemaType = schema.get(documentTypeName);
|
|
181
|
+
return /* @__PURE__ */ jsx(Stack, { padding: 1, children: /* @__PURE__ */ jsx(
|
|
182
|
+
Button,
|
|
183
|
+
{
|
|
184
|
+
"aria-label": `${isSelected ? "Remove" : "Add"} ${documentTypeName} to filter`,
|
|
185
|
+
"aria-selected": isSelected,
|
|
186
|
+
mode: "bleed",
|
|
187
|
+
onClick: () => handleDocumentTypeItemClick(documentTypeName),
|
|
188
|
+
padding: 0,
|
|
189
|
+
children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
190
|
+
/* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsx(
|
|
191
|
+
SanityDefaultPreview,
|
|
192
|
+
{
|
|
193
|
+
layout: "compact",
|
|
194
|
+
title: schemaType?.title || documentTypeName,
|
|
195
|
+
schemaType,
|
|
196
|
+
icon: schemaType?.icon
|
|
197
|
+
}
|
|
198
|
+
) }),
|
|
199
|
+
isSelected && /* @__PURE__ */ jsx(Box, { paddingX: 3, children: /* @__PURE__ */ jsx(Text, { size: 1, children: /* @__PURE__ */ jsx(CheckmarkIcon, {}) }) })
|
|
200
|
+
] })
|
|
201
|
+
}
|
|
202
|
+
) }, documentTypeName);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
) })
|
|
206
|
+
] }),
|
|
207
|
+
children: /* @__PURE__ */ jsxs(Card, { display: "flex", border: !0, radius: 2, overflow: "hidden", children: [
|
|
208
|
+
/* @__PURE__ */ jsx(Card, { flex: 1, borderRight: !0, children: /* @__PURE__ */ jsx(
|
|
209
|
+
TextInput,
|
|
210
|
+
{
|
|
211
|
+
...restElementProps,
|
|
212
|
+
autoComplete: "off",
|
|
213
|
+
border: !1,
|
|
214
|
+
onChange: (event) => setSearchQuery(event.currentTarget.value),
|
|
215
|
+
onKeyDown: handleTextInputKeyDown,
|
|
216
|
+
placeholder: "Search for document types",
|
|
217
|
+
radius: 0,
|
|
218
|
+
ref: composedRef,
|
|
219
|
+
value: searchQuery || ""
|
|
220
|
+
}
|
|
221
|
+
) }),
|
|
222
|
+
/* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", sizing: "border", padding: 1, height: "fill", children: /* @__PURE__ */ jsx(
|
|
223
|
+
Button,
|
|
224
|
+
{
|
|
225
|
+
"aria-label": "Open document types list",
|
|
226
|
+
disabled: isListOpen,
|
|
227
|
+
icon: ChevronDownIcon,
|
|
228
|
+
mode: "bleed",
|
|
229
|
+
onClick: handleToggleList,
|
|
230
|
+
padding: 2,
|
|
231
|
+
ref: openListButtonRef
|
|
232
|
+
}
|
|
233
|
+
) })
|
|
234
|
+
] })
|
|
235
|
+
}
|
|
236
|
+
),
|
|
237
|
+
/* @__PURE__ */ jsx(Flex, { wrap: "wrap", gap: 2, children: selectedTypes.map((type) => {
|
|
238
|
+
const title = schema.get(type)?.title || type;
|
|
239
|
+
return /* @__PURE__ */ jsx(Card, { padding: 1, radius: 3, border: !0, tone: "transparent", paddingLeft: 2, children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 1, overflow: "hidden", children: [
|
|
240
|
+
/* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsx(Text, { size: 1, weight: "medium", textOverflow: "ellipsis", children: title }) }),
|
|
241
|
+
/* @__PURE__ */ jsx(
|
|
242
|
+
Button,
|
|
243
|
+
{
|
|
244
|
+
"aria-label": "Remove {type} from filter",
|
|
245
|
+
fontSize: 0,
|
|
246
|
+
icon: CloseIcon,
|
|
247
|
+
mode: "bleed",
|
|
248
|
+
onClick: () => handleDocumentTypeItemClick(type),
|
|
249
|
+
padding: 2
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
] }) }, type);
|
|
253
|
+
}) })
|
|
254
|
+
] })
|
|
255
|
+
}
|
|
256
|
+
),
|
|
257
|
+
/* @__PURE__ */ jsx(
|
|
258
|
+
TabPanel,
|
|
259
|
+
{
|
|
260
|
+
"aria-labelledby": TAB_IDS.GROQ_TAB,
|
|
261
|
+
hidden: effectivePanel !== "groq",
|
|
262
|
+
id: TAB_IDS.GROQ_PANEL,
|
|
263
|
+
tabIndex: -1,
|
|
264
|
+
children: /* @__PURE__ */ jsx(Stack, { space: 3, children: /* @__PURE__ */ jsx(
|
|
265
|
+
TextArea,
|
|
266
|
+
{
|
|
267
|
+
...restElementProps,
|
|
268
|
+
onChange: (event) => onChange(event.currentTarget.value ? set(event.currentTarget.value) : unset()),
|
|
269
|
+
placeholder: '_type in ["author", "post"]',
|
|
270
|
+
value: value || "",
|
|
271
|
+
style: { fontFamily: "monospace" },
|
|
272
|
+
padding: 4
|
|
273
|
+
}
|
|
274
|
+
) })
|
|
275
|
+
}
|
|
276
|
+
),
|
|
277
|
+
!validation.valid && /* @__PURE__ */ jsx(Card, { padding: 3, radius: 2, tone: "critical", border: !0, children: /* @__PURE__ */ jsxs(Flex, { align: "flex-start", gap: 2, children: [
|
|
278
|
+
/* @__PURE__ */ jsx(Text, { size: 1, children: /* @__PURE__ */ jsx(ErrorOutlineIcon, {}) }),
|
|
279
|
+
/* @__PURE__ */ jsx(Text, { size: 1, children: validation.error })
|
|
280
|
+
] }) })
|
|
281
|
+
] });
|
|
282
|
+
}
|
|
283
|
+
const AGENT_CONTEXT_SCHEMA_TYPE_NAME = "sanity.agentContext", AGENT_CONTEXT_SCHEMA_TITLE = "Agent Context", agentContextSchema = defineType({
|
|
284
|
+
name: AGENT_CONTEXT_SCHEMA_TYPE_NAME,
|
|
285
|
+
title: AGENT_CONTEXT_SCHEMA_TITLE,
|
|
286
|
+
type: "document",
|
|
287
|
+
icon: DatabaseIcon,
|
|
288
|
+
components: {
|
|
289
|
+
input: AgentContextDocumentInput
|
|
290
|
+
},
|
|
291
|
+
fields: [
|
|
292
|
+
defineField({
|
|
293
|
+
name: "name",
|
|
294
|
+
title: "Name",
|
|
295
|
+
type: "string",
|
|
296
|
+
placeholder: "My Agent Context"
|
|
297
|
+
}),
|
|
298
|
+
defineField({
|
|
299
|
+
name: "slug",
|
|
300
|
+
title: "Slug",
|
|
301
|
+
type: "slug",
|
|
302
|
+
options: {
|
|
303
|
+
source: "name"
|
|
304
|
+
}
|
|
305
|
+
}),
|
|
306
|
+
defineField({
|
|
307
|
+
name: "groqFilter",
|
|
308
|
+
title: "Content filter",
|
|
309
|
+
description: "Define which content AI agents can access. Pick types which will generate the filter, or manually enter the filter in the GROQ tab.",
|
|
310
|
+
type: "string",
|
|
311
|
+
components: {
|
|
312
|
+
input: GroqFilterInput
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
]
|
|
316
|
+
}), contextPlugin = definePlugin({
|
|
62
317
|
name: "sanity/context/plugin",
|
|
63
318
|
schema: {
|
|
64
319
|
types: [agentContextSchema],
|
|
@@ -73,27 +328,11 @@ const contextPlugin = definePlugin({
|
|
|
73
328
|
value: {}
|
|
74
329
|
}
|
|
75
330
|
]
|
|
76
|
-
}
|
|
77
|
-
form: {
|
|
78
|
-
components: {
|
|
79
|
-
input: (props) => props.schemaType.name === AGENT_CONTEXT_SCHEMA_TYPE_NAME ? /* @__PURE__ */ jsx(AgentDocumentInput, { ...props }) : props.renderDefault(props)
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
plugins: [
|
|
83
|
-
structureTool({
|
|
84
|
-
title: AGENT_CONTEXT_SCHEMA_TITLE,
|
|
85
|
-
name: "agent-context-structure",
|
|
86
|
-
structure: (S) => S.list().title("Content").items([
|
|
87
|
-
S.listItem().title(AGENT_CONTEXT_SCHEMA_TITLE).child(
|
|
88
|
-
S.documentTypeList(AGENT_CONTEXT_SCHEMA_TYPE_NAME).title(
|
|
89
|
-
AGENT_CONTEXT_SCHEMA_TITLE
|
|
90
|
-
)
|
|
91
|
-
)
|
|
92
|
-
])
|
|
93
|
-
})
|
|
94
|
-
]
|
|
331
|
+
}
|
|
95
332
|
});
|
|
96
333
|
export {
|
|
334
|
+
AGENT_CONTEXT_SCHEMA_TYPE_NAME,
|
|
335
|
+
agentContextSchema,
|
|
97
336
|
contextPlugin
|
|
98
337
|
};
|
|
99
338
|
//# sourceMappingURL=studio.js.map
|
package/dist/studio.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"studio.js","sources":["../src/studio/context-plugin/agentContextSchema.ts","../src/studio/context-plugin/AgentDocumentInput.tsx","../src/studio/context-plugin/plugin.tsx"],"sourcesContent":["import {DatabaseIcon} from '@sanity/icons'\nimport {defineField, defineType} from 'sanity'\n\n/**\n * The name of the agent context schema type.\n */\nexport const AGENT_CONTEXT_SCHEMA_TYPE_NAME = 'sanity.agentContext'\n\n/**\n * The title of the agent context schema type.\n */\nexport const AGENT_CONTEXT_SCHEMA_TITLE = 'Agent Context'\n\n/**\n * The schema for the agent context document.\n * @beta\n */\nexport const agentContextSchema = defineType({\n name: AGENT_CONTEXT_SCHEMA_TYPE_NAME,\n title: AGENT_CONTEXT_SCHEMA_TITLE,\n type: 'document',\n icon: DatabaseIcon,\n fields: [\n defineField({\n name: 'name',\n title: 'Name',\n type: 'string',\n }),\n defineField({\n name: 'slug',\n title: 'Slug',\n type: 'slug',\n options: {\n source: 'name',\n },\n }),\n defineField({\n name: 'groqFilter',\n title: 'GROQ filter',\n type: 'text',\n }),\n ],\n})\n","import {CopyIcon} from '@sanity/icons'\nimport {Box, Button, Card, Flex, Stack, Text, useToast} from '@sanity/ui'\nimport {getValueAtPath, type InputProps, useDataset, useProjectId} from 'sanity'\n\nexport function AgentDocumentInput(props: InputProps) {\n const dataset = useDataset()\n const projectId = useProjectId()\n const toast = useToast()\n\n const slug = getValueAtPath(props.value, ['slug'])\n const currentSlug = slug && typeof slug === 'object' && 'current' in slug ? slug.current : ''\n const MCP_URL = `https://context-mcp.sanity.io/mcp/${projectId}/${dataset}/${currentSlug}`\n\n const handleCopy = () => {\n try {\n navigator.clipboard.writeText(MCP_URL)\n toast.push({\n title: 'Copied to clipboard',\n description: 'The MCP URL has been copied to your clipboard',\n status: 'success',\n closable: true,\n })\n } catch {\n toast.push({\n title: 'Error copying to clipboard',\n description: 'Please copy the MCP URL manually',\n status: 'error',\n closable: true,\n })\n }\n }\n\n return (\n <Stack space={4}>\n <Card shadow={1} padding={4} paddingLeft={4} radius={2} tone=\"primary\">\n <Stack space={4}>\n <Text size={1} muted weight=\"medium\">\n Context MCP URL\n </Text>\n\n {slug ? (\n <Flex align=\"center\" gap={2}>\n <Button icon={CopyIcon} mode=\"bleed\" fontSize={1} padding={2} onClick={handleCopy} />\n\n <Box flex={1}>\n <Text size={1} muted>\n {MCP_URL}\n </Text>\n </Box>\n </Flex>\n ) : (\n <Box paddingY={2}>\n <Text size={1} muted>\n No slug found. Please generate a slug to see the Context MCP URL.\n </Text>\n </Box>\n )}\n </Stack>\n </Card>\n\n {props.renderDefault(props)}\n </Stack>\n )\n}\n","import {definePlugin} from 'sanity'\nimport {type StructureBuilder, structureTool} from 'sanity/structure'\n\nimport {\n AGENT_CONTEXT_SCHEMA_TITLE,\n AGENT_CONTEXT_SCHEMA_TYPE_NAME,\n agentContextSchema,\n} from './agentContextSchema'\nimport {AgentDocumentInput} from './AgentDocumentInput'\n\n/**\n * The plugin for the agent context.\n * @beta\n */\nexport const contextPlugin = definePlugin({\n name: 'sanity/context/plugin',\n\n schema: {\n types: [agentContextSchema],\n\n // Add template configuration for the `sanity.agentContext` type\n // as the sanity.* namespace is filtered by default.\n templates: (prev) => [\n ...prev,\n {\n id: AGENT_CONTEXT_SCHEMA_TYPE_NAME,\n title: AGENT_CONTEXT_SCHEMA_TITLE,\n schemaType: AGENT_CONTEXT_SCHEMA_TYPE_NAME,\n value: {},\n },\n ],\n },\n\n form: {\n components: {\n input: (props) => {\n if (props.schemaType.name === AGENT_CONTEXT_SCHEMA_TYPE_NAME) {\n return <AgentDocumentInput {...props} />\n }\n\n return props.renderDefault(props)\n },\n },\n },\n\n plugins: [\n structureTool({\n title: AGENT_CONTEXT_SCHEMA_TITLE,\n name: 'agent-context-structure',\n structure: (S: StructureBuilder) => {\n return S.list()\n .title('Content')\n .items([\n S.listItem()\n .title(AGENT_CONTEXT_SCHEMA_TITLE)\n .child(\n S.documentTypeList(AGENT_CONTEXT_SCHEMA_TYPE_NAME).title(\n AGENT_CONTEXT_SCHEMA_TITLE,\n ),\n ),\n ])\n },\n }),\n ],\n})\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"studio.js","sources":["../src/studio/context-plugin/AgentContextDocumentInput.tsx","../src/studio/context-plugin/groq-filter-input/groqUtils.ts","../src/studio/context-plugin/groq-filter-input/useComposedRefs.ts","../src/studio/context-plugin/groq-filter-input/GroqFilterInput.tsx","../src/studio/context-plugin/agentContextSchema.ts","../src/studio/context-plugin/plugin.tsx"],"sourcesContent":["import {CopyIcon} from '@sanity/icons'\nimport {Box, Button, Card, Flex, Stack, Text, useToast} from '@sanity/ui'\nimport {getValueAtPath, type InputProps, useDataset, useProjectId} from 'sanity'\n\nexport function AgentContextDocumentInput(props: InputProps) {\n const dataset = useDataset()\n const projectId = useProjectId()\n const toast = useToast()\n\n const slug = getValueAtPath(props.value, ['slug'])\n const currentSlug = slug && typeof slug === 'object' && 'current' in slug ? slug.current : ''\n const MCP_URL = `https://context-mcp.sanity.io/mcp/${projectId}/${dataset}/${currentSlug}`\n\n const handleCopy = () => {\n try {\n navigator.clipboard.writeText(MCP_URL)\n toast.push({\n title: 'Copied to clipboard',\n description: 'The MCP URL has been copied to your clipboard',\n status: 'success',\n closable: true,\n })\n } catch {\n toast.push({\n title: 'Error copying to clipboard',\n description: 'Please copy the MCP URL manually',\n status: 'error',\n closable: true,\n })\n }\n }\n\n return (\n <Stack space={4}>\n <Card shadow={1} padding={4} paddingLeft={4} radius={2} tone=\"primary\">\n <Stack space={4}>\n <Text size={1} muted weight=\"medium\">\n Context MCP URL\n </Text>\n\n {slug ? (\n <Flex align=\"center\" gap={2}>\n <Button icon={CopyIcon} mode=\"bleed\" fontSize={1} padding={2} onClick={handleCopy} />\n\n <Box flex={1}>\n <Text size={1} muted>\n {MCP_URL}\n </Text>\n </Box>\n </Flex>\n ) : (\n <Box paddingY={2}>\n <Text size={1} muted>\n No slug found. Please generate a slug to see the Context MCP URL.\n </Text>\n </Box>\n )}\n </Stack>\n </Card>\n\n {props.renderDefault(props)}\n </Stack>\n )\n}\n","import {parse} from 'groq-js'\n\n/**\n * Convert a list of type names to a GROQ filter query\n */\nexport const listToQuery = (types: string[]): string => {\n const quoted = types.map((t) => `\"${t}\"`).join(', ')\n return `_type in [${quoted}]`\n}\n\n/**\n * Parse type names from a GROQ `_type in [...]` query\n */\nexport const queryToList = (query: string): string[] => {\n const match = query.match(/_type\\s+in\\s+\\[([^\\]]*)\\]/)\n if (!match?.[1]) return []\n\n return match[1]\n .split(',')\n .map((s) => s.trim().replace(/\"/g, ''))\n .filter(Boolean)\n}\n\n/**\n * Check if query is a simple `_type in [...]` filter that can be edited via the Types UI.\n * Returns false for complex queries like `_type in [\"a\"] && published == true`\n */\nexport const isSimpleTypeQuery = (query: string | undefined): boolean => {\n if (!query) return true // Empty is simple (can start fresh)\n return /^_type\\s+in\\s+\\[[^\\]]*\\]$/.test(query.trim())\n}\n\n/**\n * Validate a GROQ query string using groq-js parser\n */\nexport const validateGroq = (query: string | undefined): {valid: boolean; error?: string} => {\n if (!query) return {valid: true}\n\n try {\n parse(query)\n return {valid: true}\n } catch (e) {\n return {\n valid: false,\n error: e instanceof Error ? e.message : 'Invalid GROQ syntax',\n }\n }\n}\n","import {type Ref, useCallback} from 'react'\n\nexport function useComposedRefs<T>(...refs: (Ref<T> | undefined)[]): (node: T | null) => void {\n return useCallback(\n (node: T | null) => {\n for (const ref of refs) {\n if (typeof ref === 'function') {\n ref(node)\n } else if (ref) {\n ;(ref as {current: T | null}).current = node\n }\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n refs,\n )\n}\n","import {\n CheckmarkIcon,\n ChevronDownIcon,\n CloseIcon,\n ErrorOutlineIcon,\n GroqIcon,\n ListIcon,\n} from '@sanity/icons'\nimport {\n Box,\n Button,\n Card,\n Flex,\n Popover,\n Stack,\n Tab,\n TabList,\n TabPanel,\n Text,\n TextArea,\n TextInput,\n Tooltip,\n useClickOutsideEvent,\n} from '@sanity/ui'\nimport {useCallback, useMemo, useRef, useState} from 'react'\nimport {\n CommandList,\n SanityDefaultPreview,\n set,\n type StringInputProps,\n unset,\n useSchema,\n} from 'sanity'\n\nimport {isSimpleTypeQuery, listToQuery, queryToList, validateGroq} from './groqUtils'\nimport {useComposedRefs} from './useComposedRefs'\n\nconst TAB_IDS = {\n TYPES_TAB: 'types-tab',\n TYPES_PANEL: 'types-panel',\n GROQ_TAB: 'groq-tab',\n GROQ_PANEL: 'groq-panel',\n} as const\n\nconst ITEM_HEIGHT = 43\n\nexport function GroqFilterInput(props: StringInputProps) {\n const {value, onChange, elementProps} = props\n const {ref: refProp, ...restElementProps} = elementProps || {}\n\n const [open, setOpen] = useState<boolean>(false)\n const [inputElement, setInputElement] = useState<HTMLInputElement | null>(null)\n const inputRef = useRef<HTMLInputElement>(null)\n const popoverRef = useRef<HTMLDivElement>(null)\n const openListButtonRef = useRef<HTMLButtonElement>(null)\n const [searchQuery, setSearchQuery] = useState<string | null>(null)\n\n const schema = useSchema()\n\n // Compose the input ref with the ref prop\n const setInputRef = useCallback(\n (node: HTMLInputElement | null) => {\n inputRef.current = node\n setInputElement(node)\n },\n [inputRef],\n )\n const composedRef = useComposedRefs(setInputRef, refProp)\n\n // Check if the current query is simple enough to edit via Types UI\n const isSimple = useMemo(() => isSimpleTypeQuery(value), [value])\n\n // Validate GROQ syntax\n const validation = useMemo(() => validateGroq(value), [value])\n\n // Initialize view based on whether the current query is simple or complex\n const [panel, setPanel] = useState<'types' | 'groq'>(() =>\n isSimpleTypeQuery(value) ? 'types' : 'groq',\n )\n\n const selectedTypes = useMemo(() => {\n if (!value) return []\n return queryToList(value)\n }, [value])\n\n // Filter the type names based on the search query\n const filteredTypeNames = useMemo(() => {\n const types = schema._original?.types || []\n const typeNames = types.map((type) => type.name).filter((name) => !name.startsWith('sanity.'))\n if (!searchQuery) return typeNames\n\n return typeNames.filter((name) => name.toLowerCase().includes(searchQuery.toLowerCase()))\n }, [searchQuery, schema._original?.types])\n\n // Handle document type item click.\n // 1. If the item is already selected, remove it from the selected types.\n // 2. If the item is not selected, add it to the selected types.\n // 3. Transform the updated selected types into a GROQ query and set it as the new value.\n const handleDocumentTypeItemClick = (item: string) => {\n const nextValue = selectedTypes.includes(item)\n ? selectedTypes.filter((t) => t !== item)\n : [...selectedTypes, item]\n\n onChange(nextValue.length > 0 ? set(listToQuery(nextValue)) : unset())\n }\n\n const closeList = useCallback(() => {\n setOpen(false)\n setSearchQuery(null)\n }, [])\n\n const handleToggleList = useCallback(() => {\n if (open) {\n closeList()\n } else {\n setOpen(true)\n inputRef.current?.focus()\n }\n }, [open, closeList])\n\n const handleTextInputKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Escape') {\n closeList()\n }\n },\n [closeList],\n )\n\n const getItemSelected = useCallback(\n (index: number) => {\n const item = filteredTypeNames[index]\n return item ? selectedTypes.includes(item) : false\n },\n [filteredTypeNames, selectedTypes],\n )\n\n useClickOutsideEvent(\n () => {\n closeList()\n },\n () => [popoverRef.current, inputRef.current, openListButtonRef.current],\n )\n\n const isListOpen = open || searchQuery !== null\n\n // If query is complex or invalid, force GROQ panel regardless of selection\n const effectivePanel = !isSimple || !validation.valid ? 'groq' : panel\n\n return (\n <Stack space={2}>\n <TabList space={1}>\n <Tooltip\n animate\n disabled={isSimple && validation.valid}\n content={\n <Box padding={1} style={{maxWidth: '200px'}}>\n <Text size={1}>\n {!validation.valid\n ? 'The current filter has a syntax error that needs to be fixed in the GROQ tab.'\n : 'The current filter is too complex to edit here. Use the GROQ tab to edit it.'}\n </Text>\n </Box>\n }\n delay={{open: 200, close: 0}}\n placement=\"bottom\"\n portal\n >\n <div>\n <Tab\n aria-controls={TAB_IDS.TYPES_PANEL}\n disabled={!isSimple || !validation.valid}\n icon={ListIcon}\n id={TAB_IDS.TYPES_TAB}\n label=\"Types\"\n onClick={() => isSimple && validation.valid && setPanel('types')}\n padding={3}\n selected={effectivePanel === 'types'}\n />\n </div>\n </Tooltip>\n\n <Tab\n aria-controls={TAB_IDS.GROQ_PANEL}\n icon={GroqIcon}\n id={TAB_IDS.GROQ_TAB}\n label=\"GROQ\"\n onClick={() => setPanel('groq')}\n padding={3}\n selected={effectivePanel === 'groq'}\n />\n </TabList>\n\n {/* ----- Types panel ----- */}\n <TabPanel\n aria-labelledby={TAB_IDS.TYPES_TAB}\n hidden={effectivePanel !== 'types'}\n id={TAB_IDS.TYPES_PANEL}\n tabIndex={-1}\n >\n <Stack space={3}>\n <Popover\n animate\n constrainSize\n fallbackPlacements={['bottom', 'top']}\n matchReferenceWidth\n open={isListOpen}\n placement=\"bottom\"\n portal\n ref={popoverRef}\n content={\n <Flex direction=\"column\" height=\"fill\">\n {filteredTypeNames.length === 0 && (\n <Flex direction=\"column\" overflow=\"hidden\" flex={1} padding={5}>\n <Text align=\"center\" size={1}>\n No document types found matching <b>{`\"${searchQuery}\"`}</b>\n </Text>\n </Flex>\n )}\n\n {filteredTypeNames.length > 0 && (\n <Flex direction=\"column\" overflow=\"hidden\" flex={1}>\n <CommandList\n activeItemDataAttr=\"data-hovered\"\n ariaLabel=\"Document Types\"\n ariaMultiselectable\n fixedHeight\n getItemKey={(item) => item}\n getItemSelected={getItemSelected}\n inputElement={inputElement}\n itemHeight={ITEM_HEIGHT}\n padding={1}\n items={filteredTypeNames}\n renderItem={(documentTypeName) => {\n const isSelected = selectedTypes.includes(documentTypeName)\n const schemaType = schema.get(documentTypeName)\n\n return (\n <Stack key={documentTypeName} padding={1}>\n <Button\n aria-label={`${isSelected ? 'Remove' : 'Add'} ${documentTypeName} to filter`}\n aria-selected={isSelected}\n mode=\"bleed\"\n onClick={() => handleDocumentTypeItemClick(documentTypeName)}\n padding={0}\n >\n <Flex align=\"center\" gap={2}>\n <Box flex={1}>\n <SanityDefaultPreview\n layout=\"compact\"\n title={schemaType?.title || documentTypeName}\n schemaType={schemaType}\n icon={schemaType?.icon}\n />\n </Box>\n\n {isSelected && (\n <Box paddingX={3}>\n <Text size={1}>\n <CheckmarkIcon />\n </Text>\n </Box>\n )}\n </Flex>\n </Button>\n </Stack>\n )\n }}\n />\n </Flex>\n )}\n </Flex>\n }\n >\n <Card display=\"flex\" border radius={2} overflow=\"hidden\">\n <Card flex={1} borderRight>\n <TextInput\n {...restElementProps}\n autoComplete=\"off\"\n border={false}\n onChange={(event) => setSearchQuery(event.currentTarget.value)}\n onKeyDown={handleTextInputKeyDown}\n placeholder=\"Search for document types\"\n radius={0}\n ref={composedRef}\n value={searchQuery || ''}\n />\n </Card>\n\n <Flex align=\"center\" justify=\"center\" sizing=\"border\" padding={1} height=\"fill\">\n <Button\n aria-label=\"Open document types list\"\n disabled={isListOpen}\n icon={ChevronDownIcon}\n mode=\"bleed\"\n onClick={handleToggleList}\n padding={2}\n ref={openListButtonRef}\n />\n </Flex>\n </Card>\n </Popover>\n\n <Flex wrap=\"wrap\" gap={2}>\n {selectedTypes.map((type) => {\n const title = schema.get(type)?.title || type\n\n return (\n <Card key={type} padding={1} radius={3} border tone=\"transparent\" paddingLeft={2}>\n <Flex align=\"center\" gap={1} overflow=\"hidden\">\n <Box flex={1}>\n <Text size={1} weight=\"medium\" textOverflow=\"ellipsis\">\n {title}\n </Text>\n </Box>\n\n <Button\n aria-label=\"Remove {type} from filter\"\n fontSize={0}\n icon={CloseIcon}\n mode=\"bleed\"\n onClick={() => handleDocumentTypeItemClick(type)}\n padding={2}\n />\n </Flex>\n </Card>\n )\n })}\n </Flex>\n </Stack>\n </TabPanel>\n\n {/* ----- GROQ panel ----- */}\n <TabPanel\n aria-labelledby={TAB_IDS.GROQ_TAB}\n hidden={effectivePanel !== 'groq'}\n id={TAB_IDS.GROQ_PANEL}\n tabIndex={-1}\n >\n <Stack space={3}>\n <TextArea\n {...restElementProps}\n onChange={(event) =>\n onChange(event.currentTarget.value ? set(event.currentTarget.value) : unset())\n }\n placeholder='_type in [\"author\", \"post\"]'\n value={value || ''}\n style={{fontFamily: 'monospace'}}\n padding={4}\n />\n </Stack>\n </TabPanel>\n\n {/* ----- Result and validation errors ----- */}\n\n {!validation.valid && (\n <Card padding={3} radius={2} tone=\"critical\" border>\n <Flex align=\"flex-start\" gap={2}>\n <Text size={1}>\n <ErrorOutlineIcon />\n </Text>\n\n <Text size={1}>{validation.error}</Text>\n </Flex>\n </Card>\n )}\n </Stack>\n )\n}\n","import {DatabaseIcon} from '@sanity/icons'\nimport {defineField, defineType} from 'sanity'\n\nimport {AgentContextDocumentInput} from './AgentContextDocumentInput'\nimport {GroqFilterInput} from './groq-filter-input/GroqFilterInput'\n\n/**\n * The name of the agent context schema type.\n * @beta\n */\nexport const AGENT_CONTEXT_SCHEMA_TYPE_NAME = 'sanity.agentContext'\n\n/**\n * The title of the agent context schema type.\n */\nexport const AGENT_CONTEXT_SCHEMA_TITLE = 'Agent Context'\n\n/**\n * The schema for the agent context document.\n * @beta\n */\nexport const agentContextSchema = defineType({\n name: AGENT_CONTEXT_SCHEMA_TYPE_NAME,\n title: AGENT_CONTEXT_SCHEMA_TITLE,\n type: 'document',\n icon: DatabaseIcon,\n components: {\n input: AgentContextDocumentInput,\n },\n fields: [\n defineField({\n name: 'name',\n title: 'Name',\n type: 'string',\n placeholder: 'My Agent Context',\n }),\n defineField({\n name: 'slug',\n title: 'Slug',\n type: 'slug',\n options: {\n source: 'name',\n },\n }),\n defineField({\n name: 'groqFilter',\n title: 'Content filter',\n description:\n 'Define which content AI agents can access. Pick types which will generate the filter, or manually enter the filter in the GROQ tab.',\n type: 'string',\n components: {\n input: GroqFilterInput,\n },\n }),\n ],\n})\n","import {definePlugin} from 'sanity'\n\nimport {\n AGENT_CONTEXT_SCHEMA_TITLE,\n AGENT_CONTEXT_SCHEMA_TYPE_NAME,\n agentContextSchema,\n} from './agentContextSchema'\n\n/**\n * The plugin for the agent context.\n * @beta\n */\nexport const contextPlugin = definePlugin({\n name: 'sanity/context/plugin',\n\n schema: {\n types: [agentContextSchema],\n\n // Add template configuration for the `sanity.agentContext` type\n // as the sanity.* namespace is filtered by default.\n templates: (prev) => [\n ...prev,\n {\n id: AGENT_CONTEXT_SCHEMA_TYPE_NAME,\n title: AGENT_CONTEXT_SCHEMA_TITLE,\n schemaType: AGENT_CONTEXT_SCHEMA_TYPE_NAME,\n value: {},\n },\n ],\n },\n})\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/context",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,14 +29,25 @@
|
|
|
29
29
|
"types": "./dist/index.d.ts",
|
|
30
30
|
"files": [
|
|
31
31
|
"dist",
|
|
32
|
-
"src"
|
|
32
|
+
"src",
|
|
33
|
+
"scripts",
|
|
34
|
+
"skills"
|
|
33
35
|
],
|
|
34
36
|
"scripts": {
|
|
35
37
|
"build": "pkg build --strict --clean --check",
|
|
38
|
+
"check:lint": "eslint .",
|
|
39
|
+
"check:types": "tsc --noEmit",
|
|
36
40
|
"dev": "pkg-utils watch",
|
|
37
|
-
"
|
|
41
|
+
"postinstall": "node scripts/install-skill.mjs || exit 0",
|
|
42
|
+
"test": "vitest run --typecheck",
|
|
43
|
+
"test:unit": "vitest --typecheck",
|
|
44
|
+
"test:unit:coverage": "vitest run --coverage",
|
|
45
|
+
"preuninstall": "node scripts/uninstall-skill.mjs || exit 0"
|
|
38
46
|
},
|
|
39
47
|
"browserslist": "extends @sanity/browserslist-config",
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"groq-js": "^1.25.0"
|
|
50
|
+
},
|
|
40
51
|
"devDependencies": {
|
|
41
52
|
"@repo/eslint-config": "workspace:*",
|
|
42
53
|
"@sanity/icons": "^3",
|
|
@@ -45,11 +56,14 @@
|
|
|
45
56
|
"@sanity/ui": "^3",
|
|
46
57
|
"@types/react": "^19",
|
|
47
58
|
"@types/react-dom": "^19",
|
|
59
|
+
"@vitest/coverage-v8": "^4.0.0",
|
|
60
|
+
"eslint": "^9.39.2",
|
|
48
61
|
"react": "^19",
|
|
49
62
|
"react-dom": "^19",
|
|
50
63
|
"sanity": "^5.3.1",
|
|
51
64
|
"styled-components": "^6",
|
|
52
|
-
"typescript": "^5.9.2"
|
|
65
|
+
"typescript": "^5.9.2",
|
|
66
|
+
"vitest": "^4.0.0"
|
|
53
67
|
},
|
|
54
68
|
"peerDependencies": {
|
|
55
69
|
"@sanity/icons": "^3",
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Postinstall script for @sanity/context
|
|
4
|
+
* Copies skill files to consuming project's AI assistant skill directories
|
|
5
|
+
*
|
|
6
|
+
* Fails silently to never break npm install
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {copyFileSync, existsSync, mkdirSync, readdirSync, statSync} from 'node:fs'
|
|
10
|
+
import {dirname, join, resolve} from 'node:path'
|
|
11
|
+
import {fileURLToPath} from 'node:url'
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
14
|
+
const SKILL_FOLDER_NAME = 'sanity-context-mcp'
|
|
15
|
+
|
|
16
|
+
// AI assistant directories to install skills to
|
|
17
|
+
const SKILL_DIRS = ['.claude/skills', '.cursor/skills', '.windsurf/skills']
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Find the consuming project's root directory
|
|
21
|
+
*/
|
|
22
|
+
function findProjectRoot() {
|
|
23
|
+
let current = resolve(__dirname, '..')
|
|
24
|
+
|
|
25
|
+
while (current !== dirname(current)) {
|
|
26
|
+
current = dirname(current)
|
|
27
|
+
|
|
28
|
+
// Skip node_modules directories
|
|
29
|
+
if (current.includes('node_modules')) {
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check for package.json as project root indicator
|
|
34
|
+
if (existsSync(join(current, 'package.json'))) {
|
|
35
|
+
return current
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if we're running in development mode (not as a dependency)
|
|
44
|
+
*/
|
|
45
|
+
function isDevMode() {
|
|
46
|
+
// If we're not inside node_modules, we're in dev
|
|
47
|
+
if (!__dirname.includes('node_modules')) {
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// If INIT_CWD equals our package directory, we're in dev
|
|
52
|
+
const initCwd = process.env.INIT_CWD
|
|
53
|
+
const packageDir = resolve(__dirname, '..')
|
|
54
|
+
|
|
55
|
+
if (initCwd && resolve(initCwd) === packageDir) {
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Recursively copy directory contents
|
|
64
|
+
*/
|
|
65
|
+
function copyDir(src, dest) {
|
|
66
|
+
mkdirSync(dest, {recursive: true})
|
|
67
|
+
|
|
68
|
+
const entries = readdirSync(src)
|
|
69
|
+
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
const srcPath = join(src, entry)
|
|
72
|
+
const destPath = join(dest, entry)
|
|
73
|
+
const stat = statSync(srcPath)
|
|
74
|
+
|
|
75
|
+
if (stat.isDirectory()) {
|
|
76
|
+
copyDir(srcPath, destPath)
|
|
77
|
+
} else {
|
|
78
|
+
copyFileSync(srcPath, destPath)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Main installation logic
|
|
85
|
+
*/
|
|
86
|
+
function main() {
|
|
87
|
+
try {
|
|
88
|
+
// Skip in dev mode
|
|
89
|
+
if (isDevMode()) {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const projectRoot = findProjectRoot()
|
|
94
|
+
if (!projectRoot) {
|
|
95
|
+
// Could be global install or unusual setup - skip silently
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const sourceSkillsDir = join(__dirname, '..', 'skills')
|
|
100
|
+
if (!existsSync(sourceSkillsDir)) {
|
|
101
|
+
// skills folder doesn't exist in package - skip silently
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Install to each AI assistant directory
|
|
106
|
+
for (const skillDir of SKILL_DIRS) {
|
|
107
|
+
const targetDir = join(projectRoot, skillDir, SKILL_FOLDER_NAME)
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
copyDir(sourceSkillsDir, targetDir)
|
|
111
|
+
} catch {
|
|
112
|
+
// Fail silently on individual directory errors
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
// Fail silently - never break npm install
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
main()
|