@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
|
@@ -43,6 +43,14 @@ export type CreateBriefInput = {
|
|
|
43
43
|
fields?: Record<string, unknown>;
|
|
44
44
|
isTemplate?: boolean;
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Validate an unknown value as a `CreateBriefInput`. Throws a typed
|
|
48
|
+
* `ScaiError` (`INPUT_INVALID`) on failure; returns the narrowed input
|
|
49
|
+
* on success. `createBrief` calls this itself, so direct SDK consumers
|
|
50
|
+
* are guarded without invoking it explicitly. Mirrors
|
|
51
|
+
* `assertCreateBriefTypeInput` in `./brief-types.ts`.
|
|
52
|
+
*/
|
|
53
|
+
export declare const assertCreateBriefInput: (value: unknown) => CreateBriefInput;
|
|
46
54
|
/** Create a brief. Returns the persisted record (201). */
|
|
47
55
|
export declare const createBrief: (options: BriefApiClientOptions, input: CreateBriefInput) => Promise<Brief>;
|
|
48
56
|
/**
|
package/dist/brief/api/briefs.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.deleteBrief = exports.setBriefStatus = exports.updateBrief = exports.createBrief = exports.getBrief = exports.listBriefs = void 0;
|
|
3
|
+
exports.deleteBrief = exports.setBriefStatus = exports.updateBrief = exports.createBrief = exports.assertCreateBriefInput = exports.getBrief = exports.listBriefs = void 0;
|
|
4
|
+
const errors_1 = require("../../shared/errors");
|
|
4
5
|
const request_1 = require("./request");
|
|
5
6
|
/** List all briefs in the tenant (paged). */
|
|
6
7
|
const listBriefs = (options, query) => {
|
|
@@ -19,17 +20,54 @@ exports.listBriefs = listBriefs;
|
|
|
19
20
|
/** Read a single brief by id. 404 surfaces as `BRIEF_API_FAILED` with `"Brief not found"`. */
|
|
20
21
|
const getBrief = (options, briefId) => (0, request_1.briefRequest)(options, `/api/brief/v1/briefs/${encodeURIComponent(briefId)}`);
|
|
21
22
|
exports.getBrief = getBrief;
|
|
23
|
+
/** Required keys on a `CreateBriefInput` body. */
|
|
24
|
+
const BRIEF_REQUIRED_KEYS = ["name", "briefTypeId"];
|
|
25
|
+
/**
|
|
26
|
+
* Validate an unknown value as a `CreateBriefInput`. Throws a typed
|
|
27
|
+
* `ScaiError` (`INPUT_INVALID`) on failure; returns the narrowed input
|
|
28
|
+
* on success. `createBrief` calls this itself, so direct SDK consumers
|
|
29
|
+
* are guarded without invoking it explicitly. Mirrors
|
|
30
|
+
* `assertCreateBriefTypeInput` in `./brief-types.ts`.
|
|
31
|
+
*/
|
|
32
|
+
const assertCreateBriefInput = (value) => {
|
|
33
|
+
if (!value || typeof value !== "object") {
|
|
34
|
+
throw (0, errors_1.createScaiError)("Brief body must be a JSON object.", "INPUT_INVALID");
|
|
35
|
+
}
|
|
36
|
+
const obj = value;
|
|
37
|
+
const missing = BRIEF_REQUIRED_KEYS.filter((key) => obj[key] === undefined);
|
|
38
|
+
if (missing.length > 0) {
|
|
39
|
+
throw (0, errors_1.createScaiError)(`Brief body is missing required fields: ${missing.join(", ")}.`, "INPUT_INVALID", { hint: "Required: name (string), briefTypeId (UUID of the brief type)." });
|
|
40
|
+
}
|
|
41
|
+
if (typeof obj.name !== "string" || obj.name.length === 0) {
|
|
42
|
+
throw (0, errors_1.createScaiError)(`Invalid 'name': ${JSON.stringify(obj.name)}.`, "INPUT_INVALID", {
|
|
43
|
+
hint: "Brief name must be a non-empty string.",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (typeof obj.briefTypeId !== "string" || obj.briefTypeId.length === 0) {
|
|
47
|
+
throw (0, errors_1.createScaiError)(`Invalid 'briefTypeId': ${JSON.stringify(obj.briefTypeId)}.`, "INPUT_INVALID", {
|
|
48
|
+
hint: "Pass the brief type's UUID. List types with `scai ops brief types list`.",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (obj.fields !== undefined && (typeof obj.fields !== "object" || Array.isArray(obj.fields))) {
|
|
52
|
+
throw (0, errors_1.createScaiError)("'fields' must be an object keyed by field name.", "INPUT_INVALID");
|
|
53
|
+
}
|
|
54
|
+
return obj;
|
|
55
|
+
};
|
|
56
|
+
exports.assertCreateBriefInput = assertCreateBriefInput;
|
|
22
57
|
/** Create a brief. Returns the persisted record (201). */
|
|
23
|
-
const createBrief = (options, input) =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
58
|
+
const createBrief = (options, input) => {
|
|
59
|
+
(0, exports.assertCreateBriefInput)(input);
|
|
60
|
+
return (0, request_1.briefRequest)(options, "/api/brief/v1/briefs", {
|
|
61
|
+
method: "POST",
|
|
62
|
+
body: {
|
|
63
|
+
name: input.name,
|
|
64
|
+
briefTypeId: input.briefTypeId,
|
|
65
|
+
locale: input.locale,
|
|
66
|
+
fields: input.fields,
|
|
67
|
+
isTemplate: input.isTemplate,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
};
|
|
33
71
|
exports.createBrief = createBrief;
|
|
34
72
|
/**
|
|
35
73
|
* Partial update of a brief (`PUT /api/brief/v1/briefs/{id}` — 204 No
|
package/dist/brief/index.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export type { BriefApiClientOptions, BriefRequestInit, BriefQueryValue, BriefQue
|
|
|
23
23
|
export { BRIEF_API_HOST_TEMPLATE, DEFAULT_BRIEF_API_BASE } from "./api/types";
|
|
24
24
|
export { briefRequest } from "./api/request";
|
|
25
25
|
export type { PagedResult, LocalizedString, Link, ExternalLink, Reference, BriefField, BriefFieldBase, RichTextField, DateTimeField, TimelineField, BudgetField, BriefType, Brief, BriefStatus, BriefTask, BriefComment, ExternalMapping, } from "./api/schema";
|
|
26
|
-
export { listBriefs, getBrief, createBrief, updateBrief, setBriefStatus, deleteBrief, type CreateBriefInput, type ListBriefsQuery, } from "./api/briefs";
|
|
26
|
+
export { listBriefs, getBrief, createBrief, updateBrief, setBriefStatus, deleteBrief, assertCreateBriefInput, type CreateBriefInput, type ListBriefsQuery, } from "./api/briefs";
|
|
27
27
|
export { listBriefTypes, getBriefType, createBriefType, updateBriefType, deleteBriefType, type CreateBriefTypeInput, } from "./api/brief-types";
|
|
28
28
|
export { listBriefTasks, getBriefTask, type BriefTaskMetadata, type ListBriefTasksQuery, } from "./api/tasks";
|
|
29
29
|
export { listBriefComments, createBriefComment, type ListBriefCommentsQuery, type CreateBriefCommentInput, } from "./api/comments";
|
package/dist/brief/index.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* (read+write). The Agents env client carries both by default.
|
|
22
22
|
*/
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.resolveBriefClient = exports.BRIEF_SCOPES_REQUESTED = exports.acquireBriefToken = exports.createBriefComment = exports.listBriefComments = exports.getBriefTask = exports.listBriefTasks = exports.deleteBriefType = exports.updateBriefType = exports.createBriefType = exports.getBriefType = exports.listBriefTypes = exports.deleteBrief = exports.setBriefStatus = exports.updateBrief = exports.createBrief = exports.getBrief = exports.listBriefs = exports.briefRequest = exports.DEFAULT_BRIEF_API_BASE = exports.BRIEF_API_HOST_TEMPLATE = void 0;
|
|
24
|
+
exports.resolveBriefClient = exports.BRIEF_SCOPES_REQUESTED = exports.acquireBriefToken = exports.createBriefComment = exports.listBriefComments = exports.getBriefTask = exports.listBriefTasks = exports.deleteBriefType = exports.updateBriefType = exports.createBriefType = exports.getBriefType = exports.listBriefTypes = exports.assertCreateBriefInput = exports.deleteBrief = exports.setBriefStatus = exports.updateBrief = exports.createBrief = exports.getBrief = exports.listBriefs = exports.briefRequest = exports.DEFAULT_BRIEF_API_BASE = exports.BRIEF_API_HOST_TEMPLATE = void 0;
|
|
25
25
|
var types_1 = require("./api/types");
|
|
26
26
|
Object.defineProperty(exports, "BRIEF_API_HOST_TEMPLATE", { enumerable: true, get: function () { return types_1.BRIEF_API_HOST_TEMPLATE; } });
|
|
27
27
|
Object.defineProperty(exports, "DEFAULT_BRIEF_API_BASE", { enumerable: true, get: function () { return types_1.DEFAULT_BRIEF_API_BASE; } });
|
|
@@ -34,6 +34,7 @@ Object.defineProperty(exports, "createBrief", { enumerable: true, get: function
|
|
|
34
34
|
Object.defineProperty(exports, "updateBrief", { enumerable: true, get: function () { return briefs_1.updateBrief; } });
|
|
35
35
|
Object.defineProperty(exports, "setBriefStatus", { enumerable: true, get: function () { return briefs_1.setBriefStatus; } });
|
|
36
36
|
Object.defineProperty(exports, "deleteBrief", { enumerable: true, get: function () { return briefs_1.deleteBrief; } });
|
|
37
|
+
Object.defineProperty(exports, "assertCreateBriefInput", { enumerable: true, get: function () { return briefs_1.assertCreateBriefInput; } });
|
|
37
38
|
var brief_types_1 = require("./api/brief-types");
|
|
38
39
|
Object.defineProperty(exports, "listBriefTypes", { enumerable: true, get: function () { return brief_types_1.listBriefTypes; } });
|
|
39
40
|
Object.defineProperty(exports, "getBriefType", { enumerable: true, get: function () { return brief_types_1.getBriefType; } });
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
* for Sitecore Content Operations brief types.
|
|
2
|
+
* The brief recipe surface — declarative definitions + `sync` support
|
|
3
|
+
* for Sitecore Content Operations brief types AND brief instances.
|
|
4
|
+
*
|
|
5
|
+
* Two kinds:
|
|
6
|
+
* - `briefTypeKind` ("brief-type") — the schema template a brief is
|
|
7
|
+
* built against. Identified by stable codename.
|
|
8
|
+
* - `briefInstanceKind` ("brief") — a populated brief instance.
|
|
9
|
+
* Identified by display name; references its type by codename.
|
|
4
10
|
*
|
|
5
11
|
* See docs/recipe-sync-architecture.md.
|
|
6
12
|
*/
|
|
@@ -8,3 +14,6 @@ export { BriefTypeRecipeSchema, BriefFieldSchema, RichTextFieldSchema, DateTimeF
|
|
|
8
14
|
export { diffBriefType } from "./diff";
|
|
9
15
|
export { resolveBriefClient } from "./client";
|
|
10
16
|
export { briefTypeKind } from "./kind";
|
|
17
|
+
export { BriefInstanceRecipeSchema, BriefInstanceStatusSchema, BriefInstanceFieldsSchema, type BriefInstanceRecipe, } from "./instance-schema";
|
|
18
|
+
export { diffBriefInstance } from "./instance-diff";
|
|
19
|
+
export { briefInstanceKind } from "./instance-kind";
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.briefTypeKind = exports.resolveBriefClient = exports.diffBriefType = exports.LocalizedStringSchema = exports.BudgetFieldSchema = exports.TimelineFieldSchema = exports.DateTimeFieldSchema = exports.RichTextFieldSchema = exports.BriefFieldSchema = exports.BriefTypeRecipeSchema = void 0;
|
|
3
|
+
exports.briefInstanceKind = exports.diffBriefInstance = exports.BriefInstanceFieldsSchema = exports.BriefInstanceStatusSchema = exports.BriefInstanceRecipeSchema = exports.briefTypeKind = exports.resolveBriefClient = exports.diffBriefType = exports.LocalizedStringSchema = exports.BudgetFieldSchema = exports.TimelineFieldSchema = exports.DateTimeFieldSchema = exports.RichTextFieldSchema = exports.BriefFieldSchema = exports.BriefTypeRecipeSchema = void 0;
|
|
4
4
|
/**
|
|
5
|
-
* The
|
|
6
|
-
* for Sitecore Content Operations brief types.
|
|
5
|
+
* The brief recipe surface — declarative definitions + `sync` support
|
|
6
|
+
* for Sitecore Content Operations brief types AND brief instances.
|
|
7
|
+
*
|
|
8
|
+
* Two kinds:
|
|
9
|
+
* - `briefTypeKind` ("brief-type") — the schema template a brief is
|
|
10
|
+
* built against. Identified by stable codename.
|
|
11
|
+
* - `briefInstanceKind` ("brief") — a populated brief instance.
|
|
12
|
+
* Identified by display name; references its type by codename.
|
|
7
13
|
*
|
|
8
14
|
* See docs/recipe-sync-architecture.md.
|
|
9
15
|
*/
|
|
@@ -21,3 +27,11 @@ var client_1 = require("./client");
|
|
|
21
27
|
Object.defineProperty(exports, "resolveBriefClient", { enumerable: true, get: function () { return client_1.resolveBriefClient; } });
|
|
22
28
|
var kind_1 = require("./kind");
|
|
23
29
|
Object.defineProperty(exports, "briefTypeKind", { enumerable: true, get: function () { return kind_1.briefTypeKind; } });
|
|
30
|
+
var instance_schema_1 = require("./instance-schema");
|
|
31
|
+
Object.defineProperty(exports, "BriefInstanceRecipeSchema", { enumerable: true, get: function () { return instance_schema_1.BriefInstanceRecipeSchema; } });
|
|
32
|
+
Object.defineProperty(exports, "BriefInstanceStatusSchema", { enumerable: true, get: function () { return instance_schema_1.BriefInstanceStatusSchema; } });
|
|
33
|
+
Object.defineProperty(exports, "BriefInstanceFieldsSchema", { enumerable: true, get: function () { return instance_schema_1.BriefInstanceFieldsSchema; } });
|
|
34
|
+
var instance_diff_1 = require("./instance-diff");
|
|
35
|
+
Object.defineProperty(exports, "diffBriefInstance", { enumerable: true, get: function () { return instance_diff_1.diffBriefInstance; } });
|
|
36
|
+
var instance_kind_1 = require("./instance-kind");
|
|
37
|
+
Object.defineProperty(exports, "briefInstanceKind", { enumerable: true, get: function () { return instance_kind_1.briefInstanceKind; } });
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RecipePlan } from "../../sync";
|
|
2
|
+
import type { BriefInstanceRecipe } from "./instance-schema";
|
|
3
|
+
/** Diff a desired brief-instance recipe against captured current state. */
|
|
4
|
+
export declare const diffBriefInstance: (desired: BriefInstanceRecipe, current: BriefInstanceRecipe | null) => RecipePlan;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.diffBriefInstance = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The pure diff for the `brief` recipe kind — desired recipe vs.
|
|
6
|
+
* captured current state → a `RecipePlan`. No I/O, so it is cheap to
|
|
7
|
+
* unit-test and is the testable core of the kind.
|
|
8
|
+
*
|
|
9
|
+
* Unlike `brief-type` (which is a single full-replacement PUT), brief
|
|
10
|
+
* instances support a partial PUT: the diff therefore emits two stages
|
|
11
|
+
* of changes:
|
|
12
|
+
*
|
|
13
|
+
* - One `stage: "instance"` change carries the whole desired recipe.
|
|
14
|
+
* `apply` consumes it to drive `createBrief` or `updateBrief`.
|
|
15
|
+
* - Per-element `stage: "field"` changes describe the convergence
|
|
16
|
+
* element-by-element so the rendered plan reads cleanly. They are
|
|
17
|
+
* descriptive only — the single `stage: "instance"` change is what
|
|
18
|
+
* `apply` writes.
|
|
19
|
+
*
|
|
20
|
+
* Identification is by `name` (the brief's display name). Brief
|
|
21
|
+
* instances are not enforced unique by name on the server; the diff and
|
|
22
|
+
* apply both treat "first match wins", matching the campaign-instance
|
|
23
|
+
* precedent. See docs/recipe-sync-architecture.md.
|
|
24
|
+
*/
|
|
25
|
+
const node_util_1 = require("node:util");
|
|
26
|
+
/**
|
|
27
|
+
* The top-level recipe elements compared one-by-one when the brief
|
|
28
|
+
* exists. `briefTypeName` is included because the diff surfaces a
|
|
29
|
+
* type-change attempt — `apply` then refuses it (the Brief API has no
|
|
30
|
+
* verified path to repoint an existing brief at a different type).
|
|
31
|
+
*/
|
|
32
|
+
const COMPARED_ELEMENTS = ["briefTypeName", "locale", "status", "isTemplate", "fields"];
|
|
33
|
+
/** Diff a desired brief-instance recipe against captured current state. */
|
|
34
|
+
const diffBriefInstance = (desired, current) => {
|
|
35
|
+
const changes = [];
|
|
36
|
+
if (current === null) {
|
|
37
|
+
changes.push({
|
|
38
|
+
kind: "create",
|
|
39
|
+
path: "brief",
|
|
40
|
+
summary: `Create brief "${desired.name}"`,
|
|
41
|
+
after: desired.name,
|
|
42
|
+
meta: { stage: "instance", recipe: desired },
|
|
43
|
+
});
|
|
44
|
+
return { changes };
|
|
45
|
+
}
|
|
46
|
+
// The brief exists — compare each element so the plan reads
|
|
47
|
+
// element-by-element. `apply` acts on the lead `stage: "instance"`
|
|
48
|
+
// change, not on these per-element entries.
|
|
49
|
+
let anyElementChanged = false;
|
|
50
|
+
for (const element of COMPARED_ELEMENTS) {
|
|
51
|
+
const before = current[element];
|
|
52
|
+
const after = desired[element];
|
|
53
|
+
const path = `brief.${element}`;
|
|
54
|
+
const meta = { stage: "field", element };
|
|
55
|
+
if ((0, node_util_1.isDeepStrictEqual)(before, after)) {
|
|
56
|
+
changes.push({ kind: "noop", path, summary: `${element} unchanged`, meta });
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
anyElementChanged = true;
|
|
60
|
+
changes.push({ kind: "update", path, summary: `${element}`, before, after, meta });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// A single PUT converges every changed element at once. Emitted as
|
|
64
|
+
// the lead change so `apply` finds it without scanning.
|
|
65
|
+
if (anyElementChanged) {
|
|
66
|
+
changes.unshift({
|
|
67
|
+
kind: "update",
|
|
68
|
+
path: "brief",
|
|
69
|
+
summary: `Update brief "${desired.name}"`,
|
|
70
|
+
before: current.name,
|
|
71
|
+
after: desired.name,
|
|
72
|
+
meta: { stage: "instance", recipe: desired },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return { changes };
|
|
76
|
+
};
|
|
77
|
+
exports.diffBriefInstance = diffBriefInstance;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.briefInstanceKind = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The `brief` recipe kind — wires the Sitecore Content Operations
|
|
6
|
+
* Brief instance API into the `sync` engine.
|
|
7
|
+
*
|
|
8
|
+
* `ref.id` is the brief's display NAME — recipes identify a brief by
|
|
9
|
+
* name, not UUID, mirroring how `campaign` identifies a project. The
|
|
10
|
+
* brief type is referenced by its stable codename (`briefTypeName`)
|
|
11
|
+
* and resolved to a server id at push-time via `listBriefTypes`.
|
|
12
|
+
*
|
|
13
|
+
* `apply` is straight CRUD: `createBrief` when the brief is absent,
|
|
14
|
+
* `updateBrief` (partial PUT) to converge an existing one. Repointing
|
|
15
|
+
* an existing brief at a different brief type is refused — the Brief
|
|
16
|
+
* API has no verified path for that.
|
|
17
|
+
*
|
|
18
|
+
* See docs/recipe-sync-architecture.md.
|
|
19
|
+
*/
|
|
20
|
+
const brief_1 = require("../../brief");
|
|
21
|
+
const errors_1 = require("../../shared/errors");
|
|
22
|
+
const client_1 = require("./client");
|
|
23
|
+
const instance_diff_1 = require("./instance-diff");
|
|
24
|
+
const instance_schema_1 = require("./instance-schema");
|
|
25
|
+
/**
|
|
26
|
+
* Find a brief by its display name, paging the list endpoint until a
|
|
27
|
+
* match is found or the cursor is exhausted. The Brief list endpoint
|
|
28
|
+
* supports no server-side name filter (see `ListBriefsQuery`), so the
|
|
29
|
+
* walk is unavoidable.
|
|
30
|
+
*/
|
|
31
|
+
const findBriefByName = async (client, name) => {
|
|
32
|
+
let cursor;
|
|
33
|
+
for (;;) {
|
|
34
|
+
const page = await (0, brief_1.listBriefs)(client, cursor ? { next: cursor } : undefined);
|
|
35
|
+
const match = page.data.find((brief) => brief.name === name);
|
|
36
|
+
if (match)
|
|
37
|
+
return match;
|
|
38
|
+
if (!page.next || page.data.length === 0)
|
|
39
|
+
return null;
|
|
40
|
+
cursor = page.next;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/** Find a brief type by its codename (mirrors `briefTypeKind`'s helper). */
|
|
44
|
+
const findTypeByName = async (client, name) => {
|
|
45
|
+
const page = await (0, brief_1.listBriefTypes)(client);
|
|
46
|
+
return page.data.find((type) => type.name === name) ?? null;
|
|
47
|
+
};
|
|
48
|
+
/** Enumerate every brief on the remote — fans out into the aggregate sync. */
|
|
49
|
+
const list = async (ctx) => {
|
|
50
|
+
const client = await (0, client_1.resolveBriefClient)(ctx);
|
|
51
|
+
const refs = [];
|
|
52
|
+
let cursor;
|
|
53
|
+
for (;;) {
|
|
54
|
+
const page = await (0, brief_1.listBriefs)(client, cursor ? { next: cursor } : undefined);
|
|
55
|
+
for (const brief of page.data) {
|
|
56
|
+
refs.push({ kind: "brief", id: brief.name });
|
|
57
|
+
}
|
|
58
|
+
if (!page.next || page.data.length === 0)
|
|
59
|
+
return refs;
|
|
60
|
+
cursor = page.next;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
/** Project a live brief into the recipe shape, dropping server ids/metadata. */
|
|
64
|
+
const toRecipe = (brief, briefTypeName) => ({
|
|
65
|
+
name: brief.name,
|
|
66
|
+
briefTypeName,
|
|
67
|
+
locale: brief.locale || undefined,
|
|
68
|
+
status: brief.status,
|
|
69
|
+
isTemplate: brief.isTemplate,
|
|
70
|
+
fields: brief.fields ?? {},
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* Capture a live brief as a recipe. `null` when no brief has the name.
|
|
74
|
+
*
|
|
75
|
+
* The list endpoint omits the full nested `fields` map on some
|
|
76
|
+
* envelopes, so the matched id is re-read via `getBrief` for fidelity.
|
|
77
|
+
*/
|
|
78
|
+
const readCurrent = async (ref, ctx) => {
|
|
79
|
+
const client = await (0, client_1.resolveBriefClient)(ctx);
|
|
80
|
+
const found = await findBriefByName(client, ref.id);
|
|
81
|
+
if (!found)
|
|
82
|
+
return null;
|
|
83
|
+
const brief = await (0, brief_1.getBrief)(client, found.id);
|
|
84
|
+
// Resolve the brief-type codename. The brief carries a `Link` to its
|
|
85
|
+
// type with the id but not the codename, so a follow-up listTypes
|
|
86
|
+
// resolves it. If the type has been deleted (orphan brief), surface
|
|
87
|
+
// the id verbatim so the pulled recipe still round-trips.
|
|
88
|
+
const types = await (0, brief_1.listBriefTypes)(client);
|
|
89
|
+
const briefTypeName = types.data.find((type) => type.id === brief.briefType.id)?.name ?? brief.briefType.id;
|
|
90
|
+
return toRecipe(brief, briefTypeName);
|
|
91
|
+
};
|
|
92
|
+
/** Resolve a recipe's `briefTypeName` to its server id, or fail with a hint. */
|
|
93
|
+
const resolveBriefTypeId = async (client, briefTypeName) => {
|
|
94
|
+
const type = await findTypeByName(client, briefTypeName);
|
|
95
|
+
if (!type) {
|
|
96
|
+
throw (0, errors_1.createScaiError)(`Brief type "${briefTypeName}" not found.`, "INPUT_INVALID", {
|
|
97
|
+
hint: "Push the brief-type recipe first, or check the `briefTypeName` codename with `scai ops brief types list`.",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return type.id;
|
|
101
|
+
};
|
|
102
|
+
/** Apply a plan — create the brief, or PUT-patch it onto the desired state. */
|
|
103
|
+
const apply = async (plan, ref, ctx) => {
|
|
104
|
+
const client = await (0, client_1.resolveBriefClient)(ctx);
|
|
105
|
+
const applied = [];
|
|
106
|
+
const skipped = [];
|
|
107
|
+
// The single `stage: "instance"` change carries the full desired
|
|
108
|
+
// recipe; per-element `stage: "field"` changes are descriptive only.
|
|
109
|
+
const instanceChange = plan.changes.find((change) => change.meta?.stage === "instance");
|
|
110
|
+
if (!instanceChange) {
|
|
111
|
+
// Nothing to write — an all-noop plan.
|
|
112
|
+
for (const change of plan.changes)
|
|
113
|
+
skipped.push(change);
|
|
114
|
+
return { applied, skipped };
|
|
115
|
+
}
|
|
116
|
+
const recipe = instanceChange.meta?.recipe;
|
|
117
|
+
if (!recipe) {
|
|
118
|
+
throw (0, errors_1.createScaiError)("Brief plan change is missing its recipe payload.", "INPUT_INVALID", {
|
|
119
|
+
hint: "This is an internal diff error — change.meta.recipe was not set.",
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if (instanceChange.kind === "create") {
|
|
123
|
+
const briefTypeId = await resolveBriefTypeId(client, recipe.briefTypeName);
|
|
124
|
+
const input = {
|
|
125
|
+
name: recipe.name,
|
|
126
|
+
briefTypeId,
|
|
127
|
+
...(recipe.locale !== undefined && { locale: recipe.locale }),
|
|
128
|
+
...(Object.keys(recipe.fields ?? {}).length > 0 && { fields: recipe.fields }),
|
|
129
|
+
...(recipe.isTemplate !== undefined && { isTemplate: recipe.isTemplate }),
|
|
130
|
+
};
|
|
131
|
+
ctx.logger?.info(`Creating brief "${recipe.name}".`);
|
|
132
|
+
const created = await (0, brief_1.createBrief)(client, input);
|
|
133
|
+
// `createBrief` accepts no `status` field — POSTs land in the
|
|
134
|
+
// server default ("Draft"). If the recipe pins a different status,
|
|
135
|
+
// converge with a follow-up PUT so the post-apply state matches.
|
|
136
|
+
if (recipe.status && recipe.status !== created.status) {
|
|
137
|
+
ctx.logger?.info(`Setting brief "${recipe.name}" status to "${recipe.status}".`);
|
|
138
|
+
await (0, brief_1.updateBrief)(client, created.id, { status: recipe.status });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const existing = await findBriefByName(client, ref.id);
|
|
143
|
+
if (!existing) {
|
|
144
|
+
throw (0, errors_1.createScaiError)(`Brief "${ref.id}" not found.`, "INPUT_INVALID", {
|
|
145
|
+
hint: "The brief was expected to exist for an update — check the name or push a create.",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// The Brief API has no verified path to repoint a brief at a
|
|
149
|
+
// different type. Surface that loudly instead of silently dropping
|
|
150
|
+
// the change.
|
|
151
|
+
const types = await (0, brief_1.listBriefTypes)(client);
|
|
152
|
+
const existingTypeName = types.data.find((type) => type.id === existing.briefType.id)?.name ?? existing.briefType.id;
|
|
153
|
+
if (existingTypeName !== recipe.briefTypeName) {
|
|
154
|
+
throw (0, errors_1.createScaiError)(`Cannot repoint brief "${recipe.name}" from type "${existingTypeName}" to "${recipe.briefTypeName}".`, "INPUT_INVALID", {
|
|
155
|
+
hint: "The Brief API has no verified path to change a brief's type. Delete and recreate the brief, or correct the recipe's briefTypeName.",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const patch = {
|
|
159
|
+
name: recipe.name,
|
|
160
|
+
...(recipe.locale !== undefined && { locale: recipe.locale }),
|
|
161
|
+
...(recipe.isTemplate !== undefined && { isTemplate: recipe.isTemplate }),
|
|
162
|
+
...(recipe.status !== undefined && { status: recipe.status }),
|
|
163
|
+
fields: recipe.fields ?? {},
|
|
164
|
+
};
|
|
165
|
+
ctx.logger?.info(`Updating brief "${recipe.name}" (${existing.id}).`);
|
|
166
|
+
await (0, brief_1.updateBrief)(client, existing.id, patch);
|
|
167
|
+
}
|
|
168
|
+
applied.push(instanceChange);
|
|
169
|
+
// Per-element changes are converged by the single PUT (or POST).
|
|
170
|
+
for (const change of plan.changes) {
|
|
171
|
+
if (change === instanceChange)
|
|
172
|
+
continue;
|
|
173
|
+
if (change.kind === "noop")
|
|
174
|
+
skipped.push(change);
|
|
175
|
+
else
|
|
176
|
+
applied.push(change);
|
|
177
|
+
}
|
|
178
|
+
return { applied, skipped };
|
|
179
|
+
};
|
|
180
|
+
/** Compute the plan to converge a brief onto `desired`. */
|
|
181
|
+
const plan = async (desired, ref, ctx) => (0, instance_diff_1.diffBriefInstance)(desired, await readCurrent(ref, ctx));
|
|
182
|
+
/** The `brief` recipe kind. */
|
|
183
|
+
exports.briefInstanceKind = {
|
|
184
|
+
name: "brief",
|
|
185
|
+
schema: instance_schema_1.BriefInstanceRecipeSchema,
|
|
186
|
+
readCurrent,
|
|
187
|
+
plan,
|
|
188
|
+
apply,
|
|
189
|
+
list,
|
|
190
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `BriefInstanceRecipe` — the declarative definition of a Sitecore
|
|
3
|
+
* Content Operations *brief instance* (the unit of work — a populated
|
|
4
|
+
* brief built against a `BriefType` schema).
|
|
5
|
+
*
|
|
6
|
+
* Companion to `BriefTypeRecipeSchema` (this file's sibling `schema.ts`,
|
|
7
|
+
* which models the schema template). Brief instances reference their
|
|
8
|
+
* type by stable codename (`briefTypeName`) rather than UUID, exactly as
|
|
9
|
+
* a `campaign` recipe references nothing by id and identifies itself by
|
|
10
|
+
* `name`. See docs/recipe-sync-architecture.md.
|
|
11
|
+
*
|
|
12
|
+
* The schema captures the fields `createBrief` / `updateBrief` accept
|
|
13
|
+
* on the Brief API — `name`, `briefTypeName`, `locale`, `status`,
|
|
14
|
+
* `isTemplate`, and the per-field `fields` map. Sub-resources (tasks,
|
|
15
|
+
* comments, references, contributors, external mappings) are returned
|
|
16
|
+
* by the read endpoints but are not part of the brief write surface,
|
|
17
|
+
* so they stay out of the recipe.
|
|
18
|
+
*/
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
/**
|
|
21
|
+
* Brief workflow status — the values the Brief API accepts on a write.
|
|
22
|
+
* Mirrors the `BriefStatus` wire type in `../api/schema.ts`; surfacing
|
|
23
|
+
* the literal set here means the model reads the enum directly off the
|
|
24
|
+
* recipe schema (and bad values fail at parse-time, not push-time).
|
|
25
|
+
*
|
|
26
|
+
* `InReview` is the wire form for the "In Review" UI label.
|
|
27
|
+
*/
|
|
28
|
+
export declare const BriefInstanceStatusSchema: z.ZodEnum<{
|
|
29
|
+
Draft: "Draft";
|
|
30
|
+
InReview: "InReview";
|
|
31
|
+
Approved: "Approved";
|
|
32
|
+
Canceled: "Canceled";
|
|
33
|
+
Archived: "Archived";
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Field values on a brief instance, keyed by `BriefField.name`. The
|
|
37
|
+
* per-field shape varies by field `type` on the brief's type (RichText
|
|
38
|
+
* values are ProseMirror docs; DateTime is an ISO string; Timeline and
|
|
39
|
+
* Budget carry their own object shape). The shape is left as
|
|
40
|
+
* `z.unknown()` so any field type round-trips losslessly between
|
|
41
|
+
* `recipe pull` and `recipe push` without the recipe schema chasing
|
|
42
|
+
* the per-field encoding. Validation is deferred to the server.
|
|
43
|
+
*/
|
|
44
|
+
export declare const BriefInstanceFieldsSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
45
|
+
/** The full brief-instance recipe. */
|
|
46
|
+
export declare const BriefInstanceRecipeSchema: z.ZodObject<{
|
|
47
|
+
name: z.ZodString;
|
|
48
|
+
briefTypeName: z.ZodString;
|
|
49
|
+
locale: z.ZodOptional<z.ZodString>;
|
|
50
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
51
|
+
Draft: "Draft";
|
|
52
|
+
InReview: "InReview";
|
|
53
|
+
Approved: "Approved";
|
|
54
|
+
Canceled: "Canceled";
|
|
55
|
+
Archived: "Archived";
|
|
56
|
+
}>>;
|
|
57
|
+
isTemplate: z.ZodOptional<z.ZodBoolean>;
|
|
58
|
+
fields: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
59
|
+
}, z.core.$strip>;
|
|
60
|
+
/** A validated brief-instance recipe. */
|
|
61
|
+
export type BriefInstanceRecipe = z.infer<typeof BriefInstanceRecipeSchema>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BriefInstanceRecipeSchema = exports.BriefInstanceFieldsSchema = exports.BriefInstanceStatusSchema = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* `BriefInstanceRecipe` — the declarative definition of a Sitecore
|
|
6
|
+
* Content Operations *brief instance* (the unit of work — a populated
|
|
7
|
+
* brief built against a `BriefType` schema).
|
|
8
|
+
*
|
|
9
|
+
* Companion to `BriefTypeRecipeSchema` (this file's sibling `schema.ts`,
|
|
10
|
+
* which models the schema template). Brief instances reference their
|
|
11
|
+
* type by stable codename (`briefTypeName`) rather than UUID, exactly as
|
|
12
|
+
* a `campaign` recipe references nothing by id and identifies itself by
|
|
13
|
+
* `name`. See docs/recipe-sync-architecture.md.
|
|
14
|
+
*
|
|
15
|
+
* The schema captures the fields `createBrief` / `updateBrief` accept
|
|
16
|
+
* on the Brief API — `name`, `briefTypeName`, `locale`, `status`,
|
|
17
|
+
* `isTemplate`, and the per-field `fields` map. Sub-resources (tasks,
|
|
18
|
+
* comments, references, contributors, external mappings) are returned
|
|
19
|
+
* by the read endpoints but are not part of the brief write surface,
|
|
20
|
+
* so they stay out of the recipe.
|
|
21
|
+
*/
|
|
22
|
+
const zod_1 = require("zod");
|
|
23
|
+
/**
|
|
24
|
+
* Brief workflow status — the values the Brief API accepts on a write.
|
|
25
|
+
* Mirrors the `BriefStatus` wire type in `../api/schema.ts`; surfacing
|
|
26
|
+
* the literal set here means the model reads the enum directly off the
|
|
27
|
+
* recipe schema (and bad values fail at parse-time, not push-time).
|
|
28
|
+
*
|
|
29
|
+
* `InReview` is the wire form for the "In Review" UI label.
|
|
30
|
+
*/
|
|
31
|
+
exports.BriefInstanceStatusSchema = zod_1.z
|
|
32
|
+
.enum(["Draft", "InReview", "Approved", "Canceled", "Archived"])
|
|
33
|
+
.describe('Brief workflow status. Wire form — "InReview" is the "In Review" UI label. A brief must leave "Draft" before it can be linked to a campaign.');
|
|
34
|
+
/**
|
|
35
|
+
* Field values on a brief instance, keyed by `BriefField.name`. The
|
|
36
|
+
* per-field shape varies by field `type` on the brief's type (RichText
|
|
37
|
+
* values are ProseMirror docs; DateTime is an ISO string; Timeline and
|
|
38
|
+
* Budget carry their own object shape). The shape is left as
|
|
39
|
+
* `z.unknown()` so any field type round-trips losslessly between
|
|
40
|
+
* `recipe pull` and `recipe push` without the recipe schema chasing
|
|
41
|
+
* the per-field encoding. Validation is deferred to the server.
|
|
42
|
+
*/
|
|
43
|
+
exports.BriefInstanceFieldsSchema = zod_1.z
|
|
44
|
+
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
45
|
+
.default({})
|
|
46
|
+
.describe("Field values keyed by BriefField.name. Per-field value shape follows the brief type's field definitions (e.g. RichText is a ProseMirror doc node).");
|
|
47
|
+
/** The full brief-instance recipe. */
|
|
48
|
+
exports.BriefInstanceRecipeSchema = zod_1.z.object({
|
|
49
|
+
name: zod_1.z
|
|
50
|
+
.string()
|
|
51
|
+
.min(1)
|
|
52
|
+
.describe("Display name of the brief. Identifies the brief when pushing. Briefs are matched by name; the recipe pushes through to `createBrief` / `updateBrief`."),
|
|
53
|
+
briefTypeName: zod_1.z
|
|
54
|
+
.string()
|
|
55
|
+
.min(1)
|
|
56
|
+
.regex(/^[A-Za-z][A-Za-z0-9_]*$/, "Must start with a letter and contain only letters, digits, or underscores.")
|
|
57
|
+
.describe("Codename of the brief type this brief is built against — looked up at push-time and resolved to its server id. The brief type must already exist (push the type's recipe first if it doesn't)."),
|
|
58
|
+
locale: zod_1.z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe('Brief locale — BCP-47-ish, e.g. "en-us". Omit to let the server default apply.'),
|
|
62
|
+
status: exports.BriefInstanceStatusSchema.optional().describe('Brief workflow status. Omit to leave at the server default ("Draft" on creation).'),
|
|
63
|
+
isTemplate: zod_1.z
|
|
64
|
+
.boolean()
|
|
65
|
+
.optional()
|
|
66
|
+
.describe("Whether the brief is a template (omit to leave at the server default)."),
|
|
67
|
+
fields: exports.BriefInstanceFieldsSchema,
|
|
68
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CreateBriefInput } from "../api/briefs";
|
|
1
2
|
import { type CreateBriefTypeInput } from "../api/brief-types";
|
|
2
3
|
import type { Brief, BriefComment, BriefStatus, BriefTask, BriefType, PagedResult } from "../api/schema";
|
|
3
4
|
/**
|
|
@@ -100,6 +101,40 @@ export declare const runBriefCommentsList: (options: RunBriefBaseOptions & {
|
|
|
100
101
|
briefId?: string;
|
|
101
102
|
limit?: number;
|
|
102
103
|
}) => Promise<PagedResult<BriefComment>>;
|
|
104
|
+
/**
|
|
105
|
+
* Create a brief instance from a `CreateBriefInput`. Mirrors
|
|
106
|
+
* `runBriefTypeCreate` — honours `whatIf` for a plan-only dry run. The
|
|
107
|
+
* SDK `createBrief` is verified against the Agents tenant.
|
|
108
|
+
*/
|
|
109
|
+
export declare const runBriefCreate: (options: RunBriefBaseOptions & {
|
|
110
|
+
input: CreateBriefInput;
|
|
111
|
+
whatIf?: boolean;
|
|
112
|
+
}) => Promise<Brief | {
|
|
113
|
+
plan: CreateBriefInput;
|
|
114
|
+
}>;
|
|
115
|
+
/**
|
|
116
|
+
* Update a brief instance by id with a partial patch (`PUT`). Accepts
|
|
117
|
+
* any subset of `CreateBriefInput` plus an optional `status`. Mirrors
|
|
118
|
+
* `runBriefTypeUpdate` — honours `whatIf` for a plan-only dry run. The
|
|
119
|
+
* status-only PUT path is verified (2026-05-15); other partial fields
|
|
120
|
+
* are wired the same way but not smoke-tested.
|
|
121
|
+
*/
|
|
122
|
+
export declare const runBriefUpdate: (options: RunBriefBaseOptions & {
|
|
123
|
+
briefId: string;
|
|
124
|
+
patch: Partial<CreateBriefInput> & {
|
|
125
|
+
status?: BriefStatus;
|
|
126
|
+
};
|
|
127
|
+
whatIf?: boolean;
|
|
128
|
+
}) => Promise<{
|
|
129
|
+
id: string;
|
|
130
|
+
} | {
|
|
131
|
+
plan: {
|
|
132
|
+
id: string;
|
|
133
|
+
patch: Partial<CreateBriefInput> & {
|
|
134
|
+
status?: BriefStatus;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
}>;
|
|
103
138
|
/**
|
|
104
139
|
* Delete a brief instance. Mirrors `runBriefTypeDelete` — honours
|
|
105
140
|
* `whatIf` for a plan-only dry run. SDK `deleteBrief` is verified
|