@clubnet/seedclub 0.2.35 → 0.2.37
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/assets/extensions/seedclub/index.ts +26 -0
- package/assets/extensions/seedclub/tools/crm.ts +265 -0
- package/assets/extensions/seedclub/tools/media.ts +37 -233
- package/assets/extensions/seedclub/tools/meetings.ts +148 -141
- package/assets/extensions/seedclub/ui-copy.ts +12 -6
- package/package.json +1 -1
|
@@ -120,6 +120,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
120
120
|
for (const key of ENV_TOKEN_KEYS) delete process.env[key];
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
function isSeedClubInternalEmail(email: string | null | undefined) {
|
|
124
|
+
return !!email?.trim().toLowerCase().endsWith("@seedclub.com");
|
|
125
|
+
}
|
|
126
|
+
|
|
123
127
|
async function validateCurrentCredential(ctx: any): Promise<{ name?: string | null; email?: string | null } | null> {
|
|
124
128
|
const envToken = getRuntimeEnvToken();
|
|
125
129
|
const stored = envToken ? null : await getStoredToken();
|
|
@@ -135,6 +139,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
135
139
|
clearSeedStatuses(ctx);
|
|
136
140
|
return null;
|
|
137
141
|
}
|
|
142
|
+
if (!isSeedClubInternalEmail(user.email)) {
|
|
143
|
+
await clearCredentials();
|
|
144
|
+
if (envToken) clearRuntimeEnvTokens();
|
|
145
|
+
clearSeedStatuses(ctx);
|
|
146
|
+
markAuthRequired({
|
|
147
|
+
authUrl: null,
|
|
148
|
+
message: "Seed Club sign-in is required before /login or /model.",
|
|
149
|
+
error: "Use a @seedclub.com account to connect this agent.",
|
|
150
|
+
});
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
138
153
|
|
|
139
154
|
await applyConnectedStatus(ctx, user);
|
|
140
155
|
markAuthComplete(getPostAuthInstruction(ctx));
|
|
@@ -339,6 +354,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
339
354
|
ctx.ui.notify(`Token verification failed: ${result.error}`, "error");
|
|
340
355
|
return false;
|
|
341
356
|
}
|
|
357
|
+
if (!isSeedClubInternalEmail(result.email)) {
|
|
358
|
+
await clearCredentials();
|
|
359
|
+
clearSeedStatuses(ctx);
|
|
360
|
+
markAuthRequired({
|
|
361
|
+
authUrl: null,
|
|
362
|
+
message: "Seed Club sign-in is still required. Run /connect to retry.",
|
|
363
|
+
error: "Use a @seedclub.com account to connect this agent.",
|
|
364
|
+
});
|
|
365
|
+
ctx.ui.notify("Use a @seedclub.com account to connect this agent.", "error");
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
342
368
|
|
|
343
369
|
await storeToken(token, result.email, apiBase, { authBase, name: result.name });
|
|
344
370
|
await applyConnectedStatus(ctx, result);
|
|
@@ -120,6 +120,141 @@ async function listProgramContacts(args: { programSlug: string; search?: string
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
async function listProgramFunnels(args: { programSlug: string }) {
|
|
124
|
+
try {
|
|
125
|
+
return await api.get<any>(`/programs/${args.programSlug}/funnels`);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (error instanceof ApiError) return { error: error.message, status: error.status };
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function createFunnelStage(args: {
|
|
133
|
+
programSlug: string;
|
|
134
|
+
funnelSlug: string;
|
|
135
|
+
stageKey: string;
|
|
136
|
+
label: string;
|
|
137
|
+
sortOrder: number;
|
|
138
|
+
isEntry?: boolean;
|
|
139
|
+
isExit?: boolean;
|
|
140
|
+
confirmed?: boolean;
|
|
141
|
+
}) {
|
|
142
|
+
try {
|
|
143
|
+
if (args.confirmed !== true) {
|
|
144
|
+
return {
|
|
145
|
+
error: "Stage creation requires explicit confirmation after listing the current funnel stages.",
|
|
146
|
+
status: 400,
|
|
147
|
+
requiredFlow: [
|
|
148
|
+
"Call seedclub_list_program_funnels for the program.",
|
|
149
|
+
"Confirm the funnel, stage key, label, and sort order with the user.",
|
|
150
|
+
"Call seedclub_create_funnel_stage with confirmed=true.",
|
|
151
|
+
],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return await api.post<any>(`/programs/${args.programSlug}/funnels/${args.funnelSlug}/stages`, {
|
|
156
|
+
stage_key: args.stageKey,
|
|
157
|
+
label: args.label,
|
|
158
|
+
sort_order: args.sortOrder,
|
|
159
|
+
is_entry: args.isEntry,
|
|
160
|
+
is_exit: args.isExit,
|
|
161
|
+
});
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (error instanceof ApiError) return { error: error.message, status: error.status };
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function updateFunnelStage(args: {
|
|
169
|
+
programSlug: string;
|
|
170
|
+
funnelSlug: string;
|
|
171
|
+
stageKey: string;
|
|
172
|
+
nextStageKey?: string | null;
|
|
173
|
+
label?: string | null;
|
|
174
|
+
sortOrder?: number | null;
|
|
175
|
+
isEntry?: boolean | null;
|
|
176
|
+
isExit?: boolean | null;
|
|
177
|
+
confirmed?: boolean;
|
|
178
|
+
}) {
|
|
179
|
+
try {
|
|
180
|
+
if (args.confirmed !== true) {
|
|
181
|
+
return {
|
|
182
|
+
error: "Stage updates require explicit confirmation after listing the current funnel stages.",
|
|
183
|
+
status: 400,
|
|
184
|
+
requiredFlow: [
|
|
185
|
+
"Call seedclub_list_program_funnels for the program.",
|
|
186
|
+
"Confirm the target stage and exact changes with the user.",
|
|
187
|
+
"Call seedclub_update_funnel_stage with confirmed=true.",
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const body: Record<string, unknown> = {};
|
|
193
|
+
if (args.nextStageKey != null) body.stage_key = args.nextStageKey;
|
|
194
|
+
if (args.label != null) body.label = args.label;
|
|
195
|
+
if (args.sortOrder != null) body.sort_order = args.sortOrder;
|
|
196
|
+
if (args.isEntry != null) body.is_entry = args.isEntry;
|
|
197
|
+
if (args.isExit != null) body.is_exit = args.isExit;
|
|
198
|
+
|
|
199
|
+
return await api.patch<any>(
|
|
200
|
+
`/programs/${args.programSlug}/funnels/${args.funnelSlug}/stages/${args.stageKey}`,
|
|
201
|
+
body,
|
|
202
|
+
);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (error instanceof ApiError) return { error: error.message, status: error.status };
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function listFunnelContacts(args: { programSlug: string; funnelSlug: string; search?: string }) {
|
|
210
|
+
try {
|
|
211
|
+
return await api.get<any>(`/programs/${args.programSlug}/funnels/${args.funnelSlug}/enrollments`, {
|
|
212
|
+
search: args.search,
|
|
213
|
+
});
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (error instanceof ApiError) return { error: error.message, status: error.status };
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function listFunnelStageContacts(args: {
|
|
221
|
+
programSlug: string;
|
|
222
|
+
funnelSlug: string;
|
|
223
|
+
stageKey: string;
|
|
224
|
+
search?: string;
|
|
225
|
+
}) {
|
|
226
|
+
try {
|
|
227
|
+
return await api.get<any>(
|
|
228
|
+
`/programs/${args.programSlug}/funnels/${args.funnelSlug}/stages/${args.stageKey}/contacts`,
|
|
229
|
+
{ search: args.search },
|
|
230
|
+
);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (error instanceof ApiError) return { error: error.message, status: error.status };
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function moveFunnelEnrollment(args: {
|
|
238
|
+
programSlug: string;
|
|
239
|
+
funnelSlug: string;
|
|
240
|
+
enrollmentId: string;
|
|
241
|
+
toStageId: string;
|
|
242
|
+
reason?: string | null;
|
|
243
|
+
}) {
|
|
244
|
+
try {
|
|
245
|
+
return await api.patch<any>(
|
|
246
|
+
`/programs/${args.programSlug}/funnels/${args.funnelSlug}/enrollments/${args.enrollmentId}`,
|
|
247
|
+
{
|
|
248
|
+
reason: args.reason,
|
|
249
|
+
to_stage_id: args.toStageId,
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (error instanceof ApiError) return { error: error.message, status: error.status };
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
123
258
|
export function registerCrmTools(pi: ExtensionAPI) {
|
|
124
259
|
pi.registerTool({
|
|
125
260
|
name: "seedclub_list_crm_records",
|
|
@@ -224,4 +359,134 @@ export function registerCrmTools(pi: ExtensionAPI) {
|
|
|
224
359
|
return new Text(text, 0, 0);
|
|
225
360
|
},
|
|
226
361
|
});
|
|
362
|
+
|
|
363
|
+
pi.registerTool({
|
|
364
|
+
name: "seedclub_list_program_funnels",
|
|
365
|
+
label: "List Program Funnels",
|
|
366
|
+
description:
|
|
367
|
+
"List funnels and stages for a Seed Club program, including active enrollment counts. Use this to understand workflow lanes like seed-network scouts, angels, and founders before listing contacts.",
|
|
368
|
+
parameters: Type.Object({
|
|
369
|
+
programSlug: Type.String({ description: "Program slug, for example seed-network or 11am" }),
|
|
370
|
+
}),
|
|
371
|
+
execute: wrapExecute(listProgramFunnels),
|
|
372
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_list_program_funnels"), (args) => args?.programSlug || undefined),
|
|
373
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_list_program_funnels"), (details) => {
|
|
374
|
+
const rows = Array.isArray(details?.data) ? details.data : [];
|
|
375
|
+
const total = rows.reduce((sum: number, funnel: any) => sum + (Number(funnel?.active_enrollment_count) || 0), 0);
|
|
376
|
+
return `${rows.length} funnel${rows.length === 1 ? "" : "s"} · ${total} active`;
|
|
377
|
+
}),
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
pi.registerTool({
|
|
381
|
+
name: "seedclub_create_funnel_stage",
|
|
382
|
+
label: "Create Funnel Stage",
|
|
383
|
+
description:
|
|
384
|
+
"Create a new stage in a program funnel. Use only after seedclub_list_program_funnels and after the user explicitly confirms the funnel, stage key, label, and sort order.",
|
|
385
|
+
parameters: Type.Object({
|
|
386
|
+
programSlug: Type.String({ description: "Program slug" }),
|
|
387
|
+
funnelSlug: Type.String({ description: "Funnel slug" }),
|
|
388
|
+
stageKey: Type.String({ description: "New stage key, lowercase letters/numbers/underscore/hyphen only" }),
|
|
389
|
+
label: Type.String({ description: "Human-readable stage label" }),
|
|
390
|
+
sortOrder: Type.Number({ description: "Integer stage sort order chosen from the current stage list" }),
|
|
391
|
+
isEntry: Type.Optional(Type.Boolean({ description: "Set true if this should be the funnel entry stage" })),
|
|
392
|
+
isExit: Type.Optional(Type.Boolean({ description: "Set true if this should be an exit stage" })),
|
|
393
|
+
confirmed: Type.Boolean({ description: "Must be true after explicit user confirmation." }),
|
|
394
|
+
}),
|
|
395
|
+
execute: wrapExecute(createFunnelStage),
|
|
396
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_create_funnel_stage"), (args) =>
|
|
397
|
+
[args?.funnelSlug, args?.stageKey].filter(Boolean).join(" / ") || undefined,
|
|
398
|
+
),
|
|
399
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_create_funnel_stage"), (details) =>
|
|
400
|
+
details?.stage?.stage_key ?? details?.stage?.label ?? undefined,
|
|
401
|
+
),
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
pi.registerTool({
|
|
405
|
+
name: "seedclub_update_funnel_stage",
|
|
406
|
+
label: "Update Funnel Stage",
|
|
407
|
+
description:
|
|
408
|
+
"Update a funnel stage key, label, sort order, or entry/exit flags. Use only after seedclub_list_program_funnels and after the user explicitly confirms the target stage and exact changes.",
|
|
409
|
+
parameters: Type.Object({
|
|
410
|
+
programSlug: Type.String({ description: "Program slug" }),
|
|
411
|
+
funnelSlug: Type.String({ description: "Funnel slug" }),
|
|
412
|
+
stageKey: Type.String({ description: "Current stage key to update" }),
|
|
413
|
+
nextStageKey: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional replacement stage key" })),
|
|
414
|
+
label: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional replacement label" })),
|
|
415
|
+
sortOrder: Type.Optional(Type.Union([Type.Number(), Type.Null()], { description: "Optional replacement integer sort order" })),
|
|
416
|
+
isEntry: Type.Optional(Type.Union([Type.Boolean(), Type.Null()], { description: "Optional entry-stage flag" })),
|
|
417
|
+
isExit: Type.Optional(Type.Union([Type.Boolean(), Type.Null()], { description: "Optional exit-stage flag" })),
|
|
418
|
+
confirmed: Type.Boolean({ description: "Must be true after explicit user confirmation." }),
|
|
419
|
+
}),
|
|
420
|
+
execute: wrapExecute(updateFunnelStage),
|
|
421
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_update_funnel_stage"), (args) =>
|
|
422
|
+
[args?.funnelSlug, args?.stageKey].filter(Boolean).join(" / ") || undefined,
|
|
423
|
+
),
|
|
424
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_update_funnel_stage"), (details) =>
|
|
425
|
+
details?.stage?.stage_key ?? details?.stage?.label ?? undefined,
|
|
426
|
+
),
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
pi.registerTool({
|
|
430
|
+
name: "seedclub_list_funnel_contacts",
|
|
431
|
+
label: "List Funnel Contacts",
|
|
432
|
+
description:
|
|
433
|
+
"List active contacts enrolled in a specific program funnel. Use after seedclub_list_program_funnels when the user asks who is in scouts, angels, founders, guests, or another funnel.",
|
|
434
|
+
parameters: Type.Object({
|
|
435
|
+
programSlug: Type.String({ description: "Program slug" }),
|
|
436
|
+
funnelSlug: Type.String({ description: "Funnel slug" }),
|
|
437
|
+
search: Type.Optional(Type.String({ description: "Optional contact search text" })),
|
|
438
|
+
}),
|
|
439
|
+
execute: wrapExecute(listFunnelContacts),
|
|
440
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_list_funnel_contacts"), (args) =>
|
|
441
|
+
[args?.programSlug, args?.funnelSlug].filter(Boolean).join(" / ") || undefined,
|
|
442
|
+
),
|
|
443
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_list_funnel_contacts"), (details) => {
|
|
444
|
+
const rows = Array.isArray(details?.data) ? details.data : [];
|
|
445
|
+
const funnel = details?.funnel?.slug ?? "funnel";
|
|
446
|
+
return `${rows.length} contact${rows.length === 1 ? "" : "s"} in ${funnel}`;
|
|
447
|
+
}),
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
pi.registerTool({
|
|
451
|
+
name: "seedclub_list_funnel_stage_contacts",
|
|
452
|
+
label: "List Funnel Stage Contacts",
|
|
453
|
+
description:
|
|
454
|
+
"List active contacts in one stage of a program funnel. Use this for questions like 'who are the angels not contacted yet?' or 'which founders are imported?'.",
|
|
455
|
+
parameters: Type.Object({
|
|
456
|
+
programSlug: Type.String({ description: "Program slug" }),
|
|
457
|
+
funnelSlug: Type.String({ description: "Funnel slug" }),
|
|
458
|
+
stageKey: Type.String({ description: "Stage key, for example not_contacted or imported" }),
|
|
459
|
+
search: Type.Optional(Type.String({ description: "Optional contact search text" })),
|
|
460
|
+
}),
|
|
461
|
+
execute: wrapExecute(listFunnelStageContacts),
|
|
462
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_list_funnel_stage_contacts"), (args) =>
|
|
463
|
+
[args?.funnelSlug, args?.stageKey].filter(Boolean).join(" / ") || undefined,
|
|
464
|
+
),
|
|
465
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_list_funnel_stage_contacts"), (details) => {
|
|
466
|
+
const rows = Array.isArray(details?.data) ? details.data : [];
|
|
467
|
+
const stage = details?.stage?.stage_key ?? details?.stage?.label ?? "stage";
|
|
468
|
+
return `${rows.length} contact${rows.length === 1 ? "" : "s"} in ${stage}`;
|
|
469
|
+
}),
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
pi.registerTool({
|
|
473
|
+
name: "seedclub_move_funnel_enrollment",
|
|
474
|
+
label: "Move Funnel Enrollment",
|
|
475
|
+
description:
|
|
476
|
+
"Move an active contact enrollment to another stage in a program funnel. Use only after confirming the target enrollment id and target stage id from funnel tools.",
|
|
477
|
+
parameters: Type.Object({
|
|
478
|
+
programSlug: Type.String({ description: "Program slug" }),
|
|
479
|
+
funnelSlug: Type.String({ description: "Funnel slug" }),
|
|
480
|
+
enrollmentId: Type.String({ description: "Active enrollment id" }),
|
|
481
|
+
toStageId: Type.String({ description: "Target stage id, not the stage key" }),
|
|
482
|
+
reason: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional transition reason" })),
|
|
483
|
+
}),
|
|
484
|
+
execute: wrapExecute(moveFunnelEnrollment),
|
|
485
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_move_funnel_enrollment"), (args) =>
|
|
486
|
+
args?.enrollmentId || undefined,
|
|
487
|
+
),
|
|
488
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_move_funnel_enrollment"), (details) =>
|
|
489
|
+
details?.enrollment?.id || undefined,
|
|
490
|
+
),
|
|
491
|
+
});
|
|
227
492
|
}
|
|
@@ -147,6 +147,10 @@ function inferContentType(fileName: string) {
|
|
|
147
147
|
return "video/mp4";
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
function isIsoDate(value: string) {
|
|
151
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
152
|
+
}
|
|
153
|
+
|
|
150
154
|
async function inferMediaOrientation(
|
|
151
155
|
filePath: string,
|
|
152
156
|
contentType: string,
|
|
@@ -180,143 +184,6 @@ async function inferMediaOrientation(
|
|
|
180
184
|
}
|
|
181
185
|
}
|
|
182
186
|
|
|
183
|
-
async function publishClipAsset(args: {
|
|
184
|
-
programSlug: string;
|
|
185
|
-
storageUrl: string;
|
|
186
|
-
associationPolicy?: "required" | "preferred" | "none" | null;
|
|
187
|
-
meetingId?: string | null;
|
|
188
|
-
guestName?: string | null;
|
|
189
|
-
primaryPartyId?: string | null;
|
|
190
|
-
partyIds?: string[] | null;
|
|
191
|
-
audioUrl?: string | null;
|
|
192
|
-
contentType?: string | null;
|
|
193
|
-
description?: string | null;
|
|
194
|
-
durationSeconds?: number | null;
|
|
195
|
-
eventDate?: string | null;
|
|
196
|
-
fileName?: string | null;
|
|
197
|
-
fileSize?: number | null;
|
|
198
|
-
keyQuestion?: string | null;
|
|
199
|
-
orientation?: string | null;
|
|
200
|
-
publicationUrls?: {
|
|
201
|
-
instagram?: string | null;
|
|
202
|
-
tiktok?: string | null;
|
|
203
|
-
x?: string | null;
|
|
204
|
-
youtube?: string | null;
|
|
205
|
-
zora?: string | null;
|
|
206
|
-
} | null;
|
|
207
|
-
thumbnailUrl?: string | null;
|
|
208
|
-
title?: string | null;
|
|
209
|
-
transcriptEdited?: string | null;
|
|
210
|
-
transcriptRaw?: string | null;
|
|
211
|
-
transcriptVtt?: string | null;
|
|
212
|
-
triggerTranscription?: boolean | null;
|
|
213
|
-
visibility?: string | null;
|
|
214
|
-
workflowStatus?: string | null;
|
|
215
|
-
publishingJob?: {
|
|
216
|
-
platform: string;
|
|
217
|
-
title?: string | null;
|
|
218
|
-
description?: string | null;
|
|
219
|
-
scheduledFor?: string | null;
|
|
220
|
-
publishedAt?: string | null;
|
|
221
|
-
status?: string | null;
|
|
222
|
-
isImmediate?: boolean;
|
|
223
|
-
tags?: string[] | null;
|
|
224
|
-
thumbnailAssetId?: string | null;
|
|
225
|
-
legacyAssetSourceId?: string | null;
|
|
226
|
-
metadataJson?: Record<string, unknown> | null;
|
|
227
|
-
destination?: {
|
|
228
|
-
accountId?: string | null;
|
|
229
|
-
username?: string | null;
|
|
230
|
-
displayName?: string | null;
|
|
231
|
-
profileImageUrl?: string | null;
|
|
232
|
-
configJson?: Record<string, unknown> | null;
|
|
233
|
-
} | null;
|
|
234
|
-
} | null;
|
|
235
|
-
}) {
|
|
236
|
-
try {
|
|
237
|
-
const storageUrl = normalizeOptionalString(args.storageUrl);
|
|
238
|
-
if (!storageUrl) {
|
|
239
|
-
return {
|
|
240
|
-
error: "Provide the uploaded clip storageUrl for the final rendered clip.",
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const assetResponse = await api.post<any>(`/programs/${args.programSlug}/media/assets`, {
|
|
245
|
-
asset_kind: "clip",
|
|
246
|
-
association_policy: normalizeOptionalString(args.associationPolicy),
|
|
247
|
-
audio_url: normalizeOptionalString(args.audioUrl),
|
|
248
|
-
content_type: normalizeOptionalString(args.contentType),
|
|
249
|
-
description: normalizeOptionalString(args.description),
|
|
250
|
-
duration_seconds: normalizeOptionalNumber(args.durationSeconds),
|
|
251
|
-
event_date: normalizeOptionalString(args.eventDate),
|
|
252
|
-
file_name: normalizeOptionalString(args.fileName),
|
|
253
|
-
file_size: normalizeOptionalNumber(args.fileSize),
|
|
254
|
-
guest_name: normalizeOptionalString(args.guestName),
|
|
255
|
-
key_question: normalizeOptionalString(args.keyQuestion),
|
|
256
|
-
meeting_id: normalizeOptionalString(args.meetingId),
|
|
257
|
-
orientation: normalizeOptionalString(args.orientation),
|
|
258
|
-
party_ids: Array.isArray(args.partyIds) ? args.partyIds : null,
|
|
259
|
-
primary_party_id: normalizeOptionalString(args.primaryPartyId),
|
|
260
|
-
publication_urls: args.publicationUrls ?? null,
|
|
261
|
-
storage_url: storageUrl,
|
|
262
|
-
thumbnail_url: normalizeOptionalString(args.thumbnailUrl),
|
|
263
|
-
title: normalizeOptionalString(args.title),
|
|
264
|
-
trigger_transcription: normalizeOptionalBoolean(args.triggerTranscription),
|
|
265
|
-
transcript_edited: normalizeOptionalString(args.transcriptEdited),
|
|
266
|
-
transcript_raw: normalizeOptionalString(args.transcriptRaw),
|
|
267
|
-
transcript_vtt: normalizeOptionalString(args.transcriptVtt),
|
|
268
|
-
visibility: normalizeOptionalString(args.visibility),
|
|
269
|
-
workflow_status: normalizeOptionalString(args.workflowStatus),
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
let publishingJob: any = null;
|
|
273
|
-
if (args.publishingJob?.platform?.trim()) {
|
|
274
|
-
const createdAssetId = assetResponse?.asset?.id;
|
|
275
|
-
publishingJob = await api.post<any>(`/programs/${args.programSlug}/publishing/jobs`, {
|
|
276
|
-
platform: args.publishingJob.platform.trim(),
|
|
277
|
-
asset_id: typeof createdAssetId === "string" ? createdAssetId : null,
|
|
278
|
-
title: normalizeOptionalString(args.publishingJob.title),
|
|
279
|
-
description: normalizeOptionalString(args.publishingJob.description),
|
|
280
|
-
scheduled_for: normalizeOptionalString(args.publishingJob.scheduledFor),
|
|
281
|
-
published_at: normalizeOptionalString(args.publishingJob.publishedAt),
|
|
282
|
-
status: normalizeOptionalString(args.publishingJob.status),
|
|
283
|
-
is_immediate: args.publishingJob.isImmediate === true,
|
|
284
|
-
tags: Array.isArray(args.publishingJob.tags)
|
|
285
|
-
? args.publishingJob.tags.filter((tag) => typeof tag === "string" && tag.trim()).map((tag) => tag.trim())
|
|
286
|
-
: [],
|
|
287
|
-
thumbnail_asset_id: normalizeOptionalString(args.publishingJob.thumbnailAssetId),
|
|
288
|
-
legacy_asset_source_id: normalizeOptionalString(args.publishingJob.legacyAssetSourceId),
|
|
289
|
-
metadata_json: args.publishingJob.metadataJson ?? {},
|
|
290
|
-
destination: args.publishingJob.destination
|
|
291
|
-
? {
|
|
292
|
-
account_id: normalizeOptionalString(args.publishingJob.destination.accountId),
|
|
293
|
-
username: normalizeOptionalString(args.publishingJob.destination.username),
|
|
294
|
-
display_name: normalizeOptionalString(args.publishingJob.destination.displayName),
|
|
295
|
-
profile_image_url: normalizeOptionalString(args.publishingJob.destination.profileImageUrl),
|
|
296
|
-
config_json: args.publishingJob.destination.configJson ?? null,
|
|
297
|
-
}
|
|
298
|
-
: null,
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
return {
|
|
303
|
-
program: assetResponse?.program ?? null,
|
|
304
|
-
asset: shapeMediaAssetRow(assetResponse?.asset, false),
|
|
305
|
-
primary_party: assetResponse?.primary_party ?? null,
|
|
306
|
-
parties: Array.isArray(assetResponse?.parties) ? assetResponse.parties : [],
|
|
307
|
-
matched_funnels: Array.isArray(assetResponse?.matched_funnels) ? assetResponse.matched_funnels : [],
|
|
308
|
-
publications: Array.isArray(assetResponse?.publications) ? assetResponse.publications : [],
|
|
309
|
-
publishing_job: publishingJob?.data ?? null,
|
|
310
|
-
linkage: assetResponse?.linkage ?? null,
|
|
311
|
-
note:
|
|
312
|
-
"Created the linked media record for an already-hosted clip URL. For local files, use seedclub_upload_clip_asset so seedclub-api handles the 11am Spaces upload.",
|
|
313
|
-
};
|
|
314
|
-
} catch (error) {
|
|
315
|
-
if (error instanceof ApiError) return { error: error.message, status: error.status };
|
|
316
|
-
throw error;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
187
|
async function uploadClipAsset(args: {
|
|
321
188
|
programSlug: string;
|
|
322
189
|
filePath: string;
|
|
@@ -384,17 +251,39 @@ async function uploadClipAsset(args: {
|
|
|
384
251
|
|
|
385
252
|
const resolvedFileName = normalizeOptionalString(args.fileName) ?? basename(filePath);
|
|
386
253
|
const resolvedContentType = normalizeOptionalString(args.contentType) ?? inferContentType(resolvedFileName);
|
|
254
|
+
const resolvedTitle = normalizeOptionalString(args.title);
|
|
255
|
+
const resolvedEventDate = normalizeOptionalString(args.eventDate);
|
|
256
|
+
const resolvedMeetingId = normalizeOptionalString(args.meetingId);
|
|
257
|
+
const resolvedWorkflowStatus = normalizeOptionalString(args.workflowStatus) ?? "published";
|
|
258
|
+
const missingFields = [
|
|
259
|
+
...(resolvedTitle ? [] : ["title"]),
|
|
260
|
+
...(resolvedMeetingId ? [] : ["meetingId"]),
|
|
261
|
+
];
|
|
262
|
+
if (missingFields.length) {
|
|
263
|
+
return {
|
|
264
|
+
error:
|
|
265
|
+
`Before uploading, ask the user for the missing clip metadata: ${missingFields.join(", ")}. ` +
|
|
266
|
+
"For finished clips, workflowStatus is usually published.",
|
|
267
|
+
missing_fields: missingFields,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const uploadTitle = resolvedTitle as string;
|
|
271
|
+
const uploadMeetingId = resolvedMeetingId as string;
|
|
272
|
+
const uploadWorkflowStatus = resolvedWorkflowStatus as string;
|
|
273
|
+
if (resolvedEventDate && !isIsoDate(resolvedEventDate)) {
|
|
274
|
+
return { error: "eventDate must use YYYY-MM-DD format.", field: "eventDate" };
|
|
275
|
+
}
|
|
387
276
|
const resolvedOrientation =
|
|
388
277
|
normalizeOptionalString(args.orientation) ??
|
|
389
278
|
(await inferMediaOrientation(filePath, resolvedContentType));
|
|
390
279
|
const presignResponse = await api.post<any>(`/programs/${args.programSlug}/media/ingests`, {
|
|
391
280
|
association_policy: normalizeOptionalString(args.associationPolicy),
|
|
392
281
|
content_type: resolvedContentType,
|
|
393
|
-
event_date:
|
|
282
|
+
event_date: resolvedEventDate,
|
|
394
283
|
file_name: resolvedFileName,
|
|
395
284
|
guest_name: normalizeOptionalString(args.guestName),
|
|
396
285
|
kind: resolvedContentType.startsWith("audio/") ? "audio" : "clip",
|
|
397
|
-
meeting_id:
|
|
286
|
+
meeting_id: uploadMeetingId,
|
|
398
287
|
party_ids: Array.isArray(args.partyIds) ? args.partyIds : null,
|
|
399
288
|
person_name: normalizeOptionalString(args.personName),
|
|
400
289
|
primary_party_id: normalizeOptionalString(args.primaryPartyId),
|
|
@@ -464,12 +353,12 @@ async function uploadClipAsset(args: {
|
|
|
464
353
|
description: normalizeOptionalString(args.description),
|
|
465
354
|
derived_from_asset_id: normalizeOptionalString(args.derivedFromAssetId),
|
|
466
355
|
duration_seconds: normalizeOptionalNumber(args.durationSeconds),
|
|
467
|
-
event_date:
|
|
356
|
+
event_date: resolvedEventDate,
|
|
468
357
|
file_name: resolvedFileName,
|
|
469
358
|
file_size: fileStats.size,
|
|
470
359
|
guest_name: normalizeOptionalString(args.guestName),
|
|
471
360
|
key_question: normalizeOptionalString(args.keyQuestion),
|
|
472
|
-
meeting_id:
|
|
361
|
+
meeting_id: uploadMeetingId,
|
|
473
362
|
orientation: resolvedOrientation,
|
|
474
363
|
party_ids: Array.isArray(args.partyIds) ? args.partyIds : null,
|
|
475
364
|
primary_party_id: normalizeOptionalString(args.primaryPartyId),
|
|
@@ -478,12 +367,12 @@ async function uploadClipAsset(args: {
|
|
|
478
367
|
source_recording_id: normalizeOptionalString(args.sourceRecordingId),
|
|
479
368
|
source_stream_id: normalizeOptionalString(args.sourceStreamId),
|
|
480
369
|
thumbnail_url: normalizeOptionalString(args.thumbnailUrl),
|
|
481
|
-
title:
|
|
370
|
+
title: uploadTitle,
|
|
482
371
|
transcript_edited: normalizeOptionalString(args.transcriptEdited),
|
|
483
372
|
transcript_raw: normalizeOptionalString(args.transcriptRaw),
|
|
484
373
|
transcript_vtt: normalizeOptionalString(args.transcriptVtt),
|
|
485
374
|
visibility: normalizeOptionalString(args.visibility),
|
|
486
|
-
workflow_status:
|
|
375
|
+
workflow_status: uploadWorkflowStatus,
|
|
487
376
|
});
|
|
488
377
|
|
|
489
378
|
return {
|
|
@@ -696,12 +585,12 @@ export function registerMediaTools(pi: ExtensionAPI) {
|
|
|
696
585
|
name: "seedclub_upload_clip_asset",
|
|
697
586
|
label: "Upload Clip Asset",
|
|
698
587
|
description:
|
|
699
|
-
"Upload a locally rendered 11am clip file to DigitalOcean Spaces through seedclub-api, then create the linked media asset record and optional publishing job. Use this after ffmpeg has cut the clip and appended the current season bumper. The CLI receives only a short-lived upload URL; DigitalOcean keys stay on seedclub-api.",
|
|
588
|
+
"Upload a locally rendered 11am clip file to DigitalOcean Spaces through seedclub-api, then create the linked media asset record and optional publishing job. Use this after ffmpeg has cut the clip and appended the current season bumper. Before calling this tool, ask for or confidently infer title and meetingId; do not upload without them. eventDate is optional because seedclub-api infers it from meetingId when completing the upload; if supplied, it must be YYYY-MM-DD. workflowStatus defaults to published for finished clips unless the user chooses another status. The CLI receives only a short-lived upload URL; DigitalOcean keys stay on seedclub-api.",
|
|
700
589
|
parameters: Type.Object({
|
|
701
590
|
programSlug: Type.String({ description: "Program slug. Currently the upload backend is configured for 11am." }),
|
|
702
591
|
filePath: Type.String({ description: "Local path to the final rendered clip file after bumper append." }),
|
|
703
592
|
associationPolicy: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional association policy passed through to seedclub-api. Use required, preferred, or none." })),
|
|
704
|
-
meetingId: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "
|
|
593
|
+
meetingId: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Required for upload. Exact meeting id for the source show/interview. Ask the user or resolve it from transcript/recording context before uploading." })),
|
|
705
594
|
guestName: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional guest name passed through to seedclub-api for meeting resolution." })),
|
|
706
595
|
primaryPartyId: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional primary guest party id. If omitted, seedclub-api will infer it from meetingId when possible." })),
|
|
707
596
|
partyIds: Type.Optional(Type.Union([Type.Array(Type.String()), Type.Null()], { description: "Optional additional related party ids. If omitted, seedclub-api will infer them from meetingId when possible." })),
|
|
@@ -710,7 +599,7 @@ export function registerMediaTools(pi: ExtensionAPI) {
|
|
|
710
599
|
description: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
711
600
|
derivedFromAssetId: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
712
601
|
durationSeconds: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
|
|
713
|
-
eventDate: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional YYYY-MM-DD event date
|
|
602
|
+
eventDate: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional YYYY-MM-DD event date for the presigned storage path. seedclub-api infers final media event_date from meetingId on complete." })),
|
|
714
603
|
fileName: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional storage/display file name. Defaults to basename(filePath)." })),
|
|
715
604
|
keyQuestion: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
716
605
|
orientation: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional orientation such as vertical or horizontal. If omitted for a local upload, the tool will infer it from the media dimensions when possible." })),
|
|
@@ -730,12 +619,12 @@ export function registerMediaTools(pi: ExtensionAPI) {
|
|
|
730
619
|
sourceRecordingId: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
731
620
|
sourceStreamId: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
732
621
|
thumbnailUrl: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
733
|
-
title: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
622
|
+
title: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Required for upload. Human-readable clip title. Ask the user if it cannot be confidently inferred." })),
|
|
734
623
|
transcriptEdited: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
735
624
|
transcriptRaw: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
736
625
|
transcriptVtt: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
737
626
|
visibility: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
738
|
-
workflowStatus: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
627
|
+
workflowStatus: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional media workflow state. Defaults to published for finished clips unless the user chooses another status." })),
|
|
739
628
|
publishingJob: Type.Optional(
|
|
740
629
|
Type.Union([
|
|
741
630
|
Type.Object({
|
|
@@ -775,91 +664,6 @@ export function registerMediaTools(pi: ExtensionAPI) {
|
|
|
775
664
|
}),
|
|
776
665
|
});
|
|
777
666
|
|
|
778
|
-
pi.registerTool({
|
|
779
|
-
name: "seedclub_publish_clip_asset",
|
|
780
|
-
label: "Publish Clip Asset",
|
|
781
|
-
description:
|
|
782
|
-
"Create a clip media asset linked to the correct meeting and party records, then optionally create a publishing job for an already-hosted clip URL. For local ffmpeg outputs, prefer seedclub_upload_clip_asset so seedclub-api handles the DigitalOcean Spaces presigned upload.",
|
|
783
|
-
parameters: Type.Object({
|
|
784
|
-
programSlug: Type.String({ description: "Program slug" }),
|
|
785
|
-
storageUrl: Type.String({ description: "Public or internal URL for the final rendered clip file." }),
|
|
786
|
-
associationPolicy: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional association policy passed through to seedclub-api. Use required, preferred, or none." })),
|
|
787
|
-
meetingId: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional exact meeting id. If omitted, seedclub-api can resolve it from guestName and eventDate." })),
|
|
788
|
-
guestName: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional guest name passed through to seedclub-api for meeting resolution." })),
|
|
789
|
-
primaryPartyId: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional primary guest party id. If omitted, the tool will try to infer it from the meeting." })),
|
|
790
|
-
partyIds: Type.Optional(Type.Union([Type.Array(Type.String()), Type.Null()], { description: "Optional additional related party ids. primaryPartyId will be included automatically." })),
|
|
791
|
-
audioUrl: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
792
|
-
contentType: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
793
|
-
description: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
794
|
-
durationSeconds: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
|
|
795
|
-
eventDate: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional YYYY-MM-DD event date passed through to seedclub-api for association resolution." })),
|
|
796
|
-
fileName: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
797
|
-
fileSize: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
|
|
798
|
-
keyQuestion: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
799
|
-
orientation: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional orientation such as vertical or horizontal." })),
|
|
800
|
-
publicationUrls: Type.Optional(
|
|
801
|
-
Type.Union([
|
|
802
|
-
Type.Object({
|
|
803
|
-
instagram: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
804
|
-
tiktok: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
805
|
-
x: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
806
|
-
youtube: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
807
|
-
zora: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
808
|
-
}),
|
|
809
|
-
Type.Null(),
|
|
810
|
-
]),
|
|
811
|
-
),
|
|
812
|
-
thumbnailUrl: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
813
|
-
title: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
814
|
-
transcriptEdited: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
815
|
-
transcriptRaw: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
816
|
-
transcriptVtt: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
817
|
-
triggerTranscription: Type.Optional(Type.Union([Type.Boolean(), Type.Null()])),
|
|
818
|
-
visibility: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
819
|
-
workflowStatus: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
820
|
-
publishingJob: Type.Optional(
|
|
821
|
-
Type.Union([
|
|
822
|
-
Type.Object({
|
|
823
|
-
platform: Type.String({ description: "Publishing platform slug/name for the downstream job." }),
|
|
824
|
-
title: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
825
|
-
description: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
826
|
-
scheduledFor: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
827
|
-
publishedAt: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
828
|
-
status: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
829
|
-
isImmediate: Type.Optional(Type.Boolean()),
|
|
830
|
-
tags: Type.Optional(Type.Union([Type.Array(Type.String()), Type.Null()])),
|
|
831
|
-
thumbnailAssetId: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
832
|
-
legacyAssetSourceId: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
833
|
-
metadataJson: Type.Optional(Type.Union([Type.Record(Type.String(), Type.Unknown()), Type.Null()])),
|
|
834
|
-
destination: Type.Optional(
|
|
835
|
-
Type.Union([
|
|
836
|
-
Type.Object({
|
|
837
|
-
accountId: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
838
|
-
username: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
839
|
-
displayName: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
840
|
-
profileImageUrl: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
841
|
-
configJson: Type.Optional(Type.Union([Type.Record(Type.String(), Type.Unknown()), Type.Null()])),
|
|
842
|
-
}),
|
|
843
|
-
Type.Null(),
|
|
844
|
-
]),
|
|
845
|
-
),
|
|
846
|
-
}),
|
|
847
|
-
Type.Null(),
|
|
848
|
-
]),
|
|
849
|
-
),
|
|
850
|
-
}),
|
|
851
|
-
execute: wrapExecute(publishClipAsset),
|
|
852
|
-
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_publish_clip_asset"), (args) => args?.title || args?.guestName || args?.meetingId || args?.programSlug || undefined),
|
|
853
|
-
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_publish_clip_asset"), (details) => {
|
|
854
|
-
const asset = details?.asset ?? {};
|
|
855
|
-
const assetId = typeof asset?.id === "string" ? asset.id : null;
|
|
856
|
-
const meetingId = typeof details?.linkage?.meeting_id === "string" ? details.linkage.meeting_id : null;
|
|
857
|
-
const partyId = typeof details?.linkage?.primary_party_id === "string" ? details.linkage.primary_party_id : null;
|
|
858
|
-
const pieces = [assetId, meetingId, partyId].filter(Boolean);
|
|
859
|
-
return pieces.length ? pieces.join(" · ") : undefined;
|
|
860
|
-
}),
|
|
861
|
-
});
|
|
862
|
-
|
|
863
667
|
pi.registerTool({
|
|
864
668
|
name: "seedclub_list_program_headlines",
|
|
865
669
|
label: "List Program Headlines",
|
|
@@ -17,7 +17,7 @@ const DEFAULT_BOOKING_DURATION_MINUTES = 20;
|
|
|
17
17
|
const TRANSCRIPT_TEXT_PREVIEW_CHARS = 1800;
|
|
18
18
|
const TRANSCRIPT_VTT_PREVIEW_CHARS = 1000;
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const SHOW_GUEST_ALLOWED_FIELDS = new Set([
|
|
21
21
|
"date",
|
|
22
22
|
"startsAt",
|
|
23
23
|
"endsAt",
|
|
@@ -28,15 +28,17 @@ const GUEST_ROSTER_ALLOWED_FIELDS = new Set([
|
|
|
28
28
|
"partyId",
|
|
29
29
|
"meetingId",
|
|
30
30
|
"email",
|
|
31
|
+
"transcriptAvailable",
|
|
31
32
|
]);
|
|
32
33
|
|
|
33
|
-
const
|
|
34
|
+
const SHOW_GUEST_DEFAULT_FIELDS = [
|
|
34
35
|
"date",
|
|
35
36
|
"startsAt",
|
|
36
37
|
"title",
|
|
37
38
|
"displayName",
|
|
38
39
|
"organizationName",
|
|
39
40
|
"organizationRole",
|
|
41
|
+
"transcriptAvailable",
|
|
40
42
|
];
|
|
41
43
|
|
|
42
44
|
function summarizeCount(count: number | undefined, noun: string) {
|
|
@@ -436,33 +438,15 @@ function extractGuestSummary(row: any) {
|
|
|
436
438
|
};
|
|
437
439
|
}
|
|
438
440
|
|
|
439
|
-
function
|
|
441
|
+
function normalizeShowGuestFields(fields: string[] | undefined) {
|
|
440
442
|
const selected = Array.isArray(fields)
|
|
441
|
-
? fields.filter((field) => typeof field === "string" &&
|
|
443
|
+
? fields.filter((field) => typeof field === "string" && SHOW_GUEST_ALLOWED_FIELDS.has(field))
|
|
442
444
|
: [];
|
|
443
|
-
if (!selected.length) return [...
|
|
445
|
+
if (!selected.length) return [...SHOW_GUEST_DEFAULT_FIELDS];
|
|
444
446
|
return [...new Set(selected)];
|
|
445
447
|
}
|
|
446
448
|
|
|
447
|
-
function
|
|
448
|
-
const meeting = row?.meeting ?? null;
|
|
449
|
-
const guest = extractGuestSummary(row);
|
|
450
|
-
const startsAt = meeting?.starts_at ?? null;
|
|
451
|
-
return {
|
|
452
|
-
date: dateFromTimestamp(startsAt),
|
|
453
|
-
startsAt,
|
|
454
|
-
endsAt: meeting?.ends_at ?? null,
|
|
455
|
-
title: meeting?.title ?? null,
|
|
456
|
-
displayName: guest.displayName,
|
|
457
|
-
organizationName: guest.organizationName,
|
|
458
|
-
organizationRole: guest.organizationRole,
|
|
459
|
-
partyId: guest.partyId,
|
|
460
|
-
meetingId: options.includeMeetingIds ? (meeting?.id ?? null) : undefined,
|
|
461
|
-
email: options.includeEmails ? guest.email : undefined,
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function pickGuestRosterFields(row: Record<string, unknown>, fields: string[]) {
|
|
449
|
+
function pickShowGuestFields(row: Record<string, unknown>, fields: string[]) {
|
|
466
450
|
const picked: Record<string, unknown> = {};
|
|
467
451
|
for (const field of fields) {
|
|
468
452
|
if (field in row) picked[field] = row[field];
|
|
@@ -470,7 +454,7 @@ function pickGuestRosterFields(row: Record<string, unknown>, fields: string[]) {
|
|
|
470
454
|
return picked;
|
|
471
455
|
}
|
|
472
456
|
|
|
473
|
-
function
|
|
457
|
+
function shapeDistinctShowGuests(rows: Record<string, unknown>[]) {
|
|
474
458
|
const byGuest = new Map<string, any>();
|
|
475
459
|
for (const row of rows) {
|
|
476
460
|
const partyId = typeof row.partyId === "string" ? row.partyId : null;
|
|
@@ -489,6 +473,7 @@ function shapeDistinctGuestRoster(rows: Record<string, unknown>[]) {
|
|
|
489
473
|
firstSeenAt: startsAt,
|
|
490
474
|
lastSeenAt: startsAt,
|
|
491
475
|
appearances: 1,
|
|
476
|
+
transcriptAvailable: row.transcriptAvailable ?? null,
|
|
492
477
|
});
|
|
493
478
|
continue;
|
|
494
479
|
}
|
|
@@ -499,6 +484,7 @@ function shapeDistinctGuestRoster(rows: Record<string, unknown>[]) {
|
|
|
499
484
|
if (!existing.organizationName && row.organizationName) existing.organizationName = row.organizationName;
|
|
500
485
|
if (!existing.organizationRole && row.organizationRole) existing.organizationRole = row.organizationRole;
|
|
501
486
|
if (!existing.email && row.email) existing.email = row.email;
|
|
487
|
+
if (existing.transcriptAvailable !== true && row.transcriptAvailable === true) existing.transcriptAvailable = true;
|
|
502
488
|
}
|
|
503
489
|
|
|
504
490
|
return [...byGuest.values()].sort((a, b) => {
|
|
@@ -509,12 +495,13 @@ function shapeDistinctGuestRoster(rows: Record<string, unknown>[]) {
|
|
|
509
495
|
});
|
|
510
496
|
}
|
|
511
497
|
|
|
512
|
-
async function
|
|
498
|
+
async function listShowGuests(args: {
|
|
513
499
|
programSlug: string;
|
|
514
|
-
from
|
|
515
|
-
to
|
|
500
|
+
from?: string;
|
|
501
|
+
to?: string;
|
|
516
502
|
assignmentStatus?: string;
|
|
517
503
|
limit?: number;
|
|
504
|
+
includeTranscriptStatus?: boolean;
|
|
518
505
|
distinct?: boolean;
|
|
519
506
|
includeMeetingIds?: boolean;
|
|
520
507
|
includeEmails?: boolean;
|
|
@@ -528,8 +515,7 @@ async function listGuestRoster(args: {
|
|
|
528
515
|
const includeMeetingIds = args.includeMeetingIds === true;
|
|
529
516
|
const includeEmails = args.includeEmails === true;
|
|
530
517
|
const distinct = args.distinct === true;
|
|
531
|
-
|
|
532
|
-
const selectedFields = normalizeGuestRosterFields(args.fields);
|
|
518
|
+
const selectedFields = normalizeShowGuestFields(args.fields);
|
|
533
519
|
if (includeMeetingIds && !selectedFields.includes("meetingId")) selectedFields.push("meetingId");
|
|
534
520
|
if (includeEmails && !selectedFields.includes("email")) selectedFields.push("email");
|
|
535
521
|
|
|
@@ -541,49 +527,6 @@ async function listGuestRoster(args: {
|
|
|
541
527
|
limit,
|
|
542
528
|
});
|
|
543
529
|
|
|
544
|
-
const rows = Array.isArray(meetingsResponse?.data) ? meetingsResponse.data : [];
|
|
545
|
-
const rosterRows = rows.map((row: any) => buildGuestRosterRow(row, { includeMeetingIds, includeEmails }));
|
|
546
|
-
const distinctRows = distinct ? shapeDistinctGuestRoster(rosterRows) : null;
|
|
547
|
-
|
|
548
|
-
return {
|
|
549
|
-
programSlug: args.programSlug,
|
|
550
|
-
from: args.from,
|
|
551
|
-
to: args.to,
|
|
552
|
-
distinct,
|
|
553
|
-
fields: selectedFields,
|
|
554
|
-
count: distinct ? (distinctRows?.length ?? 0) : rosterRows.length,
|
|
555
|
-
guests: distinct
|
|
556
|
-
? (distinctRows ?? []).map((row: any) => pickGuestRosterFields(row, selectedFields))
|
|
557
|
-
: rosterRows.map((row: any) => pickGuestRosterFields(row, selectedFields)),
|
|
558
|
-
};
|
|
559
|
-
} catch (error) {
|
|
560
|
-
if (error instanceof ApiError) return { error: error.message, status: error.status };
|
|
561
|
-
throw error;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
async function listShowGuests(args: {
|
|
566
|
-
programSlug: string;
|
|
567
|
-
from?: string;
|
|
568
|
-
to?: string;
|
|
569
|
-
assignmentStatus?: string;
|
|
570
|
-
limit?: number;
|
|
571
|
-
includeTranscriptStatus?: boolean;
|
|
572
|
-
}) {
|
|
573
|
-
try {
|
|
574
|
-
const limit = normalizeLimit(args.limit, {
|
|
575
|
-
fallback: DEFAULT_MEETINGS_LIMIT,
|
|
576
|
-
max: MAX_MEETINGS_LIMIT,
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
const meetingsResponse = await api.get<any>("/meetings", {
|
|
580
|
-
program_slug: args.programSlug,
|
|
581
|
-
from: args.from,
|
|
582
|
-
to: args.to,
|
|
583
|
-
assignment_status: args.assignmentStatus,
|
|
584
|
-
limit,
|
|
585
|
-
});
|
|
586
|
-
|
|
587
530
|
const rows = Array.isArray(meetingsResponse?.data) ? meetingsResponse.data : [];
|
|
588
531
|
const includeTranscriptStatus = args.includeTranscriptStatus !== false;
|
|
589
532
|
|
|
@@ -603,12 +546,39 @@ async function listShowGuests(args: {
|
|
|
603
546
|
);
|
|
604
547
|
}
|
|
605
548
|
|
|
549
|
+
const guestRows = rows.map((row: any) => {
|
|
550
|
+
const meeting = row?.meeting ?? null;
|
|
551
|
+
const meetingId = meeting?.id ?? null;
|
|
552
|
+
const guest = extractGuestSummary(row);
|
|
553
|
+
const startsAt = meeting?.starts_at ?? null;
|
|
554
|
+
return {
|
|
555
|
+
date: dateFromTimestamp(startsAt),
|
|
556
|
+
startsAt,
|
|
557
|
+
endsAt: meeting?.ends_at ?? null,
|
|
558
|
+
title: meeting?.title ?? null,
|
|
559
|
+
displayName: guest.displayName,
|
|
560
|
+
organizationName: guest.organizationName,
|
|
561
|
+
organizationRole: guest.organizationRole,
|
|
562
|
+
partyId: guest.partyId,
|
|
563
|
+
meetingId: includeMeetingIds ? meetingId : undefined,
|
|
564
|
+
email: includeEmails ? guest.email : undefined,
|
|
565
|
+
transcriptAvailable: includeTranscriptStatus && meetingId ? transcriptByMeetingId.get(meetingId) === true : null,
|
|
566
|
+
};
|
|
567
|
+
});
|
|
568
|
+
const distinctGuestRows = distinct ? shapeDistinctShowGuests(guestRows) : null;
|
|
569
|
+
|
|
606
570
|
return {
|
|
607
571
|
program: meetingsResponse?.program ?? null,
|
|
608
572
|
from: args.from ?? null,
|
|
609
573
|
to: args.to ?? null,
|
|
610
574
|
total: rows.length,
|
|
611
575
|
returned: rows.length,
|
|
576
|
+
distinct,
|
|
577
|
+
fields: selectedFields,
|
|
578
|
+
count: distinct ? (distinctGuestRows?.length ?? 0) : guestRows.length,
|
|
579
|
+
guests: distinct
|
|
580
|
+
? (distinctGuestRows ?? []).map((row: any) => pickShowGuestFields(row, selectedFields))
|
|
581
|
+
: guestRows.map((row: any) => pickShowGuestFields(row, selectedFields)),
|
|
612
582
|
data: rows.map((row: any) => {
|
|
613
583
|
const meeting = row?.meeting ?? null;
|
|
614
584
|
const meetingId = meeting?.id ?? null;
|
|
@@ -634,19 +604,48 @@ function normalizeCompareValue(value: unknown) {
|
|
|
634
604
|
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
635
605
|
}
|
|
636
606
|
|
|
607
|
+
function primaryProgramRole(contact: any) {
|
|
608
|
+
return Array.isArray(contact?.roles) ? contact.roles[0] : contact?.primary_org_role ?? null;
|
|
609
|
+
}
|
|
610
|
+
|
|
637
611
|
function shapeContactCandidate(contact: any) {
|
|
612
|
+
const primaryRole = primaryProgramRole(contact);
|
|
638
613
|
return {
|
|
639
614
|
partyId: contact?.party?.id ?? null,
|
|
640
615
|
displayName: contact?.party?.display_name ?? contact?.person?.full_name ?? null,
|
|
641
616
|
email: contact?.person?.email ?? null,
|
|
642
|
-
organizationName:
|
|
643
|
-
organizationRole:
|
|
617
|
+
organizationName: primaryRole?.organization_name ?? null,
|
|
618
|
+
organizationRole: primaryRole?.role ?? null,
|
|
619
|
+
crmRecord: contact?.crm_record
|
|
620
|
+
? {
|
|
621
|
+
recordId: contact.crm_record.id ?? null,
|
|
622
|
+
engagementStatus: contact.crm_record.engagement_status ?? null,
|
|
623
|
+
ownerUserId: contact.crm_record.owner_user_id ?? null,
|
|
624
|
+
updatedAt: contact.crm_record.updated_at ?? null,
|
|
625
|
+
}
|
|
626
|
+
: null,
|
|
644
627
|
raw: {
|
|
645
628
|
partyType: contact?.party?.party_type ?? null,
|
|
646
629
|
},
|
|
647
630
|
};
|
|
648
631
|
}
|
|
649
632
|
|
|
633
|
+
function shapeContactCrmCandidate(contact: any) {
|
|
634
|
+
const primaryRole = primaryProgramRole(contact);
|
|
635
|
+
return contact?.crm_record
|
|
636
|
+
? {
|
|
637
|
+
recordId: contact.crm_record.id ?? null,
|
|
638
|
+
partyId: contact?.party?.id ?? null,
|
|
639
|
+
displayName: contact?.party?.display_name ?? contact?.person?.full_name ?? null,
|
|
640
|
+
email: contact?.person?.email ?? null,
|
|
641
|
+
engagementStatus: contact.crm_record.engagement_status ?? null,
|
|
642
|
+
ownerUserId: contact.crm_record.owner_user_id ?? null,
|
|
643
|
+
updatedAt: contact.crm_record.updated_at ?? null,
|
|
644
|
+
organizationName: primaryRole?.organization_name ?? null,
|
|
645
|
+
}
|
|
646
|
+
: null;
|
|
647
|
+
}
|
|
648
|
+
|
|
650
649
|
function shapeCrmCandidate(item: any) {
|
|
651
650
|
return {
|
|
652
651
|
recordId: item?.record?.id ?? null,
|
|
@@ -751,8 +750,26 @@ async function getGuestProfile(args: {
|
|
|
751
750
|
let crmMatches: any[] = [];
|
|
752
751
|
|
|
753
752
|
if (includeCrm) {
|
|
753
|
+
crmMatches = contactRows
|
|
754
|
+
.map((row: any) => {
|
|
755
|
+
const shaped = shapeContactCrmCandidate(row);
|
|
756
|
+
if (!shaped) return null;
|
|
757
|
+
const score = scoreCandidateMatch({
|
|
758
|
+
candidatePartyId: shaped.partyId,
|
|
759
|
+
candidateEmail: shaped.email,
|
|
760
|
+
candidateName: shaped.displayName,
|
|
761
|
+
partyId: resolvedPartyId ?? bestContact?.partyId ?? undefined,
|
|
762
|
+
email: resolvedEmail ?? bestContact?.email ?? undefined,
|
|
763
|
+
search: resolvedSearch,
|
|
764
|
+
});
|
|
765
|
+
return { shaped, score };
|
|
766
|
+
})
|
|
767
|
+
.filter((row: any) => row && (row.score > 0 || (!resolvedPartyId && !resolvedEmail)))
|
|
768
|
+
.sort((a: any, b: any) => b.score - a.score)
|
|
769
|
+
.map((row: any) => row.shaped);
|
|
770
|
+
|
|
754
771
|
const crmSearch = resolvedEmail ?? resolvedSearch;
|
|
755
|
-
if (crmSearch) {
|
|
772
|
+
if (!crmMatches.length && crmSearch) {
|
|
756
773
|
const crmResponse = await api.get<any>("/crm/records", {
|
|
757
774
|
search: crmSearch,
|
|
758
775
|
limit: 10,
|
|
@@ -1564,26 +1581,6 @@ async function cancelMeeting(args: { meetingId: string; calendarAccountId?: stri
|
|
|
1564
1581
|
}
|
|
1565
1582
|
}
|
|
1566
1583
|
|
|
1567
|
-
async function updateMeeting(args: {
|
|
1568
|
-
meetingId: string;
|
|
1569
|
-
startsAt?: string | null;
|
|
1570
|
-
endsAt?: string | null;
|
|
1571
|
-
title?: string | null;
|
|
1572
|
-
producerFraming?: string | null;
|
|
1573
|
-
}) {
|
|
1574
|
-
try {
|
|
1575
|
-
return await api.patch<any>(`/meetings/${args.meetingId}`, {
|
|
1576
|
-
starts_at: args.startsAt,
|
|
1577
|
-
ends_at: args.endsAt,
|
|
1578
|
-
title: args.title,
|
|
1579
|
-
producer_framing: args.producerFraming,
|
|
1580
|
-
});
|
|
1581
|
-
} catch (error) {
|
|
1582
|
-
if (error instanceof ApiError) return { error: error.message, status: error.status };
|
|
1583
|
-
throw error;
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
1584
|
async function assignMeeting(args: {
|
|
1588
1585
|
meetingId: string;
|
|
1589
1586
|
primaryPartyId: string;
|
|
@@ -1795,6 +1792,17 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1795
1792
|
to: Type.Optional(Type.String({ description: "Optional to ISO timestamp" })),
|
|
1796
1793
|
assignmentStatus: Type.Optional(Type.String({ description: "Optional assignment status filter" })),
|
|
1797
1794
|
limit: Type.Optional(Type.Number({ description: "Optional max rows to return. Defaults to 10, max 25." })),
|
|
1795
|
+
distinct: Type.Optional(Type.Boolean({ description: "Set true to de-duplicate guests." })),
|
|
1796
|
+
includeMeetingIds: Type.Optional(Type.Boolean({ description: "Set true to include meetingId in compact guest rows." })),
|
|
1797
|
+
includeEmails: Type.Optional(Type.Boolean({ description: "Set true to include guest emails in compact guest rows." })),
|
|
1798
|
+
fields: Type.Optional(
|
|
1799
|
+
Type.Array(
|
|
1800
|
+
Type.String({
|
|
1801
|
+
description:
|
|
1802
|
+
"Optional compact guest field projection. Allowed: date, startsAt, endsAt, title, displayName, organizationName, organizationRole, partyId, meetingId, email, transcriptAvailable.",
|
|
1803
|
+
}),
|
|
1804
|
+
),
|
|
1805
|
+
),
|
|
1798
1806
|
includeTranscriptStatus: Type.Optional(
|
|
1799
1807
|
Type.Boolean({
|
|
1800
1808
|
description: "Set true to check transcript availability for each meeting. Defaults to true.",
|
|
@@ -1809,37 +1817,6 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1809
1817
|
}),
|
|
1810
1818
|
});
|
|
1811
1819
|
|
|
1812
|
-
pi.registerTool({
|
|
1813
|
-
name: "seedclub_list_guest_roster",
|
|
1814
|
-
label: "List Guest Roster",
|
|
1815
|
-
description:
|
|
1816
|
-
"Return a compact, query-driven guest roster for a program and date range. Prefer this when the user asks who was on a show and you want a minimal payload.",
|
|
1817
|
-
parameters: Type.Object({
|
|
1818
|
-
programSlug: Type.String({ description: "Program slug" }),
|
|
1819
|
-
from: Type.String({ description: "Inclusive start date/time (ISO)." }),
|
|
1820
|
-
to: Type.String({ description: "Inclusive end date/time (ISO)." }),
|
|
1821
|
-
assignmentStatus: Type.Optional(Type.String({ description: "Optional assignment status filter" })),
|
|
1822
|
-
limit: Type.Optional(Type.Number({ description: "Optional max rows to return. Defaults to 10, max 25." })),
|
|
1823
|
-
distinct: Type.Optional(Type.Boolean({ description: "Set true to de-duplicate guests." })),
|
|
1824
|
-
includeMeetingIds: Type.Optional(Type.Boolean({ description: "Set true to include meetingId in rows." })),
|
|
1825
|
-
includeEmails: Type.Optional(Type.Boolean({ description: "Set true to include guest emails." })),
|
|
1826
|
-
fields: Type.Optional(
|
|
1827
|
-
Type.Array(
|
|
1828
|
-
Type.String({
|
|
1829
|
-
description:
|
|
1830
|
-
"Optional field projection. Allowed: date, startsAt, endsAt, title, displayName, organizationName, organizationRole, partyId, meetingId, email.",
|
|
1831
|
-
}),
|
|
1832
|
-
),
|
|
1833
|
-
),
|
|
1834
|
-
}),
|
|
1835
|
-
execute: wrapExecute(listGuestRoster),
|
|
1836
|
-
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_list_guest_roster"), (args) => args?.programSlug || undefined),
|
|
1837
|
-
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_list_guest_roster"), (details) => {
|
|
1838
|
-
const count = Array.isArray(details?.rows) ? details.rows.length : undefined;
|
|
1839
|
-
return typeof count === "number" ? `${count} guest${count === 1 ? "" : "s"}` : undefined;
|
|
1840
|
-
}),
|
|
1841
|
-
});
|
|
1842
|
-
|
|
1843
1820
|
pi.registerTool({
|
|
1844
1821
|
name: "seedclub_get_guest_profile",
|
|
1845
1822
|
label: "Get Guest Profile",
|
|
@@ -2124,25 +2101,55 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
2124
2101
|
});
|
|
2125
2102
|
|
|
2126
2103
|
pi.registerTool({
|
|
2127
|
-
name: "
|
|
2128
|
-
label: "
|
|
2129
|
-
description:
|
|
2104
|
+
name: "seedclub_reschedule_meeting",
|
|
2105
|
+
label: "Reschedule Meeting",
|
|
2106
|
+
description:
|
|
2107
|
+
"Reschedule a booked Seed Club meeting and update the calendar invite and studio timing together. Always fetch availability first with seedclub_list_meeting_availability and use one of the returned startsAt/endsAt pairs. Pass calendarAccountId when multiple writable calendars exist.",
|
|
2130
2108
|
parameters: Type.Object({
|
|
2131
2109
|
meetingId: Type.String({ description: "Meeting id" }),
|
|
2132
|
-
startsAt: Type.
|
|
2133
|
-
endsAt: Type.Optional(
|
|
2110
|
+
startsAt: Type.String({ description: "New meeting start time as an ISO timestamp from availability" }),
|
|
2111
|
+
endsAt: Type.Optional(
|
|
2112
|
+
Type.Union([Type.String(), Type.Null()], {
|
|
2113
|
+
description: "Optional new meeting end time as an ISO timestamp from availability.",
|
|
2114
|
+
}),
|
|
2115
|
+
),
|
|
2116
|
+
durationMinutes: Type.Optional(
|
|
2117
|
+
Type.Union([Type.Number(), Type.Null()], {
|
|
2118
|
+
description: "Optional meeting length in minutes if endsAt is not provided.",
|
|
2119
|
+
}),
|
|
2120
|
+
),
|
|
2121
|
+
timeZone: Type.Optional(Type.String({ description: "IANA timezone like America/New_York" })),
|
|
2134
2122
|
title: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
formatMeetingDateTimeRange(args?.startsAt, args?.endsAt),
|
|
2141
|
-
args?.title,
|
|
2142
|
-
args?.meetingId,
|
|
2123
|
+
description: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
2124
|
+
calendarAccountId: Type.Optional(
|
|
2125
|
+
Type.Union([Type.String(), Type.Null()], {
|
|
2126
|
+
description: "Optional calendar account id that owns the invite.",
|
|
2127
|
+
}),
|
|
2143
2128
|
),
|
|
2129
|
+
}),
|
|
2130
|
+
execute: wrapExecute(rescheduleMeeting),
|
|
2131
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_reschedule_meeting"), (args) =>
|
|
2132
|
+
firstNonEmptyString(formatMeetingDateTimeRange(args?.startsAt, args?.endsAt), args?.meetingId),
|
|
2144
2133
|
),
|
|
2145
|
-
renderResult: makeProgressResultRenderer(getToolSuccessLabel("
|
|
2134
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_reschedule_meeting")),
|
|
2135
|
+
});
|
|
2136
|
+
|
|
2137
|
+
pi.registerTool({
|
|
2138
|
+
name: "seedclub_cancel_meeting",
|
|
2139
|
+
label: "Cancel Meeting",
|
|
2140
|
+
description:
|
|
2141
|
+
"Cancel a booked Seed Club meeting and clean up the calendar invite and studio access. Pass calendarAccountId when multiple writable calendars exist.",
|
|
2142
|
+
parameters: Type.Object({
|
|
2143
|
+
meetingId: Type.String({ description: "Meeting id" }),
|
|
2144
|
+
calendarAccountId: Type.Optional(
|
|
2145
|
+
Type.Union([Type.String(), Type.Null()], {
|
|
2146
|
+
description: "Optional calendar account id that owns the invite.",
|
|
2147
|
+
}),
|
|
2148
|
+
),
|
|
2149
|
+
}),
|
|
2150
|
+
execute: wrapExecute(cancelMeeting),
|
|
2151
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_cancel_meeting"), (args) => args?.meetingId || undefined),
|
|
2152
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_cancel_meeting")),
|
|
2146
2153
|
});
|
|
2147
2154
|
|
|
2148
2155
|
pi.registerTool({
|
|
@@ -8,6 +8,12 @@ export const TOOL_CALL_LABELS: Record<string, string> = {
|
|
|
8
8
|
seedclub_create_crm_note: "Saving CRM note",
|
|
9
9
|
seedclub_create_crm_task: "Creating CRM task",
|
|
10
10
|
seedclub_list_program_contacts: "Loading program contacts",
|
|
11
|
+
seedclub_list_program_funnels: "Loading program funnels",
|
|
12
|
+
seedclub_create_funnel_stage: "Creating funnel stage",
|
|
13
|
+
seedclub_update_funnel_stage: "Updating funnel stage",
|
|
14
|
+
seedclub_list_funnel_contacts: "Loading funnel contacts",
|
|
15
|
+
seedclub_list_funnel_stage_contacts: "Loading funnel stage contacts",
|
|
16
|
+
seedclub_move_funnel_enrollment: "Moving funnel enrollment",
|
|
11
17
|
seedclub_search_people: "Searching people",
|
|
12
18
|
seedclub_list_meeting_availability: "Loading availability slots",
|
|
13
19
|
seedclub_list_meeting_calendars: "Loading booking calendars",
|
|
@@ -18,7 +24,6 @@ export const TOOL_CALL_LABELS: Record<string, string> = {
|
|
|
18
24
|
seedclub_find_common_meeting_availability: "Checking common availability",
|
|
19
25
|
seedclub_list_meetings: "Checking meeting schedule",
|
|
20
26
|
seedclub_list_show_guests: "Looking up show guests",
|
|
21
|
-
seedclub_list_guest_roster: "Building guest roster",
|
|
22
27
|
seedclub_get_guest_profile: "Resolving guest profile",
|
|
23
28
|
seedclub_find_latest_guest_transcript: "Finding the latest guest transcript",
|
|
24
29
|
seedclub_prepare_clip_packet: "Checking clip readiness",
|
|
@@ -29,12 +34,10 @@ export const TOOL_CALL_LABELS: Record<string, string> = {
|
|
|
29
34
|
seedclub_book_meeting: "Booking meeting",
|
|
30
35
|
seedclub_reschedule_meeting: "Rescheduling meeting",
|
|
31
36
|
seedclub_cancel_meeting: "Cancelling meeting",
|
|
32
|
-
seedclub_update_meeting: "Updating meeting",
|
|
33
37
|
seedclub_assign_meeting: "Assigning meeting",
|
|
34
38
|
seedclub_list_program_media_assets: "Checking media assets",
|
|
35
39
|
seedclub_get_program_media_asset: "Loading media asset details",
|
|
36
40
|
seedclub_upload_clip_asset: "Uploading clip asset",
|
|
37
|
-
seedclub_publish_clip_asset: "Publishing clip asset",
|
|
38
41
|
seedclub_list_program_headlines: "Loading program headlines",
|
|
39
42
|
seedclub_create_program_headline: "Saving headline",
|
|
40
43
|
seedclub_update_program_headline: "Updating headline",
|
|
@@ -51,6 +54,12 @@ export const TOOL_SUCCESS_LABELS: Record<string, string> = {
|
|
|
51
54
|
seedclub_create_crm_note: "CRM note saved",
|
|
52
55
|
seedclub_create_crm_task: "CRM task created",
|
|
53
56
|
seedclub_list_program_contacts: "Program contacts loaded",
|
|
57
|
+
seedclub_list_program_funnels: "Program funnels loaded",
|
|
58
|
+
seedclub_create_funnel_stage: "Funnel stage created",
|
|
59
|
+
seedclub_update_funnel_stage: "Funnel stage updated",
|
|
60
|
+
seedclub_list_funnel_contacts: "Funnel contacts loaded",
|
|
61
|
+
seedclub_list_funnel_stage_contacts: "Funnel stage contacts loaded",
|
|
62
|
+
seedclub_move_funnel_enrollment: "Funnel enrollment moved",
|
|
54
63
|
seedclub_search_people: "People loaded",
|
|
55
64
|
seedclub_list_meeting_availability: "Availability loaded",
|
|
56
65
|
seedclub_list_meeting_calendars: "Booking calendars loaded",
|
|
@@ -61,7 +70,6 @@ export const TOOL_SUCCESS_LABELS: Record<string, string> = {
|
|
|
61
70
|
seedclub_find_common_meeting_availability: "Common availability loaded",
|
|
62
71
|
seedclub_list_meetings: "Meeting schedule loaded",
|
|
63
72
|
seedclub_list_show_guests: "Show guests loaded",
|
|
64
|
-
seedclub_list_guest_roster: "Guest roster ready",
|
|
65
73
|
seedclub_get_guest_profile: "Guest profile loaded",
|
|
66
74
|
seedclub_find_latest_guest_transcript: "Guest transcript loaded",
|
|
67
75
|
seedclub_prepare_clip_packet: "Clip readiness checked",
|
|
@@ -72,12 +80,10 @@ export const TOOL_SUCCESS_LABELS: Record<string, string> = {
|
|
|
72
80
|
seedclub_book_meeting: "Meeting booked",
|
|
73
81
|
seedclub_reschedule_meeting: "Meeting rescheduled",
|
|
74
82
|
seedclub_cancel_meeting: "Meeting cancelled",
|
|
75
|
-
seedclub_update_meeting: "Meeting updated",
|
|
76
83
|
seedclub_assign_meeting: "Meeting assigned",
|
|
77
84
|
seedclub_list_program_media_assets: "Media assets checked",
|
|
78
85
|
seedclub_get_program_media_asset: "Media asset details loaded",
|
|
79
86
|
seedclub_upload_clip_asset: "Clip asset uploaded",
|
|
80
|
-
seedclub_publish_clip_asset: "Clip asset published",
|
|
81
87
|
seedclub_list_program_headlines: "Program headlines loaded",
|
|
82
88
|
seedclub_create_program_headline: "Headline saved",
|
|
83
89
|
seedclub_update_program_headline: "Headline updated",
|
package/package.json
CHANGED