@sitecoreai-labs/sitecoreai-cli 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brief/api/briefs.d.ts +8 -0
- package/dist/brief/api/briefs.js +49 -11
- package/dist/brief/index.d.ts +1 -1
- package/dist/brief/index.js +2 -1
- package/dist/brief/recipe/index.d.ts +11 -2
- package/dist/brief/recipe/index.js +17 -3
- package/dist/brief/recipe/instance-diff.d.ts +4 -0
- package/dist/brief/recipe/instance-diff.js +77 -0
- package/dist/brief/recipe/instance-kind.d.ts +4 -0
- package/dist/brief/recipe/instance-kind.js +190 -0
- package/dist/brief/recipe/instance-schema.d.ts +61 -0
- package/dist/brief/recipe/instance-schema.js +68 -0
- package/dist/brief/tasks/index.d.ts +35 -0
- package/dist/brief/tasks/index.js +62 -1
- package/dist/commands/brief/create.d.ts +2 -0
- package/dist/commands/brief/create.js +56 -0
- package/dist/commands/brief/index.d.ts +3 -1
- package/dist/commands/brief/index.js +11 -1
- package/dist/commands/brief/sync.d.ts +9 -6
- package/dist/commands/brief/sync.js +54 -22
- package/dist/commands/brief/update.d.ts +2 -0
- package/dist/commands/brief/update.js +84 -0
- package/dist/mcp/descriptions.js +3 -3
- package/dist/mcp/tools/brief-recipe.js +67 -23
- package/dist/mcp/tools/brief.js +83 -6
- package/dist/sync/aggregate-kinds.js +1 -0
- package/package.json +1 -1
|
@@ -2,19 +2,23 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerBriefRecipeTools = void 0;
|
|
4
4
|
/**
|
|
5
|
-
* Brief
|
|
6
|
-
*
|
|
5
|
+
* Brief recipe surface — the MCP projection of both brief recipe kinds.
|
|
6
|
+
* Two workflow-shaped tools, each discriminating on `kind`:
|
|
7
7
|
*
|
|
8
8
|
* - `brief_recipe_inspect` — read. `verb=pull` captures a live brief
|
|
9
|
-
* type as a declarative recipe; `verb=diff` plans
|
|
10
|
-
*
|
|
9
|
+
* type OR brief instance as a declarative recipe; `verb=diff` plans
|
|
10
|
+
* the convergence of the named resource onto a given recipe.
|
|
11
11
|
*
|
|
12
|
-
* - `brief_recipe_push` — write. Converges a brief type
|
|
13
|
-
* gated by `allowWrite`; `whatIf` returns
|
|
12
|
+
* - `brief_recipe_push` — write. Converges a brief type or brief
|
|
13
|
+
* instance onto a recipe, gated by `allowWrite`; `whatIf` returns
|
|
14
|
+
* the plan without writing.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* `kind: 'brief-type'` is the legacy default (kept for back-compat with
|
|
17
|
+
* the pre-instance surface). `kind: 'brief'` operates on populated
|
|
18
|
+
* brief instances. The matching recipe schema becomes the input shape
|
|
19
|
+
* for `recipe` automatically — the schema is the single source of truth.
|
|
20
|
+
*
|
|
21
|
+
* See docs/recipe-sync-architecture.md.
|
|
18
22
|
*/
|
|
19
23
|
const zod_1 = require("zod");
|
|
20
24
|
const recipe_1 = require("../../brief/recipe");
|
|
@@ -31,53 +35,81 @@ const planSummaryText = (plan) => {
|
|
|
31
35
|
const tally = (0, sync_1.summarizePlan)(plan);
|
|
32
36
|
return `Plan: ${tally.create} create, ${tally.update} update, ${tally.delete} delete, ${tally.noop} unchanged.`;
|
|
33
37
|
};
|
|
38
|
+
/** Map the MCP `kind` discriminator to the underlying `RecipeKind`. */
|
|
39
|
+
const recipeKindFor = (kind) => {
|
|
40
|
+
const resolved = kind ?? "brief-type";
|
|
41
|
+
return resolved === "brief"
|
|
42
|
+
? recipe_1.briefInstanceKind
|
|
43
|
+
: recipe_1.briefTypeKind;
|
|
44
|
+
};
|
|
34
45
|
const registerBriefRecipeTools = (registry) => {
|
|
35
46
|
registry.registerTool({
|
|
36
47
|
name: "brief_recipe_inspect",
|
|
37
48
|
description: descriptions_1.TOOL_DESCRIPTIONS.brief_recipe_inspect,
|
|
38
49
|
auth: "read",
|
|
39
50
|
annotations: {
|
|
40
|
-
title: "Pull or diff a brief type as a declarative recipe",
|
|
51
|
+
title: "Pull or diff a brief type or brief instance as a declarative recipe",
|
|
41
52
|
readOnlyHint: true,
|
|
42
53
|
destructiveHint: false,
|
|
43
54
|
openWorldHint: true,
|
|
44
55
|
},
|
|
45
56
|
inputSchema: {
|
|
57
|
+
kind: zod_1.z
|
|
58
|
+
.enum(["brief-type", "brief"])
|
|
59
|
+
.default("brief-type")
|
|
60
|
+
.describe("Which recipe kind to operate on. 'brief-type' (default — the schema template) or 'brief' (a populated brief instance)."),
|
|
46
61
|
verb: zod_1.z
|
|
47
62
|
.enum(["pull", "diff"])
|
|
48
|
-
.describe("pull: capture the live
|
|
49
|
-
name: zod_1.z
|
|
50
|
-
|
|
63
|
+
.describe("pull: capture the live resource named `name` as a recipe. diff: compare `recipe` against the live resource and return the plan."),
|
|
64
|
+
name: zod_1.z
|
|
65
|
+
.string()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Brief-type codename or brief display name. Required for verb='pull'."),
|
|
68
|
+
recipe: zod_1.z
|
|
69
|
+
.union([recipe_1.briefTypeKind.schema, recipe_1.briefInstanceKind.schema])
|
|
51
70
|
.optional()
|
|
52
|
-
.describe("A brief-type recipe. Required for verb='diff'."),
|
|
71
|
+
.describe("A brief-type recipe or brief-instance recipe (matching `kind`). Required for verb='diff'."),
|
|
53
72
|
...common_1.environmentBindingShape,
|
|
54
73
|
},
|
|
55
74
|
handler: async (input, context) => {
|
|
56
75
|
const ctx = syncContextFrom(context, input.environmentName);
|
|
76
|
+
const kind = recipeKindFor(input.kind);
|
|
77
|
+
const humanKind = input.kind === "brief" ? "brief" : "brief type";
|
|
57
78
|
if (input.verb === "pull") {
|
|
58
79
|
if (!input.name) {
|
|
59
80
|
throw (0, errors_1.createScaiError)("verb='pull' requires `name`.", "INPUT_INVALID");
|
|
60
81
|
}
|
|
61
|
-
const recipe = await (0, sync_1.syncPull)(
|
|
82
|
+
const recipe = await (0, sync_1.syncPull)(kind, { kind: kind.name, id: input.name }, ctx);
|
|
62
83
|
return {
|
|
63
84
|
content: [
|
|
64
85
|
{
|
|
65
86
|
type: "text",
|
|
66
87
|
text: recipe
|
|
67
|
-
? `Captured "${input.name}" as a recipe.`
|
|
68
|
-
: `No
|
|
88
|
+
? `Captured ${humanKind} "${input.name}" as a recipe.`
|
|
89
|
+
: `No ${humanKind} named "${input.name}".`,
|
|
69
90
|
},
|
|
70
91
|
],
|
|
71
|
-
structuredContent: {
|
|
92
|
+
structuredContent: {
|
|
93
|
+
kind: input.kind ?? "brief-type",
|
|
94
|
+
verb: input.verb,
|
|
95
|
+
found: recipe !== null,
|
|
96
|
+
recipe,
|
|
97
|
+
},
|
|
72
98
|
};
|
|
73
99
|
}
|
|
74
100
|
if (!input.recipe) {
|
|
75
101
|
throw (0, errors_1.createScaiError)("verb='diff' requires `recipe`.", "INPUT_INVALID");
|
|
76
102
|
}
|
|
77
|
-
const
|
|
103
|
+
const recipe = input.recipe;
|
|
104
|
+
const plan = await (0, sync_1.syncDiff)(kind, recipe, { kind: kind.name, id: recipe.name }, ctx);
|
|
78
105
|
return {
|
|
79
106
|
content: [{ type: "text", text: planSummaryText(plan) }],
|
|
80
|
-
structuredContent: {
|
|
107
|
+
structuredContent: {
|
|
108
|
+
kind: input.kind ?? "brief-type",
|
|
109
|
+
verb: input.verb,
|
|
110
|
+
plan,
|
|
111
|
+
summary: (0, sync_1.summarizePlan)(plan),
|
|
112
|
+
},
|
|
81
113
|
};
|
|
82
114
|
},
|
|
83
115
|
});
|
|
@@ -86,13 +118,19 @@ const registerBriefRecipeTools = (registry) => {
|
|
|
86
118
|
description: descriptions_1.TOOL_DESCRIPTIONS.brief_recipe_push,
|
|
87
119
|
auth: "write",
|
|
88
120
|
annotations: {
|
|
89
|
-
title: "Push a brief-type recipe — converge the
|
|
121
|
+
title: "Push a brief-type or brief recipe — converge the resource onto the recipe",
|
|
90
122
|
readOnlyHint: false,
|
|
91
123
|
destructiveHint: true,
|
|
92
124
|
openWorldHint: true,
|
|
93
125
|
},
|
|
94
126
|
inputSchema: {
|
|
95
|
-
|
|
127
|
+
kind: zod_1.z
|
|
128
|
+
.enum(["brief-type", "brief"])
|
|
129
|
+
.default("brief-type")
|
|
130
|
+
.describe("Which recipe kind to push. 'brief-type' (default — the schema template) or 'brief' (a populated brief instance)."),
|
|
131
|
+
recipe: zod_1.z
|
|
132
|
+
.union([recipe_1.briefTypeKind.schema, recipe_1.briefInstanceKind.schema])
|
|
133
|
+
.describe("The recipe to converge onto. Schema must match `kind`. The resource is identified by `recipe.name`."),
|
|
96
134
|
prune: zod_1.z
|
|
97
135
|
.boolean()
|
|
98
136
|
.default(false)
|
|
@@ -103,14 +141,20 @@ const registerBriefRecipeTools = (registry) => {
|
|
|
103
141
|
},
|
|
104
142
|
handler: async (input, context, extra) => {
|
|
105
143
|
const ctx = syncContextFrom(context, input.environmentName, extra.signal);
|
|
144
|
+
const kind = recipeKindFor(input.kind);
|
|
145
|
+
const recipe = input.recipe;
|
|
106
146
|
const mode = input.whatIf ? "what-if" : "apply";
|
|
107
|
-
const outcome = await (0, sync_1.syncPush)(
|
|
147
|
+
const outcome = await (0, sync_1.syncPush)(kind, recipe, { kind: kind.name, id: recipe.name }, ctx, {
|
|
148
|
+
mode,
|
|
149
|
+
prune: input.prune,
|
|
150
|
+
});
|
|
108
151
|
const text = outcome.result
|
|
109
152
|
? `Applied ${outcome.result.applied.length} change(s); ${outcome.result.skipped.length} skipped.`
|
|
110
153
|
: planSummaryText(outcome.plan);
|
|
111
154
|
return {
|
|
112
155
|
content: [{ type: "text", text }],
|
|
113
156
|
structuredContent: {
|
|
157
|
+
kind: input.kind ?? "brief-type",
|
|
114
158
|
mode,
|
|
115
159
|
plan: outcome.plan,
|
|
116
160
|
summary: (0, sync_1.summarizePlan)(outcome.plan),
|
package/dist/mcp/tools/brief.js
CHANGED
|
@@ -186,21 +186,21 @@ const registerBriefTools = (registry) => {
|
|
|
186
186
|
.describe("Which Brief resource the verb targets. 'brief-type' supports create/update/delete; 'brief' supports set-status/delete; 'comment' supports create."),
|
|
187
187
|
verb: zod_1.z
|
|
188
188
|
.enum(["create", "update", "delete", "set-status"])
|
|
189
|
-
.describe("Mutation verb. brief-type: create (POST), update (PUT-replace), delete (irreversible). brief: set-status, delete. comment: create."),
|
|
189
|
+
.describe("Mutation verb. brief-type: create (POST), update (PUT-replace), delete (irreversible). brief: create (POST), update (partial PUT), set-status, delete. comment: create."),
|
|
190
190
|
briefTypeId: zod_1.z
|
|
191
191
|
.string()
|
|
192
192
|
.uuid()
|
|
193
193
|
.optional()
|
|
194
|
-
.describe("Brief type UUID. Required for brief-type verb='update' and verb='delete'."),
|
|
194
|
+
.describe("Brief type UUID. Required for brief-type verb='update' and verb='delete', and for brief verb='create' (the type to build the brief against)."),
|
|
195
195
|
briefId: zod_1.z
|
|
196
196
|
.string()
|
|
197
197
|
.uuid()
|
|
198
198
|
.optional()
|
|
199
|
-
.describe("Brief UUID. Required for resource='brief'
|
|
199
|
+
.describe("Brief UUID. Required for resource='brief' verbs 'update', 'set-status', and 'delete'."),
|
|
200
200
|
status: zod_1.z
|
|
201
201
|
.enum(["Draft", "InReview", "Approved", "Canceled", "Archived"])
|
|
202
202
|
.optional()
|
|
203
|
-
.describe("Target brief status for verb='set-status'. Wire form — 'InReview' is the 'In Review' UI label. A brief must leave 'Draft' before it can be linked to a campaign."),
|
|
203
|
+
.describe("Target brief status. Required for verb='set-status'; optional on brief 'create'/'update' (defaults to server 'Draft' on create). Wire form — 'InReview' is the 'In Review' UI label. A brief must leave 'Draft' before it can be linked to a campaign."),
|
|
204
204
|
commentText: zod_1.z
|
|
205
205
|
.string()
|
|
206
206
|
.optional()
|
|
@@ -221,7 +221,19 @@ const registerBriefTools = (registry) => {
|
|
|
221
221
|
.describe("Field definitions (RichText | DateTime | Timeline | Budget). Use brief_inspect verb='type' on an existing type for a worked example."),
|
|
222
222
|
})
|
|
223
223
|
.optional()
|
|
224
|
-
.describe("Full BriefType body for verb='create' or verb='update'. Required on writes; ignored on delete."),
|
|
224
|
+
.describe("Full BriefType body for resource='brief-type' verb='create' or verb='update'. Required on brief-type writes; ignored on delete."),
|
|
225
|
+
brief: zod_1.z
|
|
226
|
+
.object({
|
|
227
|
+
name: zod_1.z.string().min(1).describe("Brief display name."),
|
|
228
|
+
locale: zod_1.z.string().optional().describe("BCP-47-ish locale, e.g. 'en-us'."),
|
|
229
|
+
fields: zod_1.z
|
|
230
|
+
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
231
|
+
.optional()
|
|
232
|
+
.describe("Field values keyed by BriefField.name; the per-field shape follows the brief type's field definitions."),
|
|
233
|
+
isTemplate: zod_1.z.boolean().optional().describe("Whether the brief is a template."),
|
|
234
|
+
})
|
|
235
|
+
.optional()
|
|
236
|
+
.describe("Brief body. Required for resource='brief' verb='create' (with `briefTypeId`); optional for verb='update' (any subset of name/locale/fields/isTemplate plus the top-level `status`)."),
|
|
225
237
|
...common_1.environmentBindingShape,
|
|
226
238
|
...common_1.allowWriteShape,
|
|
227
239
|
...common_1.whatIfShape,
|
|
@@ -230,6 +242,71 @@ const registerBriefTools = (registry) => {
|
|
|
230
242
|
const taskOpts = baseTaskOptions(context.configPath, input.environmentName ?? context.envName);
|
|
231
243
|
const whatIf = input.whatIf;
|
|
232
244
|
if (input.resource === "brief") {
|
|
245
|
+
if (input.verb === "create") {
|
|
246
|
+
if (!input.brief) {
|
|
247
|
+
throw (0, errors_1.createScaiError)("verb='create' on resource='brief' requires `brief`.", "INPUT_INVALID");
|
|
248
|
+
}
|
|
249
|
+
if (!input.briefTypeId) {
|
|
250
|
+
throw (0, errors_1.createScaiError)("verb='create' on resource='brief' requires `briefTypeId`.", "INPUT_INVALID");
|
|
251
|
+
}
|
|
252
|
+
const createInput = {
|
|
253
|
+
name: input.brief.name,
|
|
254
|
+
briefTypeId: input.briefTypeId,
|
|
255
|
+
...(input.brief.locale !== undefined && { locale: input.brief.locale }),
|
|
256
|
+
...(input.brief.fields !== undefined && { fields: input.brief.fields }),
|
|
257
|
+
...(input.brief.isTemplate !== undefined && { isTemplate: input.brief.isTemplate }),
|
|
258
|
+
};
|
|
259
|
+
const result = await (0, tasks_1.runBriefCreate)({
|
|
260
|
+
...taskOpts,
|
|
261
|
+
input: createInput,
|
|
262
|
+
whatIf,
|
|
263
|
+
});
|
|
264
|
+
const isPlan = "plan" in result;
|
|
265
|
+
return {
|
|
266
|
+
content: [
|
|
267
|
+
{
|
|
268
|
+
type: "text",
|
|
269
|
+
text: isPlan
|
|
270
|
+
? `Plan: create brief '${input.brief.name}'.`
|
|
271
|
+
: `Created brief '${input.brief.name}'.`,
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
structuredContent: { resource: input.resource, verb: input.verb, result },
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (input.verb === "update") {
|
|
278
|
+
if (!input.briefId) {
|
|
279
|
+
throw (0, errors_1.createScaiError)("verb='update' on resource='brief' requires `briefId`.", "INPUT_INVALID");
|
|
280
|
+
}
|
|
281
|
+
const patch = {
|
|
282
|
+
...(input.brief?.name !== undefined && { name: input.brief.name }),
|
|
283
|
+
...(input.brief?.locale !== undefined && { locale: input.brief.locale }),
|
|
284
|
+
...(input.brief?.fields !== undefined && { fields: input.brief.fields }),
|
|
285
|
+
...(input.brief?.isTemplate !== undefined && { isTemplate: input.brief.isTemplate }),
|
|
286
|
+
...(input.status !== undefined && { status: input.status }),
|
|
287
|
+
};
|
|
288
|
+
if (Object.keys(patch).length === 0) {
|
|
289
|
+
throw (0, errors_1.createScaiError)("verb='update' on resource='brief' requires at least one field in `brief` or a top-level `status`.", "INPUT_INVALID");
|
|
290
|
+
}
|
|
291
|
+
const result = await (0, tasks_1.runBriefUpdate)({
|
|
292
|
+
...taskOpts,
|
|
293
|
+
briefId: input.briefId,
|
|
294
|
+
patch,
|
|
295
|
+
whatIf,
|
|
296
|
+
});
|
|
297
|
+
const isPlan = "plan" in result;
|
|
298
|
+
return {
|
|
299
|
+
content: [
|
|
300
|
+
{
|
|
301
|
+
type: "text",
|
|
302
|
+
text: isPlan
|
|
303
|
+
? `Plan: update brief ${input.briefId} (${Object.keys(patch).join(", ")}).`
|
|
304
|
+
: `Updated brief ${input.briefId}.`,
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
structuredContent: { resource: input.resource, verb: input.verb, result },
|
|
308
|
+
};
|
|
309
|
+
}
|
|
233
310
|
if (input.verb === "set-status") {
|
|
234
311
|
if (!input.briefId) {
|
|
235
312
|
throw (0, errors_1.createScaiError)("verb='set-status' requires `briefId`.", "INPUT_INVALID");
|
|
@@ -277,7 +354,7 @@ const registerBriefTools = (registry) => {
|
|
|
277
354
|
structuredContent: { resource: input.resource, verb: input.verb, result },
|
|
278
355
|
};
|
|
279
356
|
}
|
|
280
|
-
throw (0, errors_1.createScaiError)("resource='brief' supports
|
|
357
|
+
throw (0, errors_1.createScaiError)("resource='brief' supports verbs 'create', 'update', 'set-status', and 'delete'.", "INPUT_INVALID");
|
|
281
358
|
}
|
|
282
359
|
if (input.resource === "comment") {
|
|
283
360
|
if (input.verb !== "create") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sitecoreai-labs/sitecoreai-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "SitecoreAI developer toolkit — a native TypeScript CLI, SDK, and MCP server for deploy, serialization, recipes, publishing, and content operations.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|