@okostream/mcp 0.12.4 → 0.12.6
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 +8 -4
- package/dist/server.js +115 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,10 +96,14 @@ Refusals are explicit and never require guessing:
|
|
|
96
96
|
OKO team.
|
|
97
97
|
- `PLATFORM_REJECTED_READ` — no access to that resource, or it does not exist.
|
|
98
98
|
|
|
99
|
-
Image URLs are
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
Image URLs are fetched by the OKO server first, so a VPN, proxy or captive
|
|
100
|
+
portal on the organizer's machine is not in the way. If the server cannot
|
|
101
|
+
reach the URL (a private host, a login wall, a timeout), the MCP client
|
|
102
|
+
fetches it from the local machine; on a machine where a VPN or proxy
|
|
103
|
+
intercepts DNS, public hosts resolve to fake addresses (`198.18.x.x`), such
|
|
104
|
+
uploads still work, and the result says so. When both roads fail, the error
|
|
105
|
+
names both reasons and asks for a file from disk (`OKO_IMAGE_ROOTS`), which
|
|
106
|
+
always works. The result's `sourceVia` says who fetched the image.
|
|
103
107
|
|
|
104
108
|
## Capabilities
|
|
105
109
|
|
package/dist/server.js
CHANGED
|
@@ -116223,7 +116223,7 @@ function sanitizeScopes(scopes) {
|
|
|
116223
116223
|
}
|
|
116224
116224
|
|
|
116225
116225
|
// build/version.js
|
|
116226
|
-
var OKO_MCP_VERSION = "0.12.
|
|
116226
|
+
var OKO_MCP_VERSION = "0.12.6";
|
|
116227
116227
|
|
|
116228
116228
|
// build/auth.js
|
|
116229
116229
|
var state = null;
|
|
@@ -116465,6 +116465,15 @@ function isPublicUnicastAddress(address) {
|
|
|
116465
116465
|
return false;
|
|
116466
116466
|
return import_ipaddr.default.parse(normalized).range() === "unicast";
|
|
116467
116467
|
}
|
|
116468
|
+
function pinnedLookup(address, family) {
|
|
116469
|
+
return (_hostname, options2, callback) => {
|
|
116470
|
+
if (options2 && typeof options2 === "object" && options2.all) {
|
|
116471
|
+
callback(null, [{ address, family }]);
|
|
116472
|
+
return;
|
|
116473
|
+
}
|
|
116474
|
+
callback(null, address, family);
|
|
116475
|
+
};
|
|
116476
|
+
}
|
|
116468
116477
|
|
|
116469
116478
|
// build/tools/eventAccess.js
|
|
116470
116479
|
var EVENT_ACCESS_FIELD_BY_MODE = {
|
|
@@ -116640,9 +116649,7 @@ async function readRemoteImage(rawUrl, redirects = 0) {
|
|
|
116640
116649
|
remoteFetchNotes.push(`Image fetched through a VPN/proxy with DNS interception: ${url.hostname} resolved to ${pinned.address}, so OKO could not verify the destination address itself; the connection went through the proxy. This is expected on such machines and fine for public image links.`);
|
|
116641
116650
|
}
|
|
116642
116651
|
const dispatcher = new import_undici.Agent({
|
|
116643
|
-
connect: {
|
|
116644
|
-
lookup: (_hostname, _options, callback) => callback(null, pinned.address, pinned.family)
|
|
116645
|
-
}
|
|
116652
|
+
connect: { lookup: pinnedLookup(pinned.address, pinned.family) }
|
|
116646
116653
|
});
|
|
116647
116654
|
const controller = new AbortController();
|
|
116648
116655
|
const timeout = setTimeout(() => controller.abort(), REMOTE_IMAGE_TIMEOUT_MS);
|
|
@@ -116673,7 +116680,7 @@ async function readRemoteImage(rawUrl, redirects = 0) {
|
|
|
116673
116680
|
if (Number.isFinite(contentLength) && contentLength > MAX_SOURCE_IMAGE_BYTES) {
|
|
116674
116681
|
throw new Error("Image URL is larger than 15MB.");
|
|
116675
116682
|
}
|
|
116676
|
-
return responseBufferWithLimit(response, MAX_SOURCE_IMAGE_BYTES);
|
|
116683
|
+
return await responseBufferWithLimit(response, MAX_SOURCE_IMAGE_BYTES);
|
|
116677
116684
|
} catch (err) {
|
|
116678
116685
|
if (err instanceof Error && err.name === "AbortError") {
|
|
116679
116686
|
throw new Error("Image URL fetch timed out.");
|
|
@@ -116710,12 +116717,65 @@ async function readLocalImage(inputPath) {
|
|
|
116710
116717
|
throw new Error("Image file is larger than 15MB.");
|
|
116711
116718
|
return readFile(filePath);
|
|
116712
116719
|
}
|
|
116713
|
-
|
|
116714
|
-
if (
|
|
116715
|
-
return
|
|
116716
|
-
|
|
116717
|
-
|
|
116718
|
-
|
|
116720
|
+
function serverFetchRefusal(error3) {
|
|
116721
|
+
if (!error3 || typeof error3 !== "object")
|
|
116722
|
+
return null;
|
|
116723
|
+
const details = error3.details;
|
|
116724
|
+
if (details && typeof details === "object" && details.code === "REMOTE_FETCH_FAILED") {
|
|
116725
|
+
const record2 = details;
|
|
116726
|
+
const fallbackMessage = String(error3.message ?? "the server could not fetch the URL");
|
|
116727
|
+
return {
|
|
116728
|
+
reason: typeof record2.reason === "string" ? record2.reason : "unknown",
|
|
116729
|
+
message: typeof record2.message === "string" ? record2.message : fallbackMessage
|
|
116730
|
+
};
|
|
116731
|
+
}
|
|
116732
|
+
const code = error3.code;
|
|
116733
|
+
const message = String(error3.message ?? "");
|
|
116734
|
+
if (code === "functions/invalid-argument" && message.includes("Image payload is empty or too large")) {
|
|
116735
|
+
return { reason: "server_without_url_support", message: "The OKO server release in use does not fetch URLs yet." };
|
|
116736
|
+
}
|
|
116737
|
+
return null;
|
|
116738
|
+
}
|
|
116739
|
+
var SERVER_FETCH_NOTE = "Image fetched by the OKO server directly from the URL; this machine's network was not involved.";
|
|
116740
|
+
function localFallbackNote(serverFailure) {
|
|
116741
|
+
return `The OKO server could not fetch the URL (${serverFailure.reason}: ${serverFailure.message}), so the image was fetched from this machine instead.`;
|
|
116742
|
+
}
|
|
116743
|
+
function remoteImageUnavailableMessage(url, serverFailure, localError) {
|
|
116744
|
+
const localMessage = localError instanceof Error ? localError.message : String(localError);
|
|
116745
|
+
return `Image URL could not be fetched by the OKO server or from this machine. Server (${serverFailure.reason}): ${serverFailure.message} This machine: ${localMessage} Ask the organizer to download the image and pass its path via source.path (allowed directories come from OKO_IMAGE_ROOTS), or to share a public HTTPS link that returns the image file directly. URL: ${url}`;
|
|
116746
|
+
}
|
|
116747
|
+
async function uploadImageFromSource(eventId, kind, cardId, source) {
|
|
116748
|
+
if (source.path) {
|
|
116749
|
+
const buffer2 = await readLocalImage(source.path);
|
|
116750
|
+
const uploaded2 = await uploadMasterImage(eventId, kind, cardId, { imageBase64: buffer2.toString("base64") });
|
|
116751
|
+
return { uploaded: uploaded2, sourceBytes: buffer2.byteLength, sourceVia: "client", notes: [] };
|
|
116752
|
+
}
|
|
116753
|
+
if (!source.url)
|
|
116754
|
+
throw new Error("Provide image source path or url.");
|
|
116755
|
+
let serverFailure;
|
|
116756
|
+
try {
|
|
116757
|
+
const uploaded2 = await uploadMasterImage(eventId, kind, cardId, { sourceUrl: source.url });
|
|
116758
|
+
return { uploaded: uploaded2, sourceBytes: uploaded2.sourceBytes ?? 0, sourceVia: "server", notes: [SERVER_FETCH_NOTE] };
|
|
116759
|
+
} catch (error3) {
|
|
116760
|
+
const refusal = serverFetchRefusal(error3);
|
|
116761
|
+
if (!refusal)
|
|
116762
|
+
throw error3;
|
|
116763
|
+
serverFailure = refusal;
|
|
116764
|
+
}
|
|
116765
|
+
let buffer;
|
|
116766
|
+
try {
|
|
116767
|
+
buffer = await readRemoteImage(source.url);
|
|
116768
|
+
} catch (localError) {
|
|
116769
|
+
takeRemoteFetchNotes();
|
|
116770
|
+
throw new Error(remoteImageUnavailableMessage(source.url, serverFailure, localError));
|
|
116771
|
+
}
|
|
116772
|
+
const uploaded = await uploadMasterImage(eventId, kind, cardId, { imageBase64: buffer.toString("base64") });
|
|
116773
|
+
return {
|
|
116774
|
+
uploaded,
|
|
116775
|
+
sourceBytes: buffer.byteLength,
|
|
116776
|
+
sourceVia: "client",
|
|
116777
|
+
notes: [localFallbackNote(serverFailure), ...takeRemoteFetchNotes()]
|
|
116778
|
+
};
|
|
116719
116779
|
}
|
|
116720
116780
|
function readImageRef(eventData, kind, cardId) {
|
|
116721
116781
|
const blocks = eventData.blocks && typeof eventData.blocks === "object" ? eventData.blocks : {};
|
|
@@ -116853,8 +116913,8 @@ var cardItemInputSchema = external_exports.object({
|
|
|
116853
116913
|
formId: cardIdSchema.nullable().optional()
|
|
116854
116914
|
}).strict();
|
|
116855
116915
|
var imageSourceSchema = external_exports.object({
|
|
116856
|
-
path: external_exports.string().min(1).optional().describe("Local image path on the MCP client machine."),
|
|
116857
|
-
url: external_exports.string().url().optional().describe("HTTPS image URL
|
|
116916
|
+
path: external_exports.string().min(1).optional().describe("Local image path on the MCP client machine (inside OKO_IMAGE_ROOTS)."),
|
|
116917
|
+
url: external_exports.string().url().optional().describe("Public HTTPS image URL. The OKO server fetches it first (works behind VPNs and proxies); if the server cannot reach it, the MCP client fetches it from this machine; if both fail, ask the organizer for a local file via path.")
|
|
116858
116918
|
}).strict().refine((source) => Boolean(source.path) !== Boolean(source.url), {
|
|
116859
116919
|
message: "Provide exactly one image source: path or url."
|
|
116860
116920
|
});
|
|
@@ -116920,13 +116980,13 @@ async function deleteStorageUrls(eventId, masterUrl, fallbackUrl) {
|
|
|
116920
116980
|
const warnings = await deleteStoragePaths(eventId, paths);
|
|
116921
116981
|
return { attempted: [...new Set(paths)].length, warnings };
|
|
116922
116982
|
}
|
|
116923
|
-
async function uploadMasterImage(eventId, kind, cardId,
|
|
116983
|
+
async function uploadMasterImage(eventId, kind, cardId, source) {
|
|
116924
116984
|
const uploadImage = httpsCallable(functions, "uploadMcpImage");
|
|
116925
116985
|
const result = await uploadImage({
|
|
116926
116986
|
eventId,
|
|
116927
116987
|
kind,
|
|
116928
116988
|
...cardId ? { cardId } : {},
|
|
116929
|
-
imageBase64:
|
|
116989
|
+
...source.sourceUrl ? { sourceUrl: source.sourceUrl } : { imageBase64: source.imageBase64 }
|
|
116930
116990
|
});
|
|
116931
116991
|
return result.data;
|
|
116932
116992
|
}
|
|
@@ -117007,8 +117067,6 @@ async function uploadEventImageForUser(uid, eventId, kind, cardId, source) {
|
|
|
117007
117067
|
throw new Error("cardId is only valid when kind is card.");
|
|
117008
117068
|
const event = await readEvent(eventId, uid, "edit");
|
|
117009
117069
|
const previous = readImageRef(event, kind, cardId);
|
|
117010
|
-
const sourceBuffer = await readImageSource(source);
|
|
117011
|
-
const remoteNotes = takeRemoteFetchNotes();
|
|
117012
117070
|
const eventBlocks = event.blocks && typeof event.blocks === "object" ? event.blocks : {};
|
|
117013
117071
|
const placeholderCfg = eventBlocks.video?.placeholder && typeof eventBlocks.video.placeholder === "object" ? eventBlocks.video.placeholder : {};
|
|
117014
117072
|
const blockEnabled = kind === "promoBanner" ? eventBlocks.promoBanner?.enabled === true : kind === "videoPlaceholder" ? placeholderCfg.enabled === true : readCardsConfig(event).enabled;
|
|
@@ -117022,7 +117080,7 @@ async function uploadEventImageForUser(uid, eventId, kind, cardId, source) {
|
|
|
117022
117080
|
const previousMasterPath = storagePathFromDownloadUrl(previous.imageMasterUrl);
|
|
117023
117081
|
const deleteWarnings = [];
|
|
117024
117082
|
let deletesAttempted = 0;
|
|
117025
|
-
const uploaded = await
|
|
117083
|
+
const { uploaded, sourceBytes, sourceVia, notes: sourceNotes } = await uploadImageFromSource(eventId, kind, cardId, source);
|
|
117026
117084
|
if (!uploaded)
|
|
117027
117085
|
throw new Error("Image upload did not return a result.");
|
|
117028
117086
|
if (kind !== "card" || previousMasterPath && previousMasterPath !== uploaded.storagePath || !previous.imageMasterUrl && previous.imageUrl) {
|
|
@@ -117037,14 +117095,15 @@ async function uploadEventImageForUser(uid, eventId, kind, cardId, source) {
|
|
|
117037
117095
|
storagePath: uploaded.storagePath,
|
|
117038
117096
|
masterUrl: uploaded.masterUrl,
|
|
117039
117097
|
variantsPending: true,
|
|
117040
|
-
sourceBytes
|
|
117098
|
+
sourceBytes,
|
|
117099
|
+
sourceVia,
|
|
117041
117100
|
masterBytes: uploaded.masterBytes,
|
|
117042
117101
|
imageWidth: uploaded.imageWidth,
|
|
117043
117102
|
imageHeight: uploaded.imageHeight,
|
|
117044
117103
|
note: "Image master uploaded. CF generateImageVariants should publish thumb/card/hero variants and update imageUrl/imageVariants in about 10-30 seconds.",
|
|
117045
117104
|
storageDeletesAttempted: deletesAttempted,
|
|
117046
117105
|
storageDeleteWarnings: deleteWarnings,
|
|
117047
|
-
hints: [...
|
|
117106
|
+
hints: [...sourceNotes, ...uploadHints]
|
|
117048
117107
|
};
|
|
117049
117108
|
}
|
|
117050
117109
|
|
|
@@ -117186,6 +117245,7 @@ var imageOutputSchema = external_exports.object({
|
|
|
117186
117245
|
masterUrl: external_exports.string().url(),
|
|
117187
117246
|
variantsPending: external_exports.boolean(),
|
|
117188
117247
|
sourceBytes: external_exports.number().int().nonnegative(),
|
|
117248
|
+
sourceVia: external_exports.enum(["server", "client"]).optional(),
|
|
117189
117249
|
masterBytes: external_exports.number().int().nonnegative(),
|
|
117190
117250
|
imageWidth: external_exports.number().int().positive(),
|
|
117191
117251
|
imageHeight: external_exports.number().int().positive(),
|
|
@@ -117363,7 +117423,7 @@ var SCOPE_UNLOCKS = {
|
|
|
117363
117423
|
"chat:moderate": "chat mode, moderation, bans and mutes",
|
|
117364
117424
|
"telegram:write": "claiming channels and publishing Telegram posts",
|
|
117365
117425
|
"workspace:read": "listing workspaces, members, invites, notifications",
|
|
117366
|
-
"workspace:write": "workspace management, invites, profile edits
|
|
117426
|
+
"workspace:write": "workspace management, invites, profile edits",
|
|
117367
117427
|
"leads:read": "reading and exporting leads"
|
|
117368
117428
|
};
|
|
117369
117429
|
function presetsWithScope(scope) {
|
|
@@ -118033,6 +118093,25 @@ async function assertCanCreateInWorkspace(uid, workspaceId) {
|
|
|
118033
118093
|
}
|
|
118034
118094
|
}
|
|
118035
118095
|
}
|
|
118096
|
+
function buildCreateWebinarDocument(input) {
|
|
118097
|
+
return {
|
|
118098
|
+
ownerId: input.uid,
|
|
118099
|
+
workspaceId: input.workspaceId,
|
|
118100
|
+
createdAt: input.createdAt,
|
|
118101
|
+
title: input.title.trim(),
|
|
118102
|
+
pinned: false,
|
|
118103
|
+
video: input.video,
|
|
118104
|
+
meta: {
|
|
118105
|
+
eventDate: input.eventDate,
|
|
118106
|
+
speakerName: input.speakerName,
|
|
118107
|
+
description: input.description
|
|
118108
|
+
},
|
|
118109
|
+
blocks: {
|
|
118110
|
+
chat: { enabled: input.chatEnabled, visibility: "always" },
|
|
118111
|
+
promoBanner: { enabled: false, text: "", link: "", visibility: "always" }
|
|
118112
|
+
}
|
|
118113
|
+
};
|
|
118114
|
+
}
|
|
118036
118115
|
async function addEventWithRtdbMirror(eventData) {
|
|
118037
118116
|
const eventRef = await addDoc(collection(firestore, "events"), eventData);
|
|
118038
118117
|
try {
|
|
@@ -118317,23 +118396,17 @@ function registerEventTools(server2) {
|
|
|
118317
118396
|
const workspaceId = args.workspaceId ?? uid;
|
|
118318
118397
|
await assertCanCreateInWorkspace(uid, workspaceId);
|
|
118319
118398
|
const video = normalizeVideo(args.videoUrl);
|
|
118320
|
-
const docRef = await addEventWithRtdbMirror({
|
|
118321
|
-
|
|
118399
|
+
const docRef = await addEventWithRtdbMirror(buildCreateWebinarDocument({
|
|
118400
|
+
uid,
|
|
118322
118401
|
workspaceId,
|
|
118323
|
-
|
|
118324
|
-
title: args.title.trim(),
|
|
118325
|
-
pinned: false,
|
|
118402
|
+
title: args.title,
|
|
118326
118403
|
video,
|
|
118327
|
-
|
|
118328
|
-
|
|
118329
|
-
|
|
118330
|
-
|
|
118331
|
-
|
|
118332
|
-
|
|
118333
|
-
chat: { enabled: args.chatEnabled, visibility: "always" },
|
|
118334
|
-
promoBanner: { enabled: false, text: "", link: "", visibility: "always" }
|
|
118335
|
-
}
|
|
118336
|
-
});
|
|
118404
|
+
eventDate: args.eventDate ? Timestamp.fromDate(new Date(args.eventDate)) : null,
|
|
118405
|
+
speakerName: args.speakerName?.trim() || null,
|
|
118406
|
+
description: args.description?.trim() || null,
|
|
118407
|
+
chatEnabled: args.chatEnabled,
|
|
118408
|
+
createdAt: serverTimestamp2()
|
|
118409
|
+
}));
|
|
118337
118410
|
return textResult({
|
|
118338
118411
|
id: docRef.id,
|
|
118339
118412
|
...eventLinks(docRef.id),
|
|
@@ -118643,10 +118716,10 @@ function registerImageTools(server2) {
|
|
|
118643
118716
|
"eventId: event to edit.",
|
|
118644
118717
|
"kind: promoBanner, videoPlaceholder, or card.",
|
|
118645
118718
|
"cardId: required when kind=card; must match an existing blocks.cards.items id.",
|
|
118646
|
-
"source: exactly one of local path or HTTPS image URL.",
|
|
118719
|
+
"source: exactly one of local path or public HTTPS image URL.",
|
|
118647
118720
|
"confirm: must be true because the public event image may be replaced."
|
|
118648
118721
|
],
|
|
118649
|
-
returns: "eventId, kind, imageId, Storage path, masterUrl, byte counts, and a note that CF-generated variants are pending.",
|
|
118722
|
+
returns: "eventId, kind, imageId, Storage path, masterUrl, byte counts, sourceVia (server or client fetched the URL), hints, and a note that CF-generated variants are pending.",
|
|
118650
118723
|
examples: [
|
|
118651
118724
|
'Use kind="promoBanner" with source.url to set a webinar banner from an image link.',
|
|
118652
118725
|
'Use kind="card" with cardId after the card item already exists.'
|
|
@@ -118654,7 +118727,7 @@ function registerImageTools(server2) {
|
|
|
118654
118727
|
notes: [
|
|
118655
118728
|
"Uploading an image does not enable its block: toggle the block independently via oko_configure_blocks, in either order.",
|
|
118656
118729
|
"The MCP server reads local paths on the organizer machine.",
|
|
118657
|
-
"
|
|
118730
|
+
"A URL is fetched by the OKO server first (no VPN or proxy in the way), then from this machine if the server could not reach it; if both fail, the error names both reasons \u2014 ask the organizer to download the image and pass source.path. Public HTTPS only, max 15MB; private/local hosts are blocked.",
|
|
118658
118731
|
"For card images, imageId is the cardId so OKO CF4 can update the correct item.",
|
|
118659
118732
|
IMAGE_ADVICE.promoBanner,
|
|
118660
118733
|
IMAGE_ADVICE.videoPlaceholder,
|
|
@@ -118891,7 +118964,6 @@ async function recordToolAudit(toolName, outcome, args, startedAt, error3) {
|
|
|
118891
118964
|
var REPORT_URL = `${APP_ORIGIN}/contact`;
|
|
118892
118965
|
var KEYS_URL = `${APP_ORIGIN}/app/mcp`;
|
|
118893
118966
|
var SCOPE_WORKAROUNDS = {
|
|
118894
|
-
oko_move_event: "Without workspace:write you can still place the event in another workspace: call oko_duplicate_event with targetWorkspaceId to create a copy there. The original stays where it is; live overrides (chat mode, placeholder visibility) are not copied and must be set again on the copy.",
|
|
118895
118967
|
oko_trash_event: "This key cannot delete events. Tell the organizer which event to remove; they can do it in the OKO cabinet, or create a key with the full preset.",
|
|
118896
118968
|
oko_delete_event_permanently: "This key cannot delete events. Tell the organizer which event to remove; they can do it in the OKO cabinet, or create a key with the full preset.",
|
|
118897
118969
|
oko_publish_telegram_post: "This key cannot publish to Telegram. The organizer can publish the announcement from the OKO cabinet, or create a key with the full preset."
|
|
@@ -119205,7 +119277,7 @@ function registerLifecycleTools(server2) {
|
|
|
119205
119277
|
notes: [
|
|
119206
119278
|
"Copies title, video, meta, blocks, forms, and Telegram config; does not copy analytics sessions or leads.",
|
|
119207
119279
|
"Live overrides are NOT copied: the copy starts with chat mode public and the placeholder hidden, whatever the source had. Set them again with oko_set_chat_mode and oko_set_block_visibility on the new event.",
|
|
119208
|
-
"Requires only events:write plus createEvents in the target workspace
|
|
119280
|
+
"Requires only events:write plus createEvents in the target workspace. To move (not copy) an event, use oko_move_event with the same scope."
|
|
119209
119281
|
]
|
|
119210
119282
|
}),
|
|
119211
119283
|
inputSchema: {
|
|
@@ -119258,7 +119330,7 @@ function registerLifecycleTools(server2) {
|
|
|
119258
119330
|
examples: ["Use when an event belongs in another workspace. Call oko_list_workspaces first to choose the target."],
|
|
119259
119331
|
notes: [
|
|
119260
119332
|
"Reads target workspace shares from Firestore with permissions; does not use RTDB workspaceMembers.",
|
|
119261
|
-
"Requires
|
|
119333
|
+
"Requires events:write plus ownership of the event (or of its source workspace) and createEvents in the target workspace; no workspace:write needed. Live overrides (chat mode, placeholder) travel with the event: they live in the event's own RTDB node."
|
|
119262
119334
|
]
|
|
119263
119335
|
}),
|
|
119264
119336
|
inputSchema: {
|
|
@@ -119269,7 +119341,7 @@ function registerLifecycleTools(server2) {
|
|
|
119269
119341
|
outputSchema: updateOutputSchema,
|
|
119270
119342
|
annotations: writeTool
|
|
119271
119343
|
}, async ({ eventId, targetWorkspaceId, confirm }) => {
|
|
119272
|
-
await authed(["events:write"
|
|
119344
|
+
await authed(["events:write"]);
|
|
119273
119345
|
requireConfirm(confirm, "oko_move_event", { eventId, targetWorkspaceId });
|
|
119274
119346
|
const moveEvent = httpsCallable(functions, "moveEventToWorkspace");
|
|
119275
119347
|
const result = await moveEvent({ eventId, targetWorkspaceId });
|