@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.
- package/dist/agents/tasks/agent.js +2 -2
- package/dist/agents/tasks/resources.js +2 -2
- package/dist/agents/tasks/space.js +1 -1
- package/dist/brand/api/auth.d.ts +16 -9
- package/dist/brand/api/auth.js +29 -19
- package/dist/brand/credential.d.ts +71 -1
- package/dist/brand/credential.js +119 -2
- package/dist/brand/recipe/diff.js +7 -2
- package/dist/brand/recipe/kind.js +0 -0
- package/dist/brand/recipe/schema.d.ts +113 -7
- package/dist/brand/recipe/schema.js +137 -8
- package/dist/brand/seed.d.ts +9 -5
- package/dist/brand/seed.js +30 -5
- 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/recipe/schema.js +4 -1
- package/dist/brief/tasks/index.d.ts +35 -0
- package/dist/brief/tasks/index.js +62 -1
- package/dist/campaigns/recipe/schema.d.ts +39 -8
- package/dist/campaigns/recipe/schema.js +40 -10
- package/dist/commands/agents/sync.js +2 -2
- package/dist/commands/brand/seed.js +1 -1
- package/dist/commands/brand/sync.js +2 -2
- 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/commands/campaign/sync.js +2 -2
- 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/recipe/compile/design-parameters-template.js +5 -3
- package/dist/recipe/compile/enumeration.js +6 -11
- package/dist/recipe/compile/shared.js +12 -12
- package/dist/recipe/compile.js +4 -4
- package/dist/recipe/io.d.ts +8 -3
- package/dist/recipe/io.js +11 -81
- package/dist/recipe/items/read-current.js +31 -24
- package/dist/recipe/schema/recipe.d.ts +167 -84
- package/dist/recipe/schema/recipe.js +130 -46
- package/dist/recipe/schema/source-fields.d.ts +20 -0
- package/dist/recipe/schema/source-fields.js +25 -1
- package/dist/recipe/validate.d.ts +3 -3
- package/dist/recipe/validate.js +20 -10
- package/dist/sync/aggregate-kinds.js +1 -0
- package/dist/sync/aggregate.js +2 -2
- package/dist/sync/io.d.ts +13 -3
- package/dist/sync/io.js +43 -17
- package/dist/sync/typescript-recipe.d.ts +30 -0
- package/dist/sync/typescript-recipe.js +112 -0
- package/package.json +1 -1
|
@@ -77,7 +77,7 @@ const createDiffCommand = () => {
|
|
|
77
77
|
command.action(async (options) => {
|
|
78
78
|
const logger = (0, cli_tasks_1.toLogger)(options);
|
|
79
79
|
const ctx = buildContext(options, logger);
|
|
80
|
-
const recipe = (0, sync_1.loadRecipe)(options.file ?? "", recipe_1.brandKitKind.schema);
|
|
80
|
+
const recipe = await (0, sync_1.loadRecipe)(options.file ?? "", recipe_1.brandKitKind.schema);
|
|
81
81
|
const plan = await (0, sync_1.syncDiff)(recipe_1.brandKitKind, recipe, { kind: recipe_1.brandKitKind.name, id: recipe.name }, ctx);
|
|
82
82
|
printPlan(logger, plan);
|
|
83
83
|
if (hasPaidPipeline(plan)) {
|
|
@@ -98,7 +98,7 @@ const createPushCommand = () => {
|
|
|
98
98
|
command.action(async (options) => {
|
|
99
99
|
const logger = (0, cli_tasks_1.toLogger)(options);
|
|
100
100
|
const ctx = buildContext(options, logger);
|
|
101
|
-
const recipe = (0, sync_1.loadRecipe)(options.file ?? "", recipe_1.brandKitKind.schema);
|
|
101
|
+
const recipe = await (0, sync_1.loadRecipe)(options.file ?? "", recipe_1.brandKitKind.schema);
|
|
102
102
|
const mode = options.allowWrite ? "apply" : "what-if";
|
|
103
103
|
const outcome = await (0, sync_1.syncPush)(recipe_1.brandKitKind, recipe, { kind: recipe_1.brandKitKind.name, id: recipe.name }, ctx, { mode, prune: options.prune });
|
|
104
104
|
printPlan(logger, outcome.plan);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createBriefCreateCommand = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const brief_1 = require("../../brief");
|
|
10
|
+
const tasks_1 = require("../../brief/tasks");
|
|
11
|
+
const cli_tasks_1 = require("../../shared/cli-tasks");
|
|
12
|
+
const shared_1 = require("../shared");
|
|
13
|
+
/**
|
|
14
|
+
* `scai ops brief create` — create a brief instance from a JSON
|
|
15
|
+
* document matching `CreateBriefInput`. Mirrors `brief types create`:
|
|
16
|
+
* imperative one-shot, dry-runs by default, --apply to write.
|
|
17
|
+
*
|
|
18
|
+
* The JSON body matches the raw API shape:
|
|
19
|
+
* {
|
|
20
|
+
* "name": "Q3 Launch Brief",
|
|
21
|
+
* "briefTypeId": "<uuid-from `brief types list`>",
|
|
22
|
+
* "locale": "en-us", // optional
|
|
23
|
+
* "fields": { ... }, // optional, per-field shapes vary by brief type
|
|
24
|
+
* "isTemplate": false // optional
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* For a declarative (codename-based, idempotent) push, use
|
|
28
|
+
* `scai ops brief sync push --kind brief --file <recipe>` instead.
|
|
29
|
+
*/
|
|
30
|
+
const readJsonFile = (path) => {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(node_fs_1.default.readFileSync(path, "utf8"));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw (0, cli_tasks_1.inputError)(`Could not read JSON from ${path}: ${error instanceof Error ? error.message : String(error)}`, "Pass --file <path> pointing at a valid CreateBriefInput JSON document.");
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const createBriefCreateCommand = () => {
|
|
39
|
+
const command = new commander_1.Command("create")
|
|
40
|
+
.description("Create a brief instance from a CreateBriefInput JSON document. For declarative + idempotent pushes, use `brief sync push --kind brief`.")
|
|
41
|
+
.addOption(new commander_1.Option("-f, --file <path>", "Path to a JSON file matching CreateBriefInput (name + briefTypeId, plus optional locale/fields/isTemplate).").makeOptionMandatory(true));
|
|
42
|
+
(0, shared_1.addOrgScopeOptions)(command);
|
|
43
|
+
(0, shared_1.addConfigOption)(command);
|
|
44
|
+
(0, shared_1.addVerbosityOptions)(command);
|
|
45
|
+
(0, shared_1.addApplyOption)(command);
|
|
46
|
+
(0, shared_1.addWhatIfOption)(command);
|
|
47
|
+
command.action((0, shared_1.withApplyGate)(async (options) => {
|
|
48
|
+
const input = (0, brief_1.assertCreateBriefInput)(readJsonFile(options.file));
|
|
49
|
+
await (0, tasks_1.runBriefCreate)({ ...options, input });
|
|
50
|
+
}));
|
|
51
|
+
command.addHelpText("after", "\nExamples:\n" +
|
|
52
|
+
" $ scai ops brief create -f ./brief.json -n agents --apply\n" +
|
|
53
|
+
" $ scai ops brief create -f ./brief.json -n agents # dry run\n");
|
|
54
|
+
return command;
|
|
55
|
+
};
|
|
56
|
+
exports.createBriefCreateCommand = createBriefCreateCommand;
|
|
@@ -6,10 +6,12 @@ import { Command } from "commander";
|
|
|
6
6
|
* Surface:
|
|
7
7
|
* - `scai ops brief list` — list briefs in the tenant
|
|
8
8
|
* - `scai ops brief show <briefId>` — read one brief in detail
|
|
9
|
+
* - `scai ops brief create -f <file>` — create a brief from CreateBriefInput JSON
|
|
10
|
+
* - `scai ops brief update <briefId>` — partial-PUT update (file or --status)
|
|
9
11
|
* - `scai ops brief set-status <briefId> <status>` — move a brief's workflow status
|
|
10
12
|
* - `scai ops brief delete <briefId>` — delete a brief
|
|
11
13
|
* - `scai ops brief types {list,get,create,update,delete}` — brief type CRUD
|
|
12
|
-
* - `scai ops brief sync {pull,diff,push}
|
|
14
|
+
* - `scai ops brief sync {pull,diff,push} [--kind brief|brief-type]` — recipe sync
|
|
13
15
|
* - `scai ops brief todos [briefId]` — list to-dos
|
|
14
16
|
* - `scai ops brief comments {list,add}` — list / post comments
|
|
15
17
|
*
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createBriefCommand = void 0;
|
|
4
4
|
const commander_1 = require("commander");
|
|
5
|
+
const create_1 = require("./create");
|
|
5
6
|
const list_1 = require("./list");
|
|
6
7
|
const show_1 = require("./show");
|
|
7
8
|
const set_status_1 = require("./set-status");
|
|
@@ -10,6 +11,7 @@ const todos_1 = require("./todos");
|
|
|
10
11
|
const comments_1 = require("./comments");
|
|
11
12
|
const delete_1 = require("./delete");
|
|
12
13
|
const sync_1 = require("./sync");
|
|
14
|
+
const update_1 = require("./update");
|
|
13
15
|
/**
|
|
14
16
|
* `scai ops brief …` command family — Sitecore Content Operations Brief
|
|
15
17
|
* API (`co-brief-api-<region>.sitecorecloud.io`).
|
|
@@ -17,10 +19,12 @@ const sync_1 = require("./sync");
|
|
|
17
19
|
* Surface:
|
|
18
20
|
* - `scai ops brief list` — list briefs in the tenant
|
|
19
21
|
* - `scai ops brief show <briefId>` — read one brief in detail
|
|
22
|
+
* - `scai ops brief create -f <file>` — create a brief from CreateBriefInput JSON
|
|
23
|
+
* - `scai ops brief update <briefId>` — partial-PUT update (file or --status)
|
|
20
24
|
* - `scai ops brief set-status <briefId> <status>` — move a brief's workflow status
|
|
21
25
|
* - `scai ops brief delete <briefId>` — delete a brief
|
|
22
26
|
* - `scai ops brief types {list,get,create,update,delete}` — brief type CRUD
|
|
23
|
-
* - `scai ops brief sync {pull,diff,push}
|
|
27
|
+
* - `scai ops brief sync {pull,diff,push} [--kind brief|brief-type]` — recipe sync
|
|
24
28
|
* - `scai ops brief todos [briefId]` — list to-dos
|
|
25
29
|
* - `scai ops brief comments {list,add}` — list / post comments
|
|
26
30
|
*
|
|
@@ -31,6 +35,8 @@ const createBriefCommand = () => {
|
|
|
31
35
|
const command = new commander_1.Command("brief").description("Briefs, brief types, to-dos, and comments on the Sitecore Content Operations Brief API.");
|
|
32
36
|
command.addCommand((0, list_1.createBriefListCommand)());
|
|
33
37
|
command.addCommand((0, show_1.createBriefShowCommand)());
|
|
38
|
+
command.addCommand((0, create_1.createBriefCreateCommand)());
|
|
39
|
+
command.addCommand((0, update_1.createBriefUpdateCommand)());
|
|
34
40
|
command.addCommand((0, set_status_1.createBriefSetStatusCommand)());
|
|
35
41
|
command.addCommand((0, delete_1.createBriefDeleteCommand)());
|
|
36
42
|
command.addCommand((0, types_1.createBriefTypesCommand)());
|
|
@@ -40,11 +46,15 @@ const createBriefCommand = () => {
|
|
|
40
46
|
command.addHelpText("after", "\nExamples:\n" +
|
|
41
47
|
" $ scai ops brief list -n agents # list briefs\n" +
|
|
42
48
|
" $ scai ops brief show <briefId> -n agents # read one brief\n" +
|
|
49
|
+
" $ scai ops brief create -f b.json -n agents --apply # create a brief (raw API shape)\n" +
|
|
50
|
+
" $ scai ops brief update <id> --status Approved --apply # status-only patch\n" +
|
|
43
51
|
" $ scai ops brief set-status <briefId> Approved --apply # move out of Draft\n" +
|
|
44
52
|
" $ scai ops brief delete <briefId> --apply --force # delete a brief\n" +
|
|
45
53
|
" $ scai ops brief types list -n agents # list brief schemas\n" +
|
|
46
54
|
" $ scai ops brief types create -f t.json --apply # create a new schema\n" +
|
|
47
55
|
" $ scai ops brief sync pull --name CreativeBrief # capture a type as a recipe\n" +
|
|
56
|
+
" $ scai ops brief sync pull --kind brief --name MyBrief # capture a brief as a recipe\n" +
|
|
57
|
+
" $ scai ops brief sync push --kind brief -f b.yaml --allow-write # converge a brief\n" +
|
|
48
58
|
" $ scai ops brief todos <briefId> --assignees # to-dos on a brief, with assignees\n" +
|
|
49
59
|
" $ scai ops brief comments list <briefId> # comments on a brief\n" +
|
|
50
60
|
' $ scai ops brief comments add <briefId> --text "…" --apply # post a comment\n');
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `scai ops brief sync` — pull, diff, and push a brief type
|
|
3
|
-
* declarative recipe.
|
|
4
|
-
* docs/recipe-sync-architecture.md.
|
|
2
|
+
* `scai ops brief sync` — pull, diff, and push a brief type OR a brief
|
|
3
|
+
* instance as a declarative recipe. See docs/recipe-sync-architecture.md.
|
|
5
4
|
*
|
|
6
|
-
* pull capture a live brief type
|
|
7
|
-
* diff show the plan to converge a brief type onto a recipe
|
|
5
|
+
* pull capture a live brief type or brief as a recipe file
|
|
6
|
+
* diff show the plan to converge a brief type or brief onto a recipe
|
|
8
7
|
* push apply that plan (dry-run unless --allow-write)
|
|
8
|
+
*
|
|
9
|
+
* Both verbs default to `--kind brief-type` for back-compat with the
|
|
10
|
+
* pre-instance surface — the same flag distinguishes `briefTypeKind`
|
|
11
|
+
* (the schema template) from `briefInstanceKind` (a populated brief).
|
|
9
12
|
*/
|
|
10
13
|
import { Command } from "commander";
|
|
11
|
-
/** `scai ops brief sync` — the recipe pull / diff / push verbs
|
|
14
|
+
/** `scai ops brief sync` — the recipe pull / diff / push verbs. */
|
|
12
15
|
export declare const createBriefSyncCommand: () => Command;
|
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createBriefSyncCommand = void 0;
|
|
4
4
|
/**
|
|
5
|
-
* `scai ops brief sync` — pull, diff, and push a brief type
|
|
6
|
-
* declarative recipe.
|
|
7
|
-
* docs/recipe-sync-architecture.md.
|
|
5
|
+
* `scai ops brief sync` — pull, diff, and push a brief type OR a brief
|
|
6
|
+
* instance as a declarative recipe. See docs/recipe-sync-architecture.md.
|
|
8
7
|
*
|
|
9
|
-
* pull capture a live brief type
|
|
10
|
-
* diff show the plan to converge a brief type onto a recipe
|
|
8
|
+
* pull capture a live brief type or brief as a recipe file
|
|
9
|
+
* diff show the plan to converge a brief type or brief onto a recipe
|
|
11
10
|
* push apply that plan (dry-run unless --allow-write)
|
|
11
|
+
*
|
|
12
|
+
* Both verbs default to `--kind brief-type` for back-compat with the
|
|
13
|
+
* pre-instance surface — the same flag distinguishes `briefTypeKind`
|
|
14
|
+
* (the schema template) from `briefInstanceKind` (a populated brief).
|
|
12
15
|
*/
|
|
13
16
|
const commander_1 = require("commander");
|
|
14
17
|
const shared_1 = require("../shared");
|
|
@@ -16,11 +19,28 @@ const recipe_1 = require("../../brief/recipe");
|
|
|
16
19
|
const root_config_1 = require("../../config/root-config");
|
|
17
20
|
const cli_tasks_1 = require("../../shared/cli-tasks");
|
|
18
21
|
const sync_1 = require("../../sync");
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
const BRIEF_SYNC_KINDS = ["brief-type", "brief"];
|
|
23
|
+
/** Slugify a recipe name for a default filename. */
|
|
24
|
+
const slug = (value, fallback) => value
|
|
21
25
|
.toLowerCase()
|
|
22
26
|
.replace(/[^a-z0-9]+/g, "-")
|
|
23
|
-
.replace(/^-+|-+$/g, "") ||
|
|
27
|
+
.replace(/^-+|-+$/g, "") || fallback;
|
|
28
|
+
/** Per-kind metadata — wires the discriminator to the right kind + filename suffix. */
|
|
29
|
+
const kindFor = (kind) => {
|
|
30
|
+
const resolved = kind ?? "brief-type";
|
|
31
|
+
if (resolved === "brief") {
|
|
32
|
+
return {
|
|
33
|
+
recipeKind: recipe_1.briefInstanceKind,
|
|
34
|
+
suffix: "brief.yaml",
|
|
35
|
+
humanName: "brief",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
recipeKind: recipe_1.briefTypeKind,
|
|
40
|
+
suffix: "brieftype.yaml",
|
|
41
|
+
humanName: "brief type",
|
|
42
|
+
};
|
|
43
|
+
};
|
|
24
44
|
/** Build the `SyncContext` for a brief sync command invocation. */
|
|
25
45
|
const buildContext = (options, logger) => {
|
|
26
46
|
const configPath = options.config ?? process.cwd();
|
|
@@ -44,23 +64,31 @@ const printPlan = (logger, plan) => {
|
|
|
44
64
|
const tally = (0, sync_1.summarizePlan)(plan);
|
|
45
65
|
logger.info(`Plan: ${tally.create} create, ${tally.update} update, ${tally.delete} delete, ${tally.noop} unchanged.`);
|
|
46
66
|
};
|
|
67
|
+
/** `--kind` is shared across all three verbs. */
|
|
68
|
+
const addKindOption = (command) => command.addOption(new commander_1.Option("--kind <kind>", "Recipe kind to operate on. Defaults to brief-type for back-compat.")
|
|
69
|
+
.choices(BRIEF_SYNC_KINDS)
|
|
70
|
+
.default("brief-type"));
|
|
47
71
|
const createPullCommand = () => {
|
|
48
72
|
const command = new commander_1.Command("pull")
|
|
49
|
-
.description("Capture a live brief type as a recipe file.")
|
|
50
|
-
.requiredOption("--name <name>", "Brief
|
|
51
|
-
.addOption(new commander_1.Option("--file <path>", "Output recipe file (default: <name>.
|
|
73
|
+
.description("Capture a live brief type or brief instance as a recipe file.")
|
|
74
|
+
.requiredOption("--name <name>", "Identifier of the recipe. Brief-type codename (`CreativeBrief`) or brief display name (`Q3 Launch`).")
|
|
75
|
+
.addOption(new commander_1.Option("--file <path>", "Output recipe file (default: <name>.<kind>.yaml)"));
|
|
76
|
+
addKindOption(command);
|
|
52
77
|
(0, shared_1.addEnvironmentOption)(command);
|
|
53
78
|
(0, shared_1.addConfigOption)(command);
|
|
54
79
|
(0, shared_1.addVerbosityOptions)(command);
|
|
55
80
|
command.action(async (options) => {
|
|
56
81
|
const logger = (0, cli_tasks_1.toLogger)(options);
|
|
57
82
|
const ctx = buildContext(options, logger);
|
|
83
|
+
const { recipeKind, suffix, humanName } = kindFor(options.kind);
|
|
58
84
|
const name = options.name ?? "";
|
|
59
|
-
const recipe = await (0, sync_1.syncPull)(
|
|
85
|
+
const recipe = await (0, sync_1.syncPull)(recipeKind, { kind: recipeKind.name, id: name }, ctx);
|
|
60
86
|
if (!recipe) {
|
|
61
|
-
throw (0, cli_tasks_1.inputError)(
|
|
87
|
+
throw (0, cli_tasks_1.inputError)(`${humanName.charAt(0).toUpperCase()}${humanName.slice(1)} "${name}" not found.`, recipeKind === recipe_1.briefTypeKind
|
|
88
|
+
? "List brief types with `scai ops brief types list`."
|
|
89
|
+
: "List briefs with `scai ops brief list`.");
|
|
62
90
|
}
|
|
63
|
-
const file = options.file ?? `${slug(name)}
|
|
91
|
+
const file = options.file ?? `${slug(name, humanName.replace(/\s+/g, "-"))}.${suffix}`;
|
|
64
92
|
(0, sync_1.writeRecipe)(file, recipe);
|
|
65
93
|
logger.info(`Pulled "${name}" -> ${file}`, "green");
|
|
66
94
|
});
|
|
@@ -68,35 +96,39 @@ const createPullCommand = () => {
|
|
|
68
96
|
};
|
|
69
97
|
const createDiffCommand = () => {
|
|
70
98
|
const command = new commander_1.Command("diff")
|
|
71
|
-
.description("Show the plan to converge a brief type onto a recipe file.")
|
|
99
|
+
.description("Show the plan to converge a brief type or brief onto a recipe file.")
|
|
72
100
|
.requiredOption("--file <path>", "Recipe file (.yaml / .json)");
|
|
101
|
+
addKindOption(command);
|
|
73
102
|
(0, shared_1.addEnvironmentOption)(command);
|
|
74
103
|
(0, shared_1.addConfigOption)(command);
|
|
75
104
|
(0, shared_1.addVerbosityOptions)(command);
|
|
76
105
|
command.action(async (options) => {
|
|
77
106
|
const logger = (0, cli_tasks_1.toLogger)(options);
|
|
78
107
|
const ctx = buildContext(options, logger);
|
|
79
|
-
const
|
|
80
|
-
const
|
|
108
|
+
const { recipeKind } = kindFor(options.kind);
|
|
109
|
+
const recipe = (await (0, sync_1.loadRecipe)(options.file ?? "", recipeKind.schema));
|
|
110
|
+
const plan = await (0, sync_1.syncDiff)(recipeKind, recipe, { kind: recipeKind.name, id: recipe.name }, ctx);
|
|
81
111
|
printPlan(logger, plan);
|
|
82
112
|
});
|
|
83
113
|
return command;
|
|
84
114
|
};
|
|
85
115
|
const createPushCommand = () => {
|
|
86
116
|
const command = new commander_1.Command("push")
|
|
87
|
-
.description("Converge a brief type onto a recipe file. Dry-run unless --allow-write.")
|
|
117
|
+
.description("Converge a brief type or brief onto a recipe file. Dry-run unless --allow-write.")
|
|
88
118
|
.requiredOption("--file <path>", "Recipe file (.yaml / .json)")
|
|
89
119
|
.addOption(new commander_1.Option("--allow-write", "Apply the plan (default is a dry-run)"))
|
|
90
120
|
.addOption(new commander_1.Option("--prune", "Include delete changes (off by default)"));
|
|
121
|
+
addKindOption(command);
|
|
91
122
|
(0, shared_1.addEnvironmentOption)(command);
|
|
92
123
|
(0, shared_1.addConfigOption)(command);
|
|
93
124
|
(0, shared_1.addVerbosityOptions)(command);
|
|
94
125
|
command.action(async (options) => {
|
|
95
126
|
const logger = (0, cli_tasks_1.toLogger)(options);
|
|
96
127
|
const ctx = buildContext(options, logger);
|
|
97
|
-
const
|
|
128
|
+
const { recipeKind } = kindFor(options.kind);
|
|
129
|
+
const recipe = (await (0, sync_1.loadRecipe)(options.file ?? "", recipeKind.schema));
|
|
98
130
|
const mode = options.allowWrite ? "apply" : "what-if";
|
|
99
|
-
const outcome = await (0, sync_1.syncPush)(
|
|
131
|
+
const outcome = await (0, sync_1.syncPush)(recipeKind, recipe, { kind: recipeKind.name, id: recipe.name }, ctx, { mode, prune: options.prune });
|
|
100
132
|
printPlan(logger, outcome.plan);
|
|
101
133
|
if (outcome.result) {
|
|
102
134
|
logger.info(`Applied ${outcome.result.applied.length} change(s); ${outcome.result.skipped.length} skipped.`, "green");
|
|
@@ -110,9 +142,9 @@ const createPushCommand = () => {
|
|
|
110
142
|
});
|
|
111
143
|
return command;
|
|
112
144
|
};
|
|
113
|
-
/** `scai ops brief sync` — the recipe pull / diff / push verbs
|
|
145
|
+
/** `scai ops brief sync` — the recipe pull / diff / push verbs. */
|
|
114
146
|
const createBriefSyncCommand = () => {
|
|
115
|
-
const command = new commander_1.Command("sync").description("Pull, diff, and push a brief type as a declarative recipe.");
|
|
147
|
+
const command = new commander_1.Command("sync").description("Pull, diff, and push a brief type or brief instance as a declarative recipe.");
|
|
116
148
|
command.addCommand(createPullCommand());
|
|
117
149
|
command.addCommand(createDiffCommand());
|
|
118
150
|
command.addCommand(createPushCommand());
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createBriefUpdateCommand = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const tasks_1 = require("../../brief/tasks");
|
|
10
|
+
const cli_tasks_1 = require("../../shared/cli-tasks");
|
|
11
|
+
const errors_1 = require("../../shared/errors");
|
|
12
|
+
const shared_1 = require("../shared");
|
|
13
|
+
/**
|
|
14
|
+
* `scai ops brief update <briefId>` — partial-PUT update of a brief
|
|
15
|
+
* instance. The JSON body is `Partial<CreateBriefInput> & { status? }`:
|
|
16
|
+
* any subset of `name`, `locale`, `fields`, `isTemplate`, plus an
|
|
17
|
+
* optional `status` workflow move. Read first if you only want to
|
|
18
|
+
* change one field — the PUT is partial but applies whatever keys it
|
|
19
|
+
* receives.
|
|
20
|
+
*
|
|
21
|
+
* Use `scai ops brief set-status` for status-only moves (already wired)
|
|
22
|
+
* or this command's `--status <s>` flag as a shortcut that bypasses the
|
|
23
|
+
* `--file` requirement.
|
|
24
|
+
*/
|
|
25
|
+
const KNOWN_STATUSES = [
|
|
26
|
+
"Draft",
|
|
27
|
+
"InReview",
|
|
28
|
+
"Approved",
|
|
29
|
+
"Canceled",
|
|
30
|
+
"Archived",
|
|
31
|
+
];
|
|
32
|
+
const readJsonFile = (path) => {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(node_fs_1.default.readFileSync(path, "utf8"));
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw (0, cli_tasks_1.inputError)(`Could not read JSON from ${path}: ${error instanceof Error ? error.message : String(error)}`, "Pass --file <path> pointing at a valid brief-update JSON document.");
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const assertUpdateBody = (value) => {
|
|
41
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
42
|
+
throw (0, errors_1.createScaiError)("Brief update body must be a JSON object.", "INPUT_INVALID");
|
|
43
|
+
}
|
|
44
|
+
const obj = value;
|
|
45
|
+
if (obj.status !== undefined && !KNOWN_STATUSES.includes(obj.status)) {
|
|
46
|
+
throw (0, errors_1.createScaiError)(`Invalid 'status': ${JSON.stringify(obj.status)}.`, "INPUT_INVALID", {
|
|
47
|
+
hint: `Must be one of: ${KNOWN_STATUSES.join(", ")}.`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (obj.fields !== undefined && (typeof obj.fields !== "object" || Array.isArray(obj.fields))) {
|
|
51
|
+
throw (0, errors_1.createScaiError)("'fields' must be an object keyed by field name.", "INPUT_INVALID");
|
|
52
|
+
}
|
|
53
|
+
return obj;
|
|
54
|
+
};
|
|
55
|
+
const createBriefUpdateCommand = () => {
|
|
56
|
+
const command = new commander_1.Command("update")
|
|
57
|
+
.description("Update a brief instance with a partial-PUT body. Provide --file for arbitrary patches, or --status as a shortcut for a status-only move.")
|
|
58
|
+
.argument("<briefId>", "Brief UUID")
|
|
59
|
+
.addOption(new commander_1.Option("-f, --file <path>", "Path to a JSON file with the partial patch."))
|
|
60
|
+
.addOption(new commander_1.Option("--status <status>", "Shortcut: status-only patch. Equivalent to `scai ops brief set-status`.").choices(KNOWN_STATUSES));
|
|
61
|
+
(0, shared_1.addOrgScopeOptions)(command);
|
|
62
|
+
(0, shared_1.addConfigOption)(command);
|
|
63
|
+
(0, shared_1.addVerbosityOptions)(command);
|
|
64
|
+
(0, shared_1.addApplyOption)(command);
|
|
65
|
+
(0, shared_1.addWhatIfOption)(command);
|
|
66
|
+
command.action(async (briefId, options) => {
|
|
67
|
+
await (0, shared_1.withApplyGate)(async (opts) => {
|
|
68
|
+
if (!opts.file && !opts.status) {
|
|
69
|
+
throw (0, cli_tasks_1.inputError)("Pass --file <path> or --status <status>.", "The PUT requires at least one field; pass a JSON file with the patch or use --status for the common case.");
|
|
70
|
+
}
|
|
71
|
+
const fromFile = opts.file ? assertUpdateBody(readJsonFile(opts.file)) : {};
|
|
72
|
+
const patch = {
|
|
73
|
+
...fromFile,
|
|
74
|
+
...(opts.status ? { status: opts.status } : {}),
|
|
75
|
+
};
|
|
76
|
+
await (0, tasks_1.runBriefUpdate)({ ...opts, briefId, patch });
|
|
77
|
+
})(options);
|
|
78
|
+
});
|
|
79
|
+
command.addHelpText("after", "\nExamples:\n" +
|
|
80
|
+
" $ scai ops brief update <id> --status Approved -n agents --apply\n" +
|
|
81
|
+
" $ scai ops brief update <id> -f ./patch.json -n agents --apply\n");
|
|
82
|
+
return command;
|
|
83
|
+
};
|
|
84
|
+
exports.createBriefUpdateCommand = createBriefUpdateCommand;
|
|
@@ -76,7 +76,7 @@ const createDiffCommand = () => {
|
|
|
76
76
|
command.action(async (options) => {
|
|
77
77
|
const logger = (0, cli_tasks_1.toLogger)(options);
|
|
78
78
|
const ctx = buildContext(options, logger);
|
|
79
|
-
const recipe = (0, sync_1.loadRecipe)(options.file ?? "", recipe_1.campaignKind.schema);
|
|
79
|
+
const recipe = await (0, sync_1.loadRecipe)(options.file ?? "", recipe_1.campaignKind.schema);
|
|
80
80
|
const plan = await (0, sync_1.syncDiff)(recipe_1.campaignKind, recipe, { kind: recipe_1.campaignKind.name, id: recipe.name }, ctx);
|
|
81
81
|
printPlan(logger, plan);
|
|
82
82
|
});
|
|
@@ -94,7 +94,7 @@ const createPushCommand = () => {
|
|
|
94
94
|
command.action(async (options) => {
|
|
95
95
|
const logger = (0, cli_tasks_1.toLogger)(options);
|
|
96
96
|
const ctx = buildContext(options, logger);
|
|
97
|
-
const recipe = (0, sync_1.loadRecipe)(options.file ?? "", recipe_1.campaignKind.schema);
|
|
97
|
+
const recipe = await (0, sync_1.loadRecipe)(options.file ?? "", recipe_1.campaignKind.schema);
|
|
98
98
|
const mode = options.allowWrite ? "apply" : "what-if";
|
|
99
99
|
const outcome = await (0, sync_1.syncPush)(recipe_1.campaignKind, recipe, { kind: recipe_1.campaignKind.name, id: recipe.name }, ctx, { mode, prune: options.prune });
|
|
100
100
|
printPlan(logger, outcome.plan);
|
package/dist/mcp/descriptions.js
CHANGED
|
@@ -72,7 +72,7 @@ exports.TOOL_DESCRIPTIONS = {
|
|
|
72
72
|
publish_lifecycle: "Mutating publishing operations. v1 exposes only `cancel` — the safety-improving op that stops a running publish. Submission verbs (`submit_item` / `submit_all` / `unpublish`) are intentionally CLI-only because publishing pushes content to Experience Edge and the consent model requires a token minted from a human-driven dry-run. Use `publish_inspect verb='list-running'` to find a jobId to cancel; cancellation is recoverable via resubmission.",
|
|
73
73
|
// Brief (Content Operations)
|
|
74
74
|
brief_inspect: "[unstable] Read-side Content Operations Brief surface over a discriminated { verb } input — `list` (briefs in tenant), `show` (one brief by id, with nested to-dos/comments/references), `types` (brief schema templates with field definitions and aiIntent hints), `todos` (across briefs or filtered by briefId), or `comments` (across briefs or filtered by briefId). 'Todo' is the Content Operations UI label for the Brief API's wire `tasks` resource. No writes; safe to call as part of plan assembly before any future brief_manage operation.",
|
|
75
|
-
brief_manage: "[unstable] Mutating Content Operations Brief surface over a discriminated { resource, verb } input. `resource: 'brief-type'` supports `create` (POST a new type from a full body), `update` (PUT-replace by id; no PATCH — read first if preserving fields), and `delete` (irreversible). `resource: 'brief'` supports `set-status` — move a brief to Draft | InReview | Approved | Canceled | Archived (a brief must leave Draft before it can be linked to a campaign) — and `delete` (irreversible; SDK `deleteBrief`). `resource: 'comment'` supports `create` — post a comment to a brief via `commentText` (UNVERIFIED: the write body is a best guess; smoke-test before relying on it). Requires allowWrite: true. Brief-type `name` must match /^[A-Za-z][A-Za-z0-9_]*$/.
|
|
75
|
+
brief_manage: "[unstable] Mutating Content Operations Brief surface over a discriminated { resource, verb } input. `resource: 'brief-type'` supports `create` (POST a new type from a full body), `update` (PUT-replace by id; no PATCH — read first if preserving fields), and `delete` (irreversible). `resource: 'brief'` supports `create` (POST a new brief — needs `briefTypeId` plus a `brief` body of name/locale/fields/isTemplate), `update` (partial PUT by id — any subset of name/locale/fields/isTemplate plus an optional `status`), `set-status` — move a brief to Draft | InReview | Approved | Canceled | Archived (a brief must leave Draft before it can be linked to a campaign) — and `delete` (irreversible; SDK `deleteBrief`). `resource: 'comment'` supports `create` — post a comment to a brief via `commentText` (UNVERIFIED: the write body is a best guess; smoke-test before relying on it). Requires allowWrite: true. Brief-type `name` must match /^[A-Za-z][A-Za-z0-9_]*$/. For declarative, idempotent brief writes prefer brief_recipe_push.",
|
|
76
76
|
// Campaign (Orchestrate)
|
|
77
77
|
campaign_inspect: "[unstable] Read-side Orchestrate campaign surface over a discriminated { verb } input — `list` (campaigns in tenant), `show` (one campaign by id, with deliverables and tasks inline), `tasks` (tasks under a deliverable), `task` (one task by id), or `users` (the member directory that resolves the Auth0 subjects on members and assignees). A campaign is an Orchestrate `project`; projects own deliverables, deliverables own tasks. No writes.",
|
|
78
78
|
campaign_manage: "[unstable] Mutating Orchestrate campaign surface over a discriminated { resource, verb } input — `resource: 'campaign'|'deliverable'` support verbs `create` and `delete`; `resource: 'task'` supports `create`, `update` (PUT full-replacement, no PATCH), and `delete`. Requires allowWrite: true. Deliverable/task writes need `campaignId` (and `deliverableId` for tasks); update/task-delete need `taskId`. The `delete` verb is irreversible and hits Orchestrate DELETE endpoints that were never captured during reverse-engineering — they are wired optimistically per REST conventions and remain UNVERIFIED; smoke-test before relying on them. Pass whatIf: true for a plan-only dry run.",
|
|
@@ -90,8 +90,8 @@ exports.TOOL_DESCRIPTIONS = {
|
|
|
90
90
|
brand_review: "[unstable] Score content against a brand kit using AI. Returns an overall 1–5 score plus per-section + per-field breakdowns with explanations and improvement suggestions. The kit must already have populated sections — call brand_manage with action=seed (or pre-existing brand_inspect verb=list-sections on a populated kit) first. Headline agent-loop op for evaluating marketing copy, page content, or PR drafts; pair with the file path in `label` so SARIF / CI aggregators can attribute findings to source files.",
|
|
91
91
|
brand_recipe_inspect: "[unstable] Read-side of the brand-kit recipe surface over a discriminated { verb } input. verb=pull captures a live brand kit as a declarative recipe (a clean, schema'd description — kit metadata plus section/field values, no server UUIDs). verb=diff compares a recipe against the live kit and returns the plan (create / update / noop changes). Both are read-only; neither writes. Use pull to snapshot a kit, diff to preview what a push would do.",
|
|
92
92
|
brand_recipe_push: "[unstable] Converge a brand kit onto a declarative recipe. Computes the plan, then — unless whatIf — applies it: full orchestration via seedBrandKit (create → upload → publish → ingest → enrich) when the kit is absent, then per-field value convergence. Requires allowWrite: true to mutate; pass whatIf: true for a dry-run that returns the plan without writing. When the recipe carries documents for a not-yet-created kit, push triggers paid AI pipeline runs (~5–15 min).",
|
|
93
|
-
brief_recipe_inspect: "[unstable] Pull or diff a Sitecore Content Operations brief type as a declarative recipe. verb='pull' captures the live
|
|
94
|
-
brief_recipe_push: "[unstable] Push a brief-type recipe — converge
|
|
93
|
+
brief_recipe_inspect: "[unstable] Pull or diff a Sitecore Content Operations brief type OR brief instance as a declarative recipe. `kind` discriminates: 'brief-type' (default — the schema template, identified by codename) or 'brief' (a populated brief instance, identified by display name; references its type by codename via `briefTypeName`). verb='pull' captures the live resource named `name` as a clean recipe; verb='diff' compares a given recipe against the live resource and returns the convergence plan. Read-only — neither verb writes.",
|
|
94
|
+
brief_recipe_push: "[unstable] Push a brief-type OR brief-instance recipe — converge the named Sitecore Content Operations resource onto the recipe. `kind: 'brief-type'` (default) creates the brief type when absent or PUT-replaces it when present. `kind: 'brief'` creates the brief when absent (resolving `briefTypeName` to its server id), or partial-PUT-updates it when present (refuses to repoint at a different brief type — the API has no verified path for that). Write tool: gated by `allowWrite`; pass `whatIf: true` for a dry-run that returns the plan without writing.",
|
|
95
95
|
campaign_recipe_inspect: "[unstable] Pull or diff a Sitecore Orchestrate campaign as a declarative recipe. verb='pull' captures the live campaign named `campaignName` (its project, deliverables, and tasks) as a clean recipe with server ids dropped. verb='diff' compares a given recipe against the live campaign and returns the convergence plan. Neither verb writes.",
|
|
96
96
|
campaign_recipe_push: "[unstable] Push a campaign recipe — converge a Sitecore Orchestrate campaign onto the recipe. Creates the campaign when absent, creates missing deliverables and tasks, and updates existing tasks. Additive: a recipe omitting a deliverable or task never removes it. Gated by `allowWrite`; `whatIf` returns the plan without writing.",
|
|
97
97
|
};
|
|
@@ -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),
|