@sitecoreai-labs/sitecoreai-cli 0.1.2 → 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.
Files changed (65) hide show
  1. package/dist/agents/tasks/agent.js +2 -2
  2. package/dist/agents/tasks/resources.js +2 -2
  3. package/dist/agents/tasks/space.js +1 -1
  4. package/dist/brand/api/auth.d.ts +16 -9
  5. package/dist/brand/api/auth.js +29 -19
  6. package/dist/brand/credential.d.ts +71 -1
  7. package/dist/brand/credential.js +119 -2
  8. package/dist/brand/recipe/diff.js +7 -2
  9. package/dist/brand/recipe/kind.js +0 -0
  10. package/dist/brand/recipe/schema.d.ts +113 -7
  11. package/dist/brand/recipe/schema.js +137 -8
  12. package/dist/brand/seed.d.ts +9 -5
  13. package/dist/brand/seed.js +30 -5
  14. package/dist/brief/api/briefs.d.ts +8 -0
  15. package/dist/brief/api/briefs.js +49 -11
  16. package/dist/brief/index.d.ts +1 -1
  17. package/dist/brief/index.js +2 -1
  18. package/dist/brief/recipe/index.d.ts +11 -2
  19. package/dist/brief/recipe/index.js +17 -3
  20. package/dist/brief/recipe/instance-diff.d.ts +4 -0
  21. package/dist/brief/recipe/instance-diff.js +77 -0
  22. package/dist/brief/recipe/instance-kind.d.ts +4 -0
  23. package/dist/brief/recipe/instance-kind.js +190 -0
  24. package/dist/brief/recipe/instance-schema.d.ts +61 -0
  25. package/dist/brief/recipe/instance-schema.js +68 -0
  26. package/dist/brief/recipe/schema.js +4 -1
  27. package/dist/brief/tasks/index.d.ts +35 -0
  28. package/dist/brief/tasks/index.js +62 -1
  29. package/dist/campaigns/recipe/schema.d.ts +39 -8
  30. package/dist/campaigns/recipe/schema.js +40 -10
  31. package/dist/commands/agents/sync.js +2 -2
  32. package/dist/commands/brand/seed.js +1 -1
  33. package/dist/commands/brand/sync.js +2 -2
  34. package/dist/commands/brief/create.d.ts +2 -0
  35. package/dist/commands/brief/create.js +56 -0
  36. package/dist/commands/brief/index.d.ts +3 -1
  37. package/dist/commands/brief/index.js +11 -1
  38. package/dist/commands/brief/sync.d.ts +9 -6
  39. package/dist/commands/brief/sync.js +54 -22
  40. package/dist/commands/brief/update.d.ts +2 -0
  41. package/dist/commands/brief/update.js +84 -0
  42. package/dist/commands/campaign/sync.js +2 -2
  43. package/dist/mcp/descriptions.js +3 -3
  44. package/dist/mcp/tools/brief-recipe.js +67 -23
  45. package/dist/mcp/tools/brief.js +83 -6
  46. package/dist/recipe/compile/design-parameters-template.js +5 -3
  47. package/dist/recipe/compile/enumeration.js +6 -11
  48. package/dist/recipe/compile/shared.js +12 -12
  49. package/dist/recipe/compile.js +4 -4
  50. package/dist/recipe/io.d.ts +8 -3
  51. package/dist/recipe/io.js +11 -81
  52. package/dist/recipe/items/read-current.js +31 -24
  53. package/dist/recipe/schema/recipe.d.ts +167 -84
  54. package/dist/recipe/schema/recipe.js +130 -46
  55. package/dist/recipe/schema/source-fields.d.ts +20 -0
  56. package/dist/recipe/schema/source-fields.js +25 -1
  57. package/dist/recipe/validate.d.ts +3 -3
  58. package/dist/recipe/validate.js +20 -10
  59. package/dist/sync/aggregate-kinds.js +1 -0
  60. package/dist/sync/aggregate.js +2 -2
  61. package/dist/sync/io.d.ts +13 -3
  62. package/dist/sync/io.js +43 -17
  63. package/dist/sync/typescript-recipe.d.ts +30 -0
  64. package/dist/sync/typescript-recipe.js +112 -0
  65. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BrandKitRecipeSchema = exports.BrandDocumentSchema = exports.BrandFieldValueSchema = exports.BrandRichEntrySchema = void 0;
3
+ exports.BrandKitRecipeSchema = exports.BrandDocumentSchema = exports.BrandRegistryFileDocumentSchema = exports.BrandUrlDocumentSchema = exports.BRAND_KIT_CANONICAL_SECTIONS = exports.BrandFieldValueSchema = exports.BrandRichEntrySchema = void 0;
4
4
  /**
5
5
  * `BrandKitRecipe` — the declarative definition of a Sitecore Brand
6
6
  * brand kit.
@@ -8,9 +8,26 @@ exports.BrandKitRecipeSchema = exports.BrandDocumentSchema = exports.BrandFieldV
8
8
  * This schema is the single source of truth for the `brand-kit` recipe
9
9
  * kind: it validates recipe files, drives the `sync` CLI, and becomes
10
10
  * the MCP tool input schema. Keep the `.describe()` text accurate — the
11
- * model reads it. See docs/recipe-sync-architecture.md.
11
+ * model reads it.
12
+ *
13
+ * Two authoring shapes share this schema:
14
+ *
15
+ * - **scai-native** — flat object with `name`, `documents`, `sections`.
16
+ * No `kind` / `schemaVersion` / `handle`. Used by hand-authored
17
+ * `.brandkit.yaml` files and the `scai brand sync pull` capture
18
+ * path. Stays valid: the discriminator fields are optional here.
19
+ * - **registry-superset** — the richer shape the `@registry`
20
+ * `sitecore-recipes.ts` exports use: adds `kind: "brandkit"`,
21
+ * `schemaVersion: "1"`, `handle`, `displayName`, and a
22
+ * discriminated `documents[]` shape (`url` | `registry-file`)
23
+ * with `tags` / `sections` hints per document. The orchestrator
24
+ * passes recipes through unchanged from the registry to scai;
25
+ * scai's parser accepts the extra fields without stripping them.
26
+ *
27
+ * See docs/recipe-sync-architecture.md.
12
28
  */
13
29
  const zod_1 = require("zod");
30
+ const HANDLE_PATTERN = /^[a-z][a-z0-9-]*@\d+$/;
14
31
  /** A `richArray`-field entry: text plus optional tags and a constraint. */
15
32
  exports.BrandRichEntrySchema = zod_1.z.object({
16
33
  name: zod_1.z.string().min(1).describe("The entry's text."),
@@ -31,29 +48,141 @@ exports.BrandRichEntrySchema = zod_1.z.object({
31
48
  exports.BrandFieldValueSchema = zod_1.z
32
49
  .union([zod_1.z.string(), zod_1.z.array(zod_1.z.string()), zod_1.z.array(exports.BrandRichEntrySchema)])
33
50
  .describe("A text value, a list of names, or a list of rich entries.");
34
- /** A brand document to ingest. Sitecore fetches the URL server-side. */
35
- exports.BrandDocumentSchema = zod_1.z.object({
51
+ /**
52
+ * Canonical Sitecore AI brand-kit section names — the seven buckets
53
+ * the EnrichSections pipeline produces. Mirrors
54
+ * `BRAND_KIT_CANONICAL_SECTIONS` in the registry's recipe definitions.
55
+ * Exported so callers building recipes have a stable list to bias
56
+ * `documents[].sections` against.
57
+ */
58
+ exports.BRAND_KIT_CANONICAL_SECTIONS = [
59
+ "Brand Context",
60
+ "Global Goals",
61
+ "Tone of Voice",
62
+ "Glossary and Localization",
63
+ "Do's and Don'ts",
64
+ "Grammar Checklists",
65
+ "Visual Guidelines",
66
+ ];
67
+ /**
68
+ * Optional ingestion hints — `tags` and `sections` — shared by both
69
+ * document variants. Hoisted into a shape so the URL and registry-file
70
+ * shapes stay in lockstep without duplicating field-by-field.
71
+ */
72
+ const DocumentHints = {
73
+ title: zod_1.z.string().optional().describe('Document title. Defaults to "brand guidelines".'),
74
+ summary: zod_1.z.string().optional().describe("Short document summary."),
75
+ tags: zod_1.z
76
+ .array(zod_1.z.string())
77
+ .optional()
78
+ .describe('Free-form tags surfaced to the EnrichSections pipeline (e.g. "voice").'),
79
+ sections: zod_1.z
80
+ .array(zod_1.z.string())
81
+ .optional()
82
+ .describe('Canonical section names to bias ingestion toward (e.g. ["Tone of Voice"]). Prefer values from BRAND_KIT_CANONICAL_SECTIONS.'),
83
+ };
84
+ /**
85
+ * A brand document referenced by URL. Sitecore's Documents API fetches
86
+ * the URL server-side and copies the bytes into MMS. The default
87
+ * variant — what `scai brand sync pull` emits when capturing a live
88
+ * kit.
89
+ */
90
+ exports.BrandUrlDocumentSchema = zod_1.z.object({
91
+ kind: zod_1.z.literal("url"),
36
92
  url: zod_1.z
37
93
  .string()
38
94
  .min(1)
39
95
  .describe("Publicly reachable URL of a brand document (PDF). Sitecore fetches it server-side."),
40
- title: zod_1.z.string().optional().describe('Document title. Defaults to "brand guidelines".'),
41
- summary: zod_1.z.string().optional().describe("Short document summary."),
96
+ ...DocumentHints,
42
97
  });
98
+ /**
99
+ * A brand document stored alongside the recipe in the registry repo.
100
+ * The `path` is relative to the recipe file's directory. scai itself
101
+ * does **not** upload these — the Sitecore Documents API has no
102
+ * working bytes-upload path (see
103
+ * `src/brand/documents/upload.ts::LOCAL_UPLOAD_UNSUPPORTED_MESSAGE`),
104
+ * so the orchestrator (or whoever owns the recipe before scai sees
105
+ * it) MUST translate `registry-file` entries to `url` entries
106
+ * pointing at an HTTP-reachable host before invoking `scai brand
107
+ * sync push`. The seed runner rejects unresolved `registry-file`
108
+ * documents with a clear hint; this stays in the schema as an
109
+ * authoring-time shape so registry-side recipes round-trip cleanly.
110
+ */
111
+ exports.BrandRegistryFileDocumentSchema = zod_1.z.object({
112
+ kind: zod_1.z.literal("registry-file"),
113
+ path: zod_1.z.string().min(1).describe("Path to the document, relative to the recipe file."),
114
+ ...DocumentHints,
115
+ });
116
+ /**
117
+ * A brand document to ingest. Two variants — `{ kind: "url", url }` and
118
+ * `{ kind: "registry-file", path }` — sharing optional `title` /
119
+ * `summary` / `tags` / `sections` ingestion hints.
120
+ *
121
+ * Back-compat: scai-native recipes that pre-date the discriminator wrote
122
+ * documents as the flat `{ url, title?, summary? }` shape. The
123
+ * `z.preprocess` step defaults a missing `kind` to `"url"` whenever
124
+ * `url` is present, so legacy recipes keep parsing without a migration
125
+ * step. Zod 4's discriminated unions require the discriminator to be
126
+ * present BEFORE routing, so `.default("url")` inline on the literal
127
+ * doesn't work — preprocess is the correct seam.
128
+ */
129
+ exports.BrandDocumentSchema = zod_1.z.preprocess((raw) => {
130
+ if (raw !== null &&
131
+ typeof raw === "object" &&
132
+ !Array.isArray(raw) &&
133
+ !("kind" in raw) &&
134
+ "url" in raw) {
135
+ return { kind: "url", ...raw };
136
+ }
137
+ return raw;
138
+ }, zod_1.z.discriminatedUnion("kind", [exports.BrandUrlDocumentSchema, exports.BrandRegistryFileDocumentSchema]));
43
139
  /** The full brand-kit recipe. */
44
140
  exports.BrandKitRecipeSchema = zod_1.z.object({
141
+ /**
142
+ * Registry-superset discriminator — `"brandkit"`. Optional: scai-
143
+ * native YAML/JSON recipes pre-date this field and don't carry it.
144
+ * Defaulting in (rather than requiring) keeps the legacy parse
145
+ * path intact. Registry-authored recipes set this explicitly.
146
+ */
147
+ kind: zod_1.z.literal("brandkit").optional(),
148
+ /**
149
+ * Registry-superset schema version pin — `"1"`. Optional for the
150
+ * same back-compat reason as `kind`. When set, the registry's
151
+ * discriminated-union loader picks this recipe up.
152
+ */
153
+ schemaVersion: zod_1.z.literal("1").optional(),
154
+ /**
155
+ * Stable handle of the form `<kebab-name>@<major>` (e.g.
156
+ * `acme-brand@1`). Registry-side identifier used to wire brand
157
+ * kits to themes. Optional in scai because scai-native recipes
158
+ * identify kits by `name`, not handle.
159
+ */
160
+ handle: zod_1.z
161
+ .string()
162
+ .regex(HANDLE_PATTERN, {
163
+ message: "handle must match `<kebab-name>@<major>` (e.g. acme-brand@1)",
164
+ })
165
+ .optional()
166
+ .describe("Stable handle `<kebab-name>@<major>`. Required for registry-authored recipes."),
45
167
  name: zod_1.z
46
168
  .string()
47
169
  .min(1)
48
170
  .describe("Display name of the brand kit. Identifies the kit when pushing."),
171
+ /**
172
+ * Author-facing label, mirrors the registry's superset. When absent
173
+ * the registry's loader treats it as identical to `name`; scai's
174
+ * `apply()` likewise ignores it (the API has no separate display
175
+ * name slot beyond `name`).
176
+ */
177
+ displayName: zod_1.z.string().min(1).optional().describe("Author-facing label; defaults to `name`."),
49
178
  description: zod_1.z.string().optional().describe("Human description of the kit."),
50
179
  industry: zod_1.z.string().optional().describe('Industry label, e.g. "retail" or "developer-tools".'),
51
180
  documents: zod_1.z
52
181
  .array(exports.BrandDocumentSchema)
53
182
  .default([])
54
- .describe("Brand documents to ingest. On push, when the kit has no populated sections, each is uploaded and run through the ingestion + enrichment pipelines."),
183
+ .describe("Brand documents to ingest. On push, when the kit has no populated sections, each is uploaded and run through the ingestion + enrichment pipelines. `registry-file` variants must be translated to `url` variants before scai sees the recipe — the Sitecore Documents API has no working bytes-upload path."),
55
184
  sections: zod_1.z
56
185
  .record(zod_1.z.string(), zod_1.z.record(zod_1.z.string(), exports.BrandFieldValueSchema))
57
186
  .default({})
58
- .describe("Desired field values, keyed by section name then field name. Sections and fields are created by enrichment; push converges their values."),
187
+ .describe("Desired field values, keyed by section name then field name. Prefer canonical names from BRAND_KIT_CANONICAL_SECTIONS for the outer key. Sections and fields are created by enrichment; push converges their values."),
59
188
  });
@@ -2,6 +2,7 @@ import type { BrandApiClientOptions } from "./api/client";
2
2
  import { type BrandKitSectionSummary } from "./kits/sections";
3
3
  import { type UploadDocumentSource, type UploadedDocument } from "./documents/upload";
4
4
  import type { BrandKitSummary } from "./kits/list";
5
+ import type { BrandDocument } from "./recipe/schema";
5
6
  /**
6
7
  * Stage labels emitted via `onProgress`. Useful for callers that
7
8
  * surface progress (CLI streaming output, MCP progress events).
@@ -30,12 +31,15 @@ export interface SeedBrandKitOptions {
30
31
  /**
31
32
  * Multiple source documents — uploaded before a single ingestion +
32
33
  * enrichment pass. Takes precedence over `source` when non-empty.
34
+ * Accepts the recipe-shaped `BrandDocument` union (URL or
35
+ * registry-file). The seed runner rejects `registry-file` entries
36
+ * with `INPUT_INVALID` and a clear hint — see the
37
+ * `LOCAL_UPLOAD_UNSUPPORTED_MESSAGE` rationale in
38
+ * `documents/upload.ts` for why bytes uploads don't work end-to-end.
39
+ * Callers (orchestrator's `brandkit_deploy` handler) must translate
40
+ * registry-file paths to public URLs before invoking scai.
33
41
  */
34
- documents?: {
35
- url: string;
36
- title?: string;
37
- summary?: string;
38
- }[];
42
+ documents?: BrandDocument[];
39
43
  /** Optional kit metadata. */
40
44
  description?: string;
41
45
  industry?: string;
@@ -49,12 +49,37 @@ const seedBrandKit = async (options) => {
49
49
  const kit = await (0, create_1.createBrandKit)(createInput);
50
50
  emit({ stage: "createKit", message: `Created kit ${kit.id}` });
51
51
  // 2. UPLOAD doc(s) — one or many, before a single ingestion pass.
52
+ //
53
+ // The `documents` list is the recipe-shaped discriminated union.
54
+ // `registry-file` entries arrive unresolved (path relative to the
55
+ // recipe file) and CANNOT be uploaded as bytes — the Sitecore
56
+ // Documents API has no working local-upload path (verified
57
+ // 2026-05-15; see documents/upload.ts). The caller (orchestrator's
58
+ // `brandkit_deploy` handler) must translate them to URL entries
59
+ // first. Failing fast here — with the path in the hint — beats
60
+ // a confusing 400 from `uploadDocument` minutes later.
61
+ if (options.documents) {
62
+ for (const doc of options.documents) {
63
+ if (doc.kind === "registry-file") {
64
+ throw (0, errors_1.createScaiError)(`Brand document "${doc.path}" is a registry-file path; scai cannot upload local bytes.`, "INPUT_INVALID", {
65
+ hint: 'Host the file at an HTTPS URL Sitecore\'s edge can reach (S3, GitHub raw, a CDN) and pass it as a `{ kind: "url", url }` document. The Sitecore Documents API rejects local-file uploads — translation has to happen upstream of scai.',
66
+ });
67
+ }
68
+ }
69
+ }
52
70
  const uploadList = options.documents && options.documents.length > 0
53
- ? options.documents.map((doc) => ({
54
- source: { url: doc.url },
55
- title: doc.title,
56
- summary: doc.summary,
57
- }))
71
+ ? options.documents.map((doc) => {
72
+ // After the registry-file guard above, every doc is a URL
73
+ // variant; the narrow keeps the TS surface clean.
74
+ if (doc.kind !== "url") {
75
+ throw (0, errors_1.createScaiError)("Unreachable: non-URL document survived the registry-file guard.", "INPUT_INVALID");
76
+ }
77
+ return {
78
+ source: { url: doc.url },
79
+ title: doc.title,
80
+ summary: doc.summary,
81
+ };
82
+ })
58
83
  : options.source
59
84
  ? [
60
85
  {
@@ -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
  /**
@@ -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) => (0, request_1.briefRequest)(options, "/api/brief/v1/briefs", {
24
- method: "POST",
25
- body: {
26
- name: input.name,
27
- briefTypeId: input.briefTypeId,
28
- locale: input.locale,
29
- fields: input.fields,
30
- isTemplate: input.isTemplate,
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
@@ -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";
@@ -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 `brief-type` recipe kind — declarative definition + `sync` support
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 `brief-type` recipe kind — declarative definition + `sync` support
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,4 @@
1
+ import type { RecipeKind } from "../../sync";
2
+ import { type BriefInstanceRecipe } from "./instance-schema";
3
+ /** The `brief` recipe kind. */
4
+ export declare const briefInstanceKind: RecipeKind<BriefInstanceRecipe>;