@cloudhal/mcp-server 0.0.0-stage → 0.1.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/LICENSE +202 -0
- package/README.md +18 -2
- package/dist/src/analytics-reports.d.ts +164 -0
- package/dist/src/analytics-reports.js +632 -0
- package/dist/src/analytics-tools.d.ts +4 -0
- package/dist/src/analytics-tools.js +597 -0
- package/dist/src/asset-tools.d.ts +5 -0
- package/dist/src/asset-tools.js +685 -0
- package/dist/src/body-tools.d.ts +46 -0
- package/dist/src/body-tools.js +708 -0
- package/dist/src/changes.d.ts +10 -0
- package/dist/src/changes.js +112 -0
- package/dist/src/chrome-tools.d.ts +8 -0
- package/dist/src/chrome-tools.js +207 -0
- package/dist/src/comment-tools.d.ts +9 -0
- package/dist/src/comment-tools.js +743 -0
- package/dist/src/comments.d.ts +184 -0
- package/dist/src/comments.js +408 -0
- package/dist/src/common.d.ts +258 -0
- package/dist/src/common.js +488 -0
- package/dist/src/content-shared.d.ts +50 -0
- package/dist/src/content-shared.js +102 -0
- package/dist/src/design-tools.d.ts +9 -0
- package/dist/src/design-tools.js +349 -0
- package/dist/src/elevation-tools.d.ts +3 -0
- package/dist/src/elevation-tools.js +331 -0
- package/dist/src/entry-terms.d.ts +14 -0
- package/dist/src/entry-terms.js +51 -0
- package/dist/src/entry-tools.d.ts +4 -0
- package/dist/src/entry-tools.js +442 -0
- package/dist/src/http.d.ts +48 -0
- package/dist/src/http.js +101 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +10 -0
- package/dist/src/menu-tools.d.ts +41 -0
- package/dist/src/menu-tools.js +1058 -0
- package/dist/src/page-tools.d.ts +5 -0
- package/dist/src/page-tools.js +311 -0
- package/dist/src/preview-doc.d.ts +9 -0
- package/dist/src/preview-doc.js +56 -0
- package/dist/src/preview-view.d.ts +19 -0
- package/dist/src/preview-view.js +71 -0
- package/dist/src/publish-tools.d.ts +10 -0
- package/dist/src/publish-tools.js +248 -0
- package/dist/src/render/cdp.d.ts +29 -0
- package/dist/src/render/cdp.js +190 -0
- package/dist/src/render/checks/components.d.ts +5 -0
- package/dist/src/render/checks/components.js +95 -0
- package/dist/src/render/checks/content.d.ts +5 -0
- package/dist/src/render/checks/content.js +63 -0
- package/dist/src/render/checks/contrast.d.ts +5 -0
- package/dist/src/render/checks/contrast.js +72 -0
- package/dist/src/render/checks/index.d.ts +1 -0
- package/dist/src/render/checks/index.js +29 -0
- package/dist/src/render/checks/layout.d.ts +5 -0
- package/dist/src/render/checks/layout.js +44 -0
- package/dist/src/render/checks/motion.d.ts +5 -0
- package/dist/src/render/checks/motion.js +66 -0
- package/dist/src/render/checks/navigation.d.ts +5 -0
- package/dist/src/render/checks/navigation.js +78 -0
- package/dist/src/render/checks/prelude.d.ts +1 -0
- package/dist/src/render/checks/prelude.js +64 -0
- package/dist/src/render/limiter.d.ts +11 -0
- package/dist/src/render/limiter.js +29 -0
- package/dist/src/render/page.d.ts +19 -0
- package/dist/src/render/page.js +135 -0
- package/dist/src/render/types.d.ts +89 -0
- package/dist/src/render/types.js +140 -0
- package/dist/src/render-tools.d.ts +14 -0
- package/dist/src/render-tools.js +314 -0
- package/dist/src/render.d.ts +14 -0
- package/dist/src/render.js +33 -0
- package/dist/src/schedule-tools.d.ts +2 -0
- package/dist/src/schedule-tools.js +1596 -0
- package/dist/src/schedules.d.ts +292 -0
- package/dist/src/schedules.js +890 -0
- package/dist/src/schema-transforms.d.ts +14 -0
- package/dist/src/schema-transforms.js +50 -0
- package/dist/src/search-tools.d.ts +2 -0
- package/dist/src/search-tools.js +112 -0
- package/dist/src/server-approvals.d.ts +51 -0
- package/dist/src/server-approvals.js +273 -0
- package/dist/src/server-call.d.ts +28 -0
- package/dist/src/server-call.js +259 -0
- package/dist/src/server-instructions.d.ts +4 -0
- package/dist/src/server-instructions.js +74 -0
- package/dist/src/server-resources.d.ts +4 -0
- package/dist/src/server-resources.js +83 -0
- package/dist/src/server-schema.d.ts +20 -0
- package/dist/src/server-schema.js +82 -0
- package/dist/src/server.d.ts +80 -0
- package/dist/src/server.js +135 -0
- package/dist/src/stdio.d.ts +9 -0
- package/dist/src/stdio.js +18 -0
- package/dist/src/surface-tools.d.ts +4 -0
- package/dist/src/surface-tools.js +357 -0
- package/dist/src/taxonomy-tools.d.ts +16 -0
- package/dist/src/taxonomy-tools.js +1433 -0
- package/dist/src/terms.d.ts +47 -0
- package/dist/src/terms.js +271 -0
- package/dist/src/tools.d.ts +39 -0
- package/dist/src/tools.js +511 -0
- package/dist/src/translation-tools.d.ts +3 -0
- package/dist/src/translation-tools.js +422 -0
- package/package.json +86 -3
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Human-readable change lines for approval prompts (publish). */
|
|
2
|
+
import type { Site } from "@cloudhal/core-contracts";
|
|
3
|
+
import type { SnapshotDiff } from "@cloudhal/core-ts";
|
|
4
|
+
import { type ToolContext } from "./common.js";
|
|
5
|
+
export declare const KIND_LABEL: Record<string, string>;
|
|
6
|
+
/**
|
|
7
|
+
* One line per changed node for approval prompts, at most 8. Articles also show their assignment
|
|
8
|
+
* deltas (RFC-0002 §6.4): `~ article "…" (+tag ai, −tag llms, primary category → News)`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function describeChanges(ctx: ToolContext, diff: SnapshotDiff, site?: Site): Promise<string[]>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** Human-readable change lines for approval prompts (publish). */
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import { termName } from "@cloudhal/core-ts";
|
|
6
|
+
import { describeAsset } from "./asset-tools.js";
|
|
7
|
+
import { quote, } from "./common.js";
|
|
8
|
+
import { menuChangeSummary } from "./menu-tools.js";
|
|
9
|
+
import { assignmentDeltas, describeDeltas, termLookup } from "./terms.js";
|
|
10
|
+
|
|
11
|
+
export const KIND_LABEL = {
|
|
12
|
+
site: "site",
|
|
13
|
+
page: "page",
|
|
14
|
+
contentEntry: "article",
|
|
15
|
+
taxonomy: "taxonomy",
|
|
16
|
+
term: "term",
|
|
17
|
+
redirect: "redirect",
|
|
18
|
+
menu: "menu",
|
|
19
|
+
asset: "media",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* One line per changed node for approval prompts, at most 8. Articles also show their assignment
|
|
24
|
+
* deltas (RFC-0002 §6.4): `~ article "…" (+tag ai, −tag llms, primary category → News)`.
|
|
25
|
+
*/
|
|
26
|
+
export async function describeChanges(
|
|
27
|
+
ctx ,
|
|
28
|
+
diff ,
|
|
29
|
+
site ,
|
|
30
|
+
) {
|
|
31
|
+
const locale = site?.defaultLocale;
|
|
32
|
+
const lookup = termLookup(ctx);
|
|
33
|
+
const title = async (ref ) => {
|
|
34
|
+
switch (ref.kind) {
|
|
35
|
+
case "page":
|
|
36
|
+
return (await ctx.store.getPage(ref.id))?.title;
|
|
37
|
+
case "contentEntry":
|
|
38
|
+
return (await ctx.store.getContentEntry(ref.id))?.title;
|
|
39
|
+
case "site":
|
|
40
|
+
return "site settings";
|
|
41
|
+
case "taxonomy": {
|
|
42
|
+
const t = await ctx.store.getTaxonomy(ref.id);
|
|
43
|
+
return t ? ((locale ? t.labels[locale]?.plural : undefined) ?? t.key) : undefined;
|
|
44
|
+
}
|
|
45
|
+
case "term": {
|
|
46
|
+
const t = await ctx.store.getTerm(ref.id);
|
|
47
|
+
return t ? termName(t, locale) : undefined;
|
|
48
|
+
}
|
|
49
|
+
case "redirect": {
|
|
50
|
+
const r = await ctx.store.getRedirect(ref.id);
|
|
51
|
+
return r ? `${r.from} → ${r.to}` : undefined;
|
|
52
|
+
}
|
|
53
|
+
case "menu":
|
|
54
|
+
return (await ctx.store.getMenu(ref.id))?.key;
|
|
55
|
+
default:
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
/** Media lines name the file, its type, size and the articles or pages using it (§6.2). */
|
|
60
|
+
const assetLine = async (sign , ref ) => {
|
|
61
|
+
const asset = await ctx.store.getAsset(ref.id);
|
|
62
|
+
if (!asset) return undefined;
|
|
63
|
+
const usage = (await ctx.store.assetUsage(asset.siteId, [asset.id])).get(asset.id);
|
|
64
|
+
const first = usage?.refs[0];
|
|
65
|
+
let user ;
|
|
66
|
+
if (first?.nodeKind === "contentEntry")
|
|
67
|
+
user = (await ctx.store.getContentEntry(first.nodeId))?.title;
|
|
68
|
+
else if (first?.nodeKind === "page") user = (await ctx.store.getPage(first.nodeId))?.title;
|
|
69
|
+
const more = usage && usage.total > 1 ? ` and ${usage.total - 1} more` : "";
|
|
70
|
+
return `${sign} ${describeAsset(asset, locale)}${user ? `, used by "${quote(user)}"${more}` : ""}${asset.status === "retired" ? " (retired)" : ""}`;
|
|
71
|
+
};
|
|
72
|
+
const deltas = async (
|
|
73
|
+
ref ,
|
|
74
|
+
before ,
|
|
75
|
+
) => {
|
|
76
|
+
if (ref.kind === "menu") return menuChangeSummary(ctx, ref, before);
|
|
77
|
+
if (ref.kind !== "contentEntry") return "";
|
|
78
|
+
const now = await ctx.store.getContentEntry(ref.id);
|
|
79
|
+
if (!now) return "";
|
|
80
|
+
const was = before ? await ctx.store.readSnapshotNode (before) : undefined;
|
|
81
|
+
const text = await describeDeltas(assignmentDeltas(was?.terms, now.terms), lookup, locale);
|
|
82
|
+
return text ? ` (${text})` : "";
|
|
83
|
+
};
|
|
84
|
+
const label = (kind ) => KIND_LABEL[kind] ?? kind;
|
|
85
|
+
const out = [];
|
|
86
|
+
const groups
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
= [
|
|
90
|
+
["+", diff.added.map((ref) => ({ ref }))],
|
|
91
|
+
["~", diff.changed.map((c) => ({ ref: c.after, before: c.before }))],
|
|
92
|
+
["−", diff.removed.map((ref) => ({ ref }))],
|
|
93
|
+
];
|
|
94
|
+
for (const [sign, refs] of groups)
|
|
95
|
+
for (const { ref, before } of refs) {
|
|
96
|
+
if (out.length >= 8) {
|
|
97
|
+
out.push(`… and ${diff.added.length + diff.changed.length + diff.removed.length - 8} more`);
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
if (ref.kind === "asset" && sign !== "−") {
|
|
101
|
+
const line = await assetLine(sign, ref);
|
|
102
|
+
if (line) {
|
|
103
|
+
out.push(line);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const t = sign === "−" ? undefined : await title(ref);
|
|
108
|
+
const d = sign === "−" ? "" : await deltas(ref, before);
|
|
109
|
+
out.push(`${sign} ${label(ref.kind)} ${t ? `"${quote(t)}"` : ref.id}${d}`);
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Site chrome over MCP (RFC-0025 §5): `get_chrome` reads the header, footer and brand with the
|
|
3
|
+
* form schemas and the menu keys they may name; `set_chrome` and `set_brand` change them and
|
|
4
|
+
* always wait for a person (the chrome is on every page). Both are checked against the contract
|
|
5
|
+
* and `checkChrome` before they are saved, and show on the site after a publish.
|
|
6
|
+
*/
|
|
7
|
+
import { type ToolDefinition } from "./common.js";
|
|
8
|
+
export declare function chromeTools(): ToolDefinition[];
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Site chrome over MCP (RFC-0025 §5): `get_chrome` reads the header, footer and brand with the
|
|
3
|
+
* form schemas and the menu keys they may name; `set_chrome` and `set_brand` change them and
|
|
4
|
+
* always wait for a person (the chrome is on every page). Both are checked against the contract
|
|
5
|
+
* and `checkChrome` before they are saved, and show on the site after a publish.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
brandFromForm,
|
|
11
|
+
brandToForm,
|
|
12
|
+
checkChrome,
|
|
13
|
+
chromeMenuKeys,
|
|
14
|
+
chromeSchemas,
|
|
15
|
+
logoAsset,
|
|
16
|
+
resolveChrome,
|
|
17
|
+
|
|
18
|
+
} from "@cloudhal/core-ts/chrome";
|
|
19
|
+
import { id, mapError, obj, ToolError } from "./common.js";
|
|
20
|
+
|
|
21
|
+
async function requireSite(ctx , siteId ) {
|
|
22
|
+
const site = await ctx.store.getSite(siteId);
|
|
23
|
+
if (!site) throw new ToolError("not_found", `site ${siteId} not found`);
|
|
24
|
+
return site;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** `patch` onto `current`: a value replaces the part, null removes it. */
|
|
28
|
+
function merge (current , patch ) {
|
|
29
|
+
const out = { ...(current ?? {}) };
|
|
30
|
+
for (const [k, v] of Object.entries(patch))
|
|
31
|
+
if (v === null) delete out[k];
|
|
32
|
+
else out[k] = v;
|
|
33
|
+
return out ;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const PART = {
|
|
37
|
+
anyOf: [{ type: "object" }, { type: "null" }],
|
|
38
|
+
description: "Fields to change (see get_chrome's schemas); null removes a field or the part",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function chromeTools() {
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
name: "get_chrome",
|
|
45
|
+
title: "Read the header and footer",
|
|
46
|
+
description:
|
|
47
|
+
"The site's header, footer and brand (RFC-0025): what is set, what pages show (the defaults filled in), the form schemas with every allowed value, and the menu keys the header and footer may name (primary is derived from pages when the site has no menu). Change them with set_chrome and set_brand; a page can hide its header or footer, or make the header transparent over its first band, with its own chrome field (update_page). A transparent header (overHero, or a page's chrome) only applies where the page opens with a Band painted inverse, brand, accent or gradient: it takes that surface's text color; elsewhere it stays solid. Below collapseAt the menus move behind a Menu button (mobileStyle: dropdown, drawer-start, drawer-end, fullscreen); render_page checks skip-link, landmarks, header-contrast and focus-obscured.",
|
|
48
|
+
inputSchema: obj({ siteId: id }, ["siteId"]),
|
|
49
|
+
outputSchema: obj(
|
|
50
|
+
{
|
|
51
|
+
chrome: { type: "object" },
|
|
52
|
+
brand: { type: "object" },
|
|
53
|
+
resolved: { type: "object" },
|
|
54
|
+
schemas: { type: "object" },
|
|
55
|
+
menus: { type: "array", items: { type: "string" } },
|
|
56
|
+
rev: { type: "integer" },
|
|
57
|
+
},
|
|
58
|
+
["chrome", "brand", "resolved", "schemas", "menus", "rev"],
|
|
59
|
+
),
|
|
60
|
+
annotations: {
|
|
61
|
+
readOnlyHint: true,
|
|
62
|
+
destructiveHint: false,
|
|
63
|
+
idempotentHint: true,
|
|
64
|
+
openWorldHint: false,
|
|
65
|
+
},
|
|
66
|
+
scopes: ["site:read"],
|
|
67
|
+
handler: async (a, ctx) => {
|
|
68
|
+
const site = await requireSite(ctx, a.siteId );
|
|
69
|
+
const { header, footer, announcement, consent } = resolveChrome(site);
|
|
70
|
+
return {
|
|
71
|
+
chrome: site.chrome ?? {},
|
|
72
|
+
brand: brandToForm(site.brand),
|
|
73
|
+
resolved: { header, footer, announcement: announcement ?? null, consent },
|
|
74
|
+
schemas: chromeSchemas,
|
|
75
|
+
menus: chromeMenuKeys(await ctx.store.listMenus(site.id)),
|
|
76
|
+
rev: site.rev,
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "set_chrome",
|
|
82
|
+
title: "Change the header and footer",
|
|
83
|
+
description:
|
|
84
|
+
"Change the site's header and footer (get_chrome lists the fields and allowed values). Each part's fields are merged onto the current ones; null removes a field, or the whole part (back to the default). Menus are named by key and must exist; a button action needs a label and a link; the copyright line knows {year} and {site}. Shows on every page after a publish. Always waits for a person.",
|
|
85
|
+
inputSchema: obj(
|
|
86
|
+
{
|
|
87
|
+
siteId: id,
|
|
88
|
+
rev: { type: "integer", minimum: 0, description: "The site's rev from get_chrome" },
|
|
89
|
+
header: PART,
|
|
90
|
+
footer: {
|
|
91
|
+
...PART,
|
|
92
|
+
description:
|
|
93
|
+
"Fields to change; null removes a field or the part. footer.region is one surface section (kind surface, checked like a page's) above the columns, shown on every page (start its headings at level 2); null removes it",
|
|
94
|
+
},
|
|
95
|
+
announcement: {
|
|
96
|
+
...PART,
|
|
97
|
+
description:
|
|
98
|
+
"The bar above the header: id (change it to show a new bar to readers who closed the old one), text, label + href, tone info|brand|warning, dismissible",
|
|
99
|
+
},
|
|
100
|
+
consent: {
|
|
101
|
+
...PART,
|
|
102
|
+
description:
|
|
103
|
+
"The privacy bar: mode none|notice|choices, policy (the privacy page). Cloudhal sites are cookieless and embeds load on click, so most sites need none; a person decides",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
["siteId", "rev"],
|
|
107
|
+
),
|
|
108
|
+
outputSchema: obj({ chrome: { type: "object" }, rev: { type: "integer" } }, [
|
|
109
|
+
"chrome",
|
|
110
|
+
"rev",
|
|
111
|
+
]),
|
|
112
|
+
annotations: {
|
|
113
|
+
readOnlyHint: false,
|
|
114
|
+
destructiveHint: false,
|
|
115
|
+
idempotentHint: true,
|
|
116
|
+
openWorldHint: false,
|
|
117
|
+
},
|
|
118
|
+
scopes: ["site:write"],
|
|
119
|
+
requiresApproval: true,
|
|
120
|
+
approvalPrompt: async (a) => {
|
|
121
|
+
const parts = (["header", "footer", "announcement", "consent"] ).filter(
|
|
122
|
+
(k) => a[k] !== undefined,
|
|
123
|
+
);
|
|
124
|
+
return `Change the site's ${parts.join(" and ") || "chrome"} (shown on every page)?`;
|
|
125
|
+
},
|
|
126
|
+
handler: async (a, ctx) => {
|
|
127
|
+
const site = await requireSite(ctx, a.siteId );
|
|
128
|
+
const chrome = { ...(site.chrome ?? {}) };
|
|
129
|
+
for (const part of ["header", "footer", "announcement", "consent"] ) {
|
|
130
|
+
const v = a[part];
|
|
131
|
+
if (v === undefined) continue;
|
|
132
|
+
if (v === null) delete chrome[part];
|
|
133
|
+
else if (typeof v === "object" && !Array.isArray(v))
|
|
134
|
+
(chrome )[part] = merge(
|
|
135
|
+
chrome[part],
|
|
136
|
+
v ,
|
|
137
|
+
);
|
|
138
|
+
else throw new ToolError("invalid", `${part} must be an object or null`);
|
|
139
|
+
}
|
|
140
|
+
const issues = checkChrome(chrome, chromeMenuKeys(await ctx.store.listMenus(site.id)));
|
|
141
|
+
if (issues.length > 0)
|
|
142
|
+
throw new ToolError("invalid", issues.map((i) => `${i.path}: ${i.message}`).join("; "));
|
|
143
|
+
const { chrome: _old, ...rest } = site;
|
|
144
|
+
try {
|
|
145
|
+
const saved = await ctx.store.updateSite({
|
|
146
|
+
...rest,
|
|
147
|
+
rev: a.rev ,
|
|
148
|
+
...(Object.keys(chrome).length > 0 ? { chrome } : {}),
|
|
149
|
+
});
|
|
150
|
+
return { chrome: saved.chrome ?? {}, rev: saved.rev };
|
|
151
|
+
} catch (e) {
|
|
152
|
+
mapError(e);
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "set_brand",
|
|
158
|
+
title: "Change the logo and social links",
|
|
159
|
+
description:
|
|
160
|
+
"Change the site's brand (get_chrome lists the fields): logo and logoDark as an image asset of the site (asset:<id>, from list_assets or upload_asset; the site's name shows without one), logoHeight s|m|l, and social links (network and href, up to 8, shown in footers that ask for them). Fields are merged onto the current ones; null removes one. Always waits for a person.",
|
|
161
|
+
inputSchema: obj(
|
|
162
|
+
{
|
|
163
|
+
siteId: id,
|
|
164
|
+
rev: { type: "integer", minimum: 0, description: "The site's rev from get_chrome" },
|
|
165
|
+
brand: { type: "object", description: "Fields to change; null removes one" },
|
|
166
|
+
},
|
|
167
|
+
["siteId", "rev", "brand"],
|
|
168
|
+
),
|
|
169
|
+
outputSchema: obj({ brand: { type: "object" }, rev: { type: "integer" } }, ["brand", "rev"]),
|
|
170
|
+
annotations: {
|
|
171
|
+
readOnlyHint: false,
|
|
172
|
+
destructiveHint: false,
|
|
173
|
+
idempotentHint: true,
|
|
174
|
+
openWorldHint: false,
|
|
175
|
+
},
|
|
176
|
+
scopes: ["site:write"],
|
|
177
|
+
requiresApproval: true,
|
|
178
|
+
approvalPrompt: async (a) => {
|
|
179
|
+
const keys = Object.keys((a.brand ?? {}) );
|
|
180
|
+
return `Change the site's brand (${keys.join(", ") || "none"}, shown on every page)?`;
|
|
181
|
+
},
|
|
182
|
+
handler: async (a, ctx) => {
|
|
183
|
+
const site = await requireSite(ctx, a.siteId );
|
|
184
|
+
const values = merge (
|
|
185
|
+
brandToForm(site.brand),
|
|
186
|
+
(a.brand ?? {}) ,
|
|
187
|
+
);
|
|
188
|
+
const { brand, issues } = await brandFromForm(values, async (assetId) =>
|
|
189
|
+
logoAsset(await ctx.store.getAsset(assetId), site),
|
|
190
|
+
);
|
|
191
|
+
if (issues.length > 0)
|
|
192
|
+
throw new ToolError("invalid", issues.map((i) => `${i.path}: ${i.message}`).join("; "));
|
|
193
|
+
const { brand: _old, ...rest } = site;
|
|
194
|
+
try {
|
|
195
|
+
const saved = await ctx.store.updateSite({
|
|
196
|
+
...rest,
|
|
197
|
+
rev: a.rev ,
|
|
198
|
+
...(brand ? { brand } : {}),
|
|
199
|
+
});
|
|
200
|
+
return { brand: brandToForm(saved.brand), rev: saved.rev };
|
|
201
|
+
} catch (e) {
|
|
202
|
+
mapError(e);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Actor } from "@cloudhal/core-contracts";
|
|
2
|
+
import { type ToolDefinition } from "./common.js";
|
|
3
|
+
/**
|
|
4
|
+
* Who a call that makes something public runs as (RFC-0007 §10.3): the person, never an agent.
|
|
5
|
+
* Studio's carry-out passes the approving person as the principal; a local agent's call runs as
|
|
6
|
+
* the developer who said yes in their own client.
|
|
7
|
+
*/
|
|
8
|
+
export declare const LOCAL_DEVELOPER: Actor;
|
|
9
|
+
export declare function commentTools(): ToolDefinition[];
|