@drop2run/mcp 0.10.0 → 0.11.0
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/README.md +10 -1
- package/dist/index.js +216 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,7 +88,10 @@ lose it.
|
|
|
88
88
|
| `list_folders` | Lists the account's folders, each with the path `folder` accepts |
|
|
89
89
|
| `delete_site` | Deletes a site permanently, subdomain included — requires the subdomain repeated as `confirm` |
|
|
90
90
|
| `get_site` | Reads a site's settings, whether it is paused, and its versions with the live one marked |
|
|
91
|
-
| `update_site` | Changes a site's name, serving mode, password, forms, scheduled takedown or folder |
|
|
91
|
+
| `update_site` | Changes a site's name, serving mode, password, invite-only, comments, forms, scheduled takedown or folder |
|
|
92
|
+
| `list_people` | Lists who a site is shared with, what each may do, and whether only they can open it |
|
|
93
|
+
| `invite_people` | Invites addresses the person gave — to view, or to view and comment — and emails each one |
|
|
94
|
+
| `remove_person` | Takes one address off a site's list |
|
|
92
95
|
| `pause_site` | Takes a site off the air, keeping its files, versions and subdomain |
|
|
93
96
|
| `resume_site` | Puts a paused site back on the air |
|
|
94
97
|
| `rollback_site` | Serves an earlier version again, by its id from `get_site` |
|
|
@@ -117,6 +120,12 @@ default; `expiryAction: "delete"` removes it instead, and asks for the subdomain
|
|
|
117
120
|
`delete_site` does. Settings your plan does not include are refused with the reason, and `get_site` says
|
|
118
121
|
which ones those are.
|
|
119
122
|
|
|
123
|
+
To share a site for review, `invite_people` puts the addresses the person gave on its list and emails each
|
|
124
|
+
one; `update_site` with `invitedOnly: true` makes the list the only way in, and `comments` decides who may
|
|
125
|
+
comment — `invited` for the people invited with the comment role, `anyone` for anybody signed in. Both are
|
|
126
|
+
plan features, refused with the reason where the plan lacks them. The tool description tells the model never
|
|
127
|
+
to invite an address the person did not give it.
|
|
128
|
+
|
|
120
129
|
The comment tools close the loop on a site somebody is reviewing: `list_comments` gives each thread's page
|
|
121
130
|
and the words or element it points at, so the source can be found; after the fix is published,
|
|
122
131
|
`reply_comment` answers on the page. Publish first and reply second, or the commenter opens the old
|
package/dist/index.js
CHANGED
|
@@ -177,10 +177,7 @@ async function folderFromListing(credentials, siteId, subdomain) {
|
|
|
177
177
|
}
|
|
178
178
|
async function getSite(credentials, siteId) {
|
|
179
179
|
const [site, invitedOnly] = await Promise.all([
|
|
180
|
-
call(
|
|
181
|
-
credentials,
|
|
182
|
-
`sites/${encodeURIComponent(siteId)}`
|
|
183
|
-
),
|
|
180
|
+
call(credentials, `sites/${encodeURIComponent(siteId)}`),
|
|
184
181
|
readInvitedOnly(credentials, siteId)
|
|
185
182
|
]);
|
|
186
183
|
const folderId = site.folderId !== void 0 ? site.folderId : await folderFromListing(credentials, site.siteId, site.subdomain);
|
|
@@ -202,6 +199,8 @@ async function getSite(credentials, siteId) {
|
|
|
202
199
|
expiresAt: site.expiresAt,
|
|
203
200
|
expiryAction: actionOf(site.expiresAt, site.expiryAction ?? ""),
|
|
204
201
|
scheduledExpiryAvailable: site.scheduledExpiryAvailable,
|
|
202
|
+
comments: audienceOf(site.commentAudience),
|
|
203
|
+
commentsAvailable: site.feedbackAvailable === true,
|
|
205
204
|
folderId,
|
|
206
205
|
deploys: site.deploys.map((deploy2) => ({
|
|
207
206
|
deployId: deploy2.deployId,
|
|
@@ -226,9 +225,11 @@ async function updateSiteSettings(credentials, siteId, change) {
|
|
|
226
225
|
if (change.expiryAction !== void 0) body.expiryAction = change.expiryAction;
|
|
227
226
|
if (change.formsEnabled !== void 0) body.formsEnabled = change.formsEnabled;
|
|
228
227
|
if (change.folderId !== void 0) body.folderId = change.folderId;
|
|
228
|
+
if (change.invitedOnly !== void 0) body.invitedOnly = change.invitedOnly;
|
|
229
|
+
if (change.comments !== void 0) body.commentAudience = API_AUDIENCE[change.comments];
|
|
229
230
|
if (Object.keys(body).length === 0) throw new Error("Nothing to change — name a setting.");
|
|
230
231
|
const wasInvitedOnly = change.password !== void 0 && change.password !== "" ? await readInvitedOnly(credentials, siteId) : null;
|
|
231
|
-
const settings = await call(credentials, `sites/${encodeURIComponent(siteId)}`, {
|
|
232
|
+
const { commentAudience, ...settings } = await call(credentials, `sites/${encodeURIComponent(siteId)}`, {
|
|
232
233
|
method: "PATCH",
|
|
233
234
|
body: JSON.stringify(body)
|
|
234
235
|
});
|
|
@@ -237,9 +238,19 @@ async function updateSiteSettings(credentials, siteId, change) {
|
|
|
237
238
|
...settings,
|
|
238
239
|
expiryAction: actionOf(settings.expiresAt, settings.expiryAction ?? ""),
|
|
239
240
|
invitedOnly,
|
|
241
|
+
comments: audienceOf(commentAudience),
|
|
240
242
|
replacedInviteOnly: wasInvitedOnly === true && invitedOnly === false
|
|
241
243
|
};
|
|
242
244
|
}
|
|
245
|
+
const API_AUDIENCE = {
|
|
246
|
+
off: "Off",
|
|
247
|
+
invited: "Invited",
|
|
248
|
+
anyone: "Anyone"
|
|
249
|
+
};
|
|
250
|
+
function audienceOf(value) {
|
|
251
|
+
const lower = value?.toLowerCase();
|
|
252
|
+
return lower === "off" || lower === "invited" || lower === "anyone" ? lower : null;
|
|
253
|
+
}
|
|
243
254
|
async function setSitePaused(credentials, siteId, action) {
|
|
244
255
|
const state = await call(
|
|
245
256
|
credentials,
|
|
@@ -354,6 +365,34 @@ async function setCommentResolved(credentials, siteId, threadId, resolved) {
|
|
|
354
365
|
{ method: "POST" }
|
|
355
366
|
);
|
|
356
367
|
}
|
|
368
|
+
const PEOPLE_PER_REQUEST = 50;
|
|
369
|
+
async function listPeople(credentials, siteId) {
|
|
370
|
+
const page = await call(credentials, `sites/${encodeURIComponent(siteId)}/viewers?pageSize=${PEOPLE_PER_REQUEST}`);
|
|
371
|
+
return {
|
|
372
|
+
invitedOnly: page.invitedOnly,
|
|
373
|
+
total: page.total,
|
|
374
|
+
limit: page.limit,
|
|
375
|
+
people: page.viewers.map((row) => ({
|
|
376
|
+
email: row.email,
|
|
377
|
+
role: enumName(row.role),
|
|
378
|
+
owner: row.owner,
|
|
379
|
+
status: enumName(row.status)
|
|
380
|
+
}))
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
async function invitePeople(credentials, siteId, emails, role) {
|
|
384
|
+
return await call(credentials, `sites/${encodeURIComponent(siteId)}/viewers`, {
|
|
385
|
+
method: "POST",
|
|
386
|
+
body: JSON.stringify({ emails, role: role === "comment" ? "Comment" : "View", notify: true })
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
async function removePerson(credentials, siteId, email) {
|
|
390
|
+
await call(
|
|
391
|
+
credentials,
|
|
392
|
+
`sites/${encodeURIComponent(siteId)}/viewers?email=${encodeURIComponent(email)}`,
|
|
393
|
+
{ method: "DELETE" }
|
|
394
|
+
);
|
|
395
|
+
}
|
|
357
396
|
const VERSIONS_LISTED = 10;
|
|
358
397
|
function servingModeOf$1(site) {
|
|
359
398
|
if (site.spaMode) return "spa";
|
|
@@ -382,6 +421,7 @@ function describeSite(site, folderPath) {
|
|
|
382
421
|
` mode ${servingModeOf$1(site)}${site.modeIsManual ? "" : " (detected on each publish)"}`,
|
|
383
422
|
` password ${site.passwordProtected ? "on" : "off"}${unavailable(site.passwordProtectionAvailable)}`,
|
|
384
423
|
` forms ${site.formsEnabled ? "on" : "off"}${unavailable(site.formsAvailable)}`,
|
|
424
|
+
` comments ${site.comments ?? "unknown"}${unavailable(site.commentsAvailable)}`,
|
|
385
425
|
` takedown ${site.expiresAt === null ? "none" : `${site.expiresAt} (${site.expiryAction})`}${unavailable(site.scheduledExpiryAvailable)}`,
|
|
386
426
|
` folder ${site.folderId === null ? "(top level)" : folderPath ?? site.folderId}`,
|
|
387
427
|
"",
|
|
@@ -816,13 +856,14 @@ const FILE_TYPES = [
|
|
|
816
856
|
reader: true,
|
|
817
857
|
editable: true
|
|
818
858
|
},
|
|
819
|
-
// Source the viewer highlights.
|
|
859
|
+
// Source the viewer highlights. Inside a documents site a link straight to one opens it in the
|
|
860
|
+
// reader; on an ordinary site, and with `?raw`, it hands over the file.
|
|
820
861
|
{
|
|
821
862
|
ext: ".css",
|
|
822
863
|
mime: "text/css; charset=utf-8",
|
|
823
864
|
kind: "code",
|
|
824
865
|
document: false,
|
|
825
|
-
reader:
|
|
866
|
+
reader: true,
|
|
826
867
|
editable: true
|
|
827
868
|
},
|
|
828
869
|
{
|
|
@@ -830,7 +871,7 @@ const FILE_TYPES = [
|
|
|
830
871
|
mime: "text/javascript; charset=utf-8",
|
|
831
872
|
kind: "code",
|
|
832
873
|
document: false,
|
|
833
|
-
reader:
|
|
874
|
+
reader: true,
|
|
834
875
|
editable: true
|
|
835
876
|
},
|
|
836
877
|
{
|
|
@@ -838,7 +879,7 @@ const FILE_TYPES = [
|
|
|
838
879
|
mime: null,
|
|
839
880
|
kind: "code",
|
|
840
881
|
document: false,
|
|
841
|
-
reader:
|
|
882
|
+
reader: true,
|
|
842
883
|
editable: false
|
|
843
884
|
},
|
|
844
885
|
{
|
|
@@ -846,7 +887,7 @@ const FILE_TYPES = [
|
|
|
846
887
|
mime: null,
|
|
847
888
|
kind: "code",
|
|
848
889
|
document: false,
|
|
849
|
-
reader:
|
|
890
|
+
reader: true,
|
|
850
891
|
editable: false
|
|
851
892
|
},
|
|
852
893
|
{
|
|
@@ -854,7 +895,7 @@ const FILE_TYPES = [
|
|
|
854
895
|
mime: null,
|
|
855
896
|
kind: "code",
|
|
856
897
|
document: false,
|
|
857
|
-
reader:
|
|
898
|
+
reader: true,
|
|
858
899
|
editable: false
|
|
859
900
|
},
|
|
860
901
|
{
|
|
@@ -862,19 +903,18 @@ const FILE_TYPES = [
|
|
|
862
903
|
mime: null,
|
|
863
904
|
kind: "code",
|
|
864
905
|
document: false,
|
|
865
|
-
reader:
|
|
906
|
+
reader: true,
|
|
866
907
|
editable: false
|
|
867
908
|
},
|
|
868
909
|
// The languages an assistant writes most, beyond the web's own. Same row as `.ts`: shown as source
|
|
869
|
-
//
|
|
870
|
-
// link names it directly, and no media type — adding one changes what a browser does with a
|
|
910
|
+
// in the reader, handed over as a file with `?raw` or on an ordinary site, and no media type — adding one changes what a browser does with a
|
|
871
911
|
// published file, which I10 makes a decision of its own rather than a side effect of this list.
|
|
872
912
|
{
|
|
873
913
|
ext: ".py",
|
|
874
914
|
mime: null,
|
|
875
915
|
kind: "code",
|
|
876
916
|
document: false,
|
|
877
|
-
reader:
|
|
917
|
+
reader: true,
|
|
878
918
|
editable: false
|
|
879
919
|
},
|
|
880
920
|
{
|
|
@@ -882,7 +922,7 @@ const FILE_TYPES = [
|
|
|
882
922
|
mime: null,
|
|
883
923
|
kind: "code",
|
|
884
924
|
document: false,
|
|
885
|
-
reader:
|
|
925
|
+
reader: true,
|
|
886
926
|
editable: false
|
|
887
927
|
},
|
|
888
928
|
{
|
|
@@ -890,7 +930,7 @@ const FILE_TYPES = [
|
|
|
890
930
|
mime: null,
|
|
891
931
|
kind: "code",
|
|
892
932
|
document: false,
|
|
893
|
-
reader:
|
|
933
|
+
reader: true,
|
|
894
934
|
editable: false
|
|
895
935
|
},
|
|
896
936
|
{
|
|
@@ -898,7 +938,7 @@ const FILE_TYPES = [
|
|
|
898
938
|
mime: null,
|
|
899
939
|
kind: "code",
|
|
900
940
|
document: false,
|
|
901
|
-
reader:
|
|
941
|
+
reader: true,
|
|
902
942
|
editable: false
|
|
903
943
|
},
|
|
904
944
|
{
|
|
@@ -906,7 +946,7 @@ const FILE_TYPES = [
|
|
|
906
946
|
mime: null,
|
|
907
947
|
kind: "code",
|
|
908
948
|
document: false,
|
|
909
|
-
reader:
|
|
949
|
+
reader: true,
|
|
910
950
|
editable: false
|
|
911
951
|
},
|
|
912
952
|
{
|
|
@@ -914,7 +954,7 @@ const FILE_TYPES = [
|
|
|
914
954
|
mime: null,
|
|
915
955
|
kind: "code",
|
|
916
956
|
document: false,
|
|
917
|
-
reader:
|
|
957
|
+
reader: true,
|
|
918
958
|
editable: false
|
|
919
959
|
},
|
|
920
960
|
{
|
|
@@ -922,7 +962,7 @@ const FILE_TYPES = [
|
|
|
922
962
|
mime: null,
|
|
923
963
|
kind: "code",
|
|
924
964
|
document: false,
|
|
925
|
-
reader:
|
|
965
|
+
reader: true,
|
|
926
966
|
editable: false
|
|
927
967
|
},
|
|
928
968
|
{
|
|
@@ -930,7 +970,7 @@ const FILE_TYPES = [
|
|
|
930
970
|
mime: null,
|
|
931
971
|
kind: "code",
|
|
932
972
|
document: false,
|
|
933
|
-
reader:
|
|
973
|
+
reader: true,
|
|
934
974
|
editable: false
|
|
935
975
|
},
|
|
936
976
|
{
|
|
@@ -938,7 +978,7 @@ const FILE_TYPES = [
|
|
|
938
978
|
mime: null,
|
|
939
979
|
kind: "code",
|
|
940
980
|
document: false,
|
|
941
|
-
reader:
|
|
981
|
+
reader: true,
|
|
942
982
|
editable: false
|
|
943
983
|
},
|
|
944
984
|
{
|
|
@@ -946,7 +986,7 @@ const FILE_TYPES = [
|
|
|
946
986
|
mime: null,
|
|
947
987
|
kind: "code",
|
|
948
988
|
document: false,
|
|
949
|
-
reader:
|
|
989
|
+
reader: true,
|
|
950
990
|
editable: false
|
|
951
991
|
},
|
|
952
992
|
{
|
|
@@ -954,7 +994,7 @@ const FILE_TYPES = [
|
|
|
954
994
|
mime: null,
|
|
955
995
|
kind: "code",
|
|
956
996
|
document: false,
|
|
957
|
-
reader:
|
|
997
|
+
reader: true,
|
|
958
998
|
editable: false
|
|
959
999
|
},
|
|
960
1000
|
{
|
|
@@ -962,7 +1002,7 @@ const FILE_TYPES = [
|
|
|
962
1002
|
mime: null,
|
|
963
1003
|
kind: "code",
|
|
964
1004
|
document: false,
|
|
965
|
-
reader:
|
|
1005
|
+
reader: true,
|
|
966
1006
|
editable: false
|
|
967
1007
|
},
|
|
968
1008
|
{
|
|
@@ -970,7 +1010,7 @@ const FILE_TYPES = [
|
|
|
970
1010
|
mime: null,
|
|
971
1011
|
kind: "code",
|
|
972
1012
|
document: false,
|
|
973
|
-
reader:
|
|
1013
|
+
reader: true,
|
|
974
1014
|
editable: false
|
|
975
1015
|
},
|
|
976
1016
|
{
|
|
@@ -978,7 +1018,7 @@ const FILE_TYPES = [
|
|
|
978
1018
|
mime: null,
|
|
979
1019
|
kind: "code",
|
|
980
1020
|
document: false,
|
|
981
|
-
reader:
|
|
1021
|
+
reader: true,
|
|
982
1022
|
editable: false
|
|
983
1023
|
},
|
|
984
1024
|
{
|
|
@@ -986,7 +1026,7 @@ const FILE_TYPES = [
|
|
|
986
1026
|
mime: "application/xml",
|
|
987
1027
|
kind: "code",
|
|
988
1028
|
document: false,
|
|
989
|
-
reader:
|
|
1029
|
+
reader: true,
|
|
990
1030
|
editable: true
|
|
991
1031
|
},
|
|
992
1032
|
// Pictures, shown with an img element.
|
|
@@ -1057,25 +1097,25 @@ const FILE_TYPES = [
|
|
|
1057
1097
|
reader: false,
|
|
1058
1098
|
editable: false
|
|
1059
1099
|
},
|
|
1060
|
-
// A customer's own pages.
|
|
1100
|
+
// A customer's own pages. Inside a documents site a navigation opens the reader, which frames the
|
|
1101
|
+
// page; `?raw` and every ordinary site serve the page itself.
|
|
1061
1102
|
{
|
|
1062
1103
|
ext: ".html",
|
|
1063
1104
|
mime: "text/html; charset=utf-8",
|
|
1064
1105
|
kind: "html",
|
|
1065
1106
|
document: false,
|
|
1066
|
-
reader:
|
|
1107
|
+
reader: true,
|
|
1067
1108
|
editable: true
|
|
1068
1109
|
},
|
|
1069
1110
|
{
|
|
1070
|
-
//
|
|
1071
|
-
//
|
|
1072
|
-
//
|
|
1073
|
-
// introduced as a refactor and a refactor that changes what a URL answers is not one.
|
|
1111
|
+
// An HTML page in every respect the Worker decides by extension: media type, the revalidated
|
|
1112
|
+
// cache policy invariant I7 requires, and the badge's `isHtml`. Missing from `TYPES` it used to
|
|
1113
|
+
// download, which inside the reader meant a frame that downloaded instead of showing a page.
|
|
1074
1114
|
ext: ".htm",
|
|
1075
|
-
mime:
|
|
1115
|
+
mime: "text/html; charset=utf-8",
|
|
1076
1116
|
kind: "html",
|
|
1077
1117
|
document: false,
|
|
1078
|
-
reader:
|
|
1118
|
+
reader: true,
|
|
1079
1119
|
editable: true
|
|
1080
1120
|
},
|
|
1081
1121
|
// Everything below renders as nothing — the viewer offers a download — but still needs a media
|
|
@@ -2154,7 +2194,7 @@ async function signInWithCode(waitSeconds, replace = false) {
|
|
|
2154
2194
|
"Call this tool again to keep waiting. Nothing has been changed yet."
|
|
2155
2195
|
].join("\n");
|
|
2156
2196
|
}
|
|
2157
|
-
const SERVER_VERSION = "0.
|
|
2197
|
+
const SERVER_VERSION = "0.11.0";
|
|
2158
2198
|
const CREDENTIAL_WRITE = {
|
|
2159
2199
|
readOnlyHint: false,
|
|
2160
2200
|
destructiveHint: false,
|
|
@@ -2204,6 +2244,12 @@ const ANSWERS_A_COMMENT = {
|
|
|
2204
2244
|
idempotentHint: false,
|
|
2205
2245
|
openWorldHint: true
|
|
2206
2246
|
};
|
|
2247
|
+
const INVITES_PEOPLE = {
|
|
2248
|
+
readOnlyHint: false,
|
|
2249
|
+
destructiveHint: false,
|
|
2250
|
+
idempotentHint: true,
|
|
2251
|
+
openWorldHint: true
|
|
2252
|
+
};
|
|
2207
2253
|
const MARKS_A_THREAD = {
|
|
2208
2254
|
readOnlyHint: false,
|
|
2209
2255
|
destructiveHint: false,
|
|
@@ -2292,6 +2338,9 @@ const SETTINGS_OUTPUT = {
|
|
|
2292
2338
|
"Whether only invited people can open it, or null when that could not be read. Null does not mean public."
|
|
2293
2339
|
),
|
|
2294
2340
|
formsEnabled: z.boolean().describe("Whether it accepts form submissions."),
|
|
2341
|
+
comments: z.enum(["off", "invited", "anyone"]).nullable().describe(
|
|
2342
|
+
"Who may comment on its pages: nobody, the people invited to comment (only while it is invite-only), or anybody signed in. Null when that could not be read."
|
|
2343
|
+
),
|
|
2295
2344
|
expiresAt: z.string().nullable().describe("When it is scheduled to come down, or null."),
|
|
2296
2345
|
expiryAction: z.string().nullable().describe("What the takedown does: `pause` or `delete`, or null when none is scheduled."),
|
|
2297
2346
|
folderId: z.string().nullable().describe("The folder it is filed in, or null at the top level.")
|
|
@@ -2320,10 +2369,12 @@ function structuredSite(site) {
|
|
|
2320
2369
|
expiresAt: site.expiresAt,
|
|
2321
2370
|
expiryAction: site.expiryAction,
|
|
2322
2371
|
folderId: site.folderId,
|
|
2372
|
+
comments: site.comments,
|
|
2323
2373
|
available: {
|
|
2324
2374
|
password: site.passwordProtectionAvailable,
|
|
2325
2375
|
forms: site.formsAvailable,
|
|
2326
|
-
scheduledTakedown: site.scheduledExpiryAvailable
|
|
2376
|
+
scheduledTakedown: site.scheduledExpiryAvailable,
|
|
2377
|
+
comments: site.commentsAvailable
|
|
2327
2378
|
},
|
|
2328
2379
|
versions: site.deploys.map((deploy2) => ({
|
|
2329
2380
|
deployId: deploy2.deployId,
|
|
@@ -2426,6 +2477,12 @@ function createServer() {
|
|
|
2426
2477
|
"a password or a date only when the person gave one, and scheduling a deletion asks for",
|
|
2427
2478
|
"`confirm` the way delete_site does.",
|
|
2428
2479
|
"",
|
|
2480
|
+
"To share a site for review: invite_people with the addresses the person gave (each is",
|
|
2481
|
+
"emailed), update_site `invitedOnly: true` so only they can open it, and `comments` to let",
|
|
2482
|
+
"them comment — `invited` for the people invited with the comment role, `anyone` for",
|
|
2483
|
+
"anybody signed in. list_people shows the list; remove_person takes someone off it.",
|
|
2484
|
+
"Never invite an address the person did not give you.",
|
|
2485
|
+
"",
|
|
2429
2486
|
"People viewing a site can leave comments pinned to its pages. list_comments reads them,",
|
|
2430
2487
|
"with the page and the words each one points at, so the source can be found and changed.",
|
|
2431
2488
|
'After changing it, publish first and reply second — a reply that says "fixed" before the',
|
|
@@ -2597,7 +2654,8 @@ function createServer() {
|
|
|
2597
2654
|
available: z.object({
|
|
2598
2655
|
password: z.boolean(),
|
|
2599
2656
|
forms: z.boolean(),
|
|
2600
|
-
scheduledTakedown: z.boolean()
|
|
2657
|
+
scheduledTakedown: z.boolean(),
|
|
2658
|
+
comments: z.boolean()
|
|
2601
2659
|
}).describe("Which settings the account's plan includes."),
|
|
2602
2660
|
versions: z.array(VERSION_OUTPUT).describe("Its versions, newest first.")
|
|
2603
2661
|
},
|
|
@@ -2636,6 +2694,12 @@ function createServer() {
|
|
|
2636
2694
|
folder: z.string().optional().describe(
|
|
2637
2695
|
"Folder to move the site to — a path such as Clients/Acme or an id from list_folders — or `root` for the top level. Never creates a folder."
|
|
2638
2696
|
),
|
|
2697
|
+
invitedOnly: z.boolean().optional().describe(
|
|
2698
|
+
"True: only the people on the site's list (invite_people) can open it, each signed in. It replaces a password. False: anybody with the link can open it again. Change it only when the person asked who should see the site."
|
|
2699
|
+
),
|
|
2700
|
+
comments: z.enum(["off", "invited", "anyone"]).optional().describe(
|
|
2701
|
+
"Who may comment on the site's pages: `off`; `invited` — the people invited with the comment role, on an invite-only site; or `anyone` signed in. Only on plans that include comments."
|
|
2702
|
+
),
|
|
2639
2703
|
confirm: z.string().optional().describe(
|
|
2640
2704
|
"Required only with `expiryAction: delete`: the site's subdomain, repeated exactly as the person said it. Ask for it rather than filling it in."
|
|
2641
2705
|
)
|
|
@@ -2643,7 +2707,19 @@ function createServer() {
|
|
|
2643
2707
|
outputSchema: SETTINGS_OUTPUT,
|
|
2644
2708
|
annotations: CHANGES_A_SITE
|
|
2645
2709
|
},
|
|
2646
|
-
({
|
|
2710
|
+
({
|
|
2711
|
+
site,
|
|
2712
|
+
name,
|
|
2713
|
+
mode,
|
|
2714
|
+
password,
|
|
2715
|
+
forms,
|
|
2716
|
+
expiresAt,
|
|
2717
|
+
expiryAction,
|
|
2718
|
+
folder,
|
|
2719
|
+
invitedOnly,
|
|
2720
|
+
comments,
|
|
2721
|
+
confirm
|
|
2722
|
+
}) => withCredentials(async (credentials) => {
|
|
2647
2723
|
const found = await findSite(credentials, site);
|
|
2648
2724
|
if (expiryAction !== void 0 && expiresAt === void 0) {
|
|
2649
2725
|
throw new Error("`expiryAction` only applies with `expiresAt` — send the date too.");
|
|
@@ -2662,7 +2738,9 @@ function createServer() {
|
|
|
2662
2738
|
// `delete` alongside an empty date would still run its owner-only check for nothing.
|
|
2663
2739
|
expiryAction: expiresAt === "" ? void 0 : expiryAction,
|
|
2664
2740
|
formsEnabled: forms,
|
|
2665
|
-
folderId: folder === void 0 ? void 0 : await resolveFolderTarget(credentials, folder)
|
|
2741
|
+
folderId: folder === void 0 ? void 0 : await resolveFolderTarget(credentials, folder),
|
|
2742
|
+
invitedOnly,
|
|
2743
|
+
comments
|
|
2666
2744
|
};
|
|
2667
2745
|
const settings = await updateSiteSettings(credentials, found.siteId, change);
|
|
2668
2746
|
const lines = [
|
|
@@ -2670,9 +2748,15 @@ function createServer() {
|
|
|
2670
2748
|
` access ${accessOf(settings)}`,
|
|
2671
2749
|
` mode ${servingModeOf$1(settings)}`,
|
|
2672
2750
|
` forms ${settings.formsEnabled ? "on" : "off"}`,
|
|
2751
|
+
` comments ${settings.comments ?? "unknown"}`,
|
|
2752
|
+
// The one combination that reads as on and does nothing: invited commenters on a site anybody
|
|
2753
|
+
// can open, which the API keeps (it is the owner's choice for when the site goes private).
|
|
2754
|
+
...settings.comments === "invited" && settings.invitedOnly === false ? [
|
|
2755
|
+
"Comments are set to invited people, but the site is not invite-only, so nobody can comment yet. Set `invitedOnly: true`, or `comments: anyone`."
|
|
2756
|
+
] : [],
|
|
2673
2757
|
` takedown ${settings.expiresAt === null ? "none" : `${settings.expiresAt} (${settings.expiryAction})`}`,
|
|
2674
2758
|
...settings.replacedInviteOnly ? [
|
|
2675
|
-
"The password replaced invite-only: the people on its list can no longer get in without it. The list is kept
|
|
2759
|
+
"The password replaced invite-only: the people on its list can no longer get in without it. The list is kept; `invitedOnly: true` switches back."
|
|
2676
2760
|
] : [],
|
|
2677
2761
|
...settings.live ? ["Reaching every edge takes up to about a minute."] : ["Nothing is published yet, so this applies from the first publish."]
|
|
2678
2762
|
];
|
|
@@ -2683,6 +2767,7 @@ function createServer() {
|
|
|
2683
2767
|
passwordProtected: settings.passwordProtected,
|
|
2684
2768
|
invitedOnly: settings.invitedOnly,
|
|
2685
2769
|
formsEnabled: settings.formsEnabled,
|
|
2770
|
+
comments: settings.comments,
|
|
2686
2771
|
expiresAt: settings.expiresAt,
|
|
2687
2772
|
expiryAction: settings.expiryAction,
|
|
2688
2773
|
folderId: settings.folderId
|
|
@@ -2748,6 +2833,94 @@ ${promoted.url}`,
|
|
|
2748
2833
|
);
|
|
2749
2834
|
})
|
|
2750
2835
|
);
|
|
2836
|
+
server.registerTool(
|
|
2837
|
+
"list_people",
|
|
2838
|
+
{
|
|
2839
|
+
title: "List who a site is shared with",
|
|
2840
|
+
description: "Lists the people on a site's list — the ones invite_people added — with what each may do (`view` or `comment`) and whether they have opened it yet. Says whether the list decides who can open the site, which it does only while the site is invite-only.",
|
|
2841
|
+
inputSchema: { site: SITE_INPUT },
|
|
2842
|
+
outputSchema: {
|
|
2843
|
+
invitedOnly: z.boolean().describe("Whether only the people on the list can open the site now."),
|
|
2844
|
+
total: z.number().int().describe("How many are on the list."),
|
|
2845
|
+
limit: z.number().int().describe("The most the plan allows on one site's list."),
|
|
2846
|
+
people: z.array(
|
|
2847
|
+
z.object({
|
|
2848
|
+
email: z.string(),
|
|
2849
|
+
role: z.string().describe("`view` or `comment`."),
|
|
2850
|
+
owner: z.boolean().describe("Whether this is the site's owner."),
|
|
2851
|
+
status: z.string().describe("`opened` or `not-opened`.")
|
|
2852
|
+
})
|
|
2853
|
+
)
|
|
2854
|
+
},
|
|
2855
|
+
annotations: READS_ONLY
|
|
2856
|
+
},
|
|
2857
|
+
({ site }) => withCredentials(async (credentials) => {
|
|
2858
|
+
const found = await findSite(credentials, site);
|
|
2859
|
+
const list = await listPeople(credentials, found.siteId);
|
|
2860
|
+
const rows = list.people.map(
|
|
2861
|
+
(person) => ` ${person.email} ${person.role}${person.owner ? ", owner" : ""}, ${person.status}`
|
|
2862
|
+
);
|
|
2863
|
+
return report(
|
|
2864
|
+
[
|
|
2865
|
+
`${list.total} of ${list.limit} on ${found.subdomain}'s list. ` + (list.invitedOnly ? "Only they can open it." : "It is not invite-only, so anybody with the link can open it."),
|
|
2866
|
+
...rows,
|
|
2867
|
+
...list.total > list.people.length ? [` … and ${list.total - list.people.length} more, in the dashboard`] : []
|
|
2868
|
+
].join("\n"),
|
|
2869
|
+
{ ...list }
|
|
2870
|
+
);
|
|
2871
|
+
})
|
|
2872
|
+
);
|
|
2873
|
+
server.registerTool(
|
|
2874
|
+
"invite_people",
|
|
2875
|
+
{
|
|
2876
|
+
title: "Invite people to a site",
|
|
2877
|
+
description: "Adds email addresses to a site's list and emails each new one an invitation from the account. Use only addresses the person gave you — never guess, infer or look one up. `comment` lets them comment as well as open it. Inviting does not make the site private; update_site's `invitedOnly: true` does. On plans with private sharing; all or nothing — one bad address, or a list past the plan's limit, adds nobody.",
|
|
2878
|
+
inputSchema: {
|
|
2879
|
+
site: SITE_INPUT,
|
|
2880
|
+
emails: z.array(z.string().min(3)).min(1).max(50).describe("The addresses, exactly as the person gave them."),
|
|
2881
|
+
role: z.enum(["view", "comment"]).optional().describe("`view` (the default) to open the site, `comment` to open it and comment.")
|
|
2882
|
+
},
|
|
2883
|
+
outputSchema: {
|
|
2884
|
+
added: z.array(z.string()).describe("Addresses added and emailed."),
|
|
2885
|
+
alreadyListed: z.array(z.string()).describe("Addresses already on the list, left as they were and not emailed again."),
|
|
2886
|
+
total: z.number().int().describe("How many are on the list now.")
|
|
2887
|
+
},
|
|
2888
|
+
annotations: INVITES_PEOPLE
|
|
2889
|
+
},
|
|
2890
|
+
({ site, emails, role }) => withCredentials(async (credentials) => {
|
|
2891
|
+
const found = await findSite(credentials, site);
|
|
2892
|
+
const invited = await invitePeople(credentials, found.siteId, emails, role ?? "view");
|
|
2893
|
+
const detail = await getSite(credentials, found.siteId);
|
|
2894
|
+
return report(
|
|
2895
|
+
[
|
|
2896
|
+
invited.added.length === 0 ? `Nobody new on ${found.subdomain}'s list.` : `Invited ${invited.added.join(", ")} to ${found.subdomain} — each was emailed.`,
|
|
2897
|
+
...invited.alreadyListed.length === 0 ? [] : [`Already on the list, not emailed again: ${invited.alreadyListed.join(", ")}.`],
|
|
2898
|
+
...detail.invitedOnly === false ? [
|
|
2899
|
+
"The site is not invite-only yet, so the list does not decide who opens it. update_site with `invitedOnly: true` does."
|
|
2900
|
+
] : []
|
|
2901
|
+
].join("\n"),
|
|
2902
|
+
{ ...invited }
|
|
2903
|
+
);
|
|
2904
|
+
})
|
|
2905
|
+
);
|
|
2906
|
+
server.registerTool(
|
|
2907
|
+
"remove_person",
|
|
2908
|
+
{
|
|
2909
|
+
title: "Remove someone from a site's list",
|
|
2910
|
+
description: "Takes one address off a site's list. On an invite-only site their access ends within a minute; putting them back is another invitation, with another email.",
|
|
2911
|
+
inputSchema: {
|
|
2912
|
+
site: SITE_INPUT,
|
|
2913
|
+
email: z.string().min(3).describe("The address to remove, from list_people.")
|
|
2914
|
+
},
|
|
2915
|
+
outputSchema: { email: z.string().describe("The address removed.") },
|
|
2916
|
+
annotations: CHANGES_A_SITE
|
|
2917
|
+
},
|
|
2918
|
+
({ site, email }) => withCredentials(async (credentials) => {
|
|
2919
|
+
const found = await findSite(credentials, site);
|
|
2920
|
+
await removePerson(credentials, found.siteId, email);
|
|
2921
|
+
return report(`Removed ${email} from ${found.subdomain}'s list.`, { email });
|
|
2922
|
+
})
|
|
2923
|
+
);
|
|
2751
2924
|
server.registerTool(
|
|
2752
2925
|
"list_comments",
|
|
2753
2926
|
{
|
package/package.json
CHANGED