mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-10 01:43:43 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3134e5027a | |||
| 94024a7cdf |
@@ -33,6 +33,17 @@ export const PROMPT_DEFINITIONS: ListPromptsResult['prompts'] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'manage_custom_fields',
|
||||
description: 'List, create, rename, recolour, or delete project-level custom field definitions',
|
||||
arguments: [
|
||||
{
|
||||
name: 'instruction',
|
||||
description: 'What to do, e.g. "add a Speaker field" or "delete the Camera field"',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function userPrompt(description: string, text: string): GetPromptResult {
|
||||
@@ -79,9 +90,11 @@ Count to end (countToEnd):
|
||||
- Do not set \`countToEnd: true\` unless the user explicitly asks for "count to end", "count to scheduled end", or confirms after you explain this behaviour.
|
||||
|
||||
End action (endAction):
|
||||
- \`none\` (default): stops at end; operator must manually start the next event.
|
||||
- \`load-next\`: pre-arms the next event; operator triggers start. Use when a human handoff is needed.
|
||||
- \`play-next\`: automatically starts the next event. Use for seamless back-to-back segments with no gap.
|
||||
- Leave \`endAction\` unset unless the user explicitly asks for automatic cueing or chaining. It defaults to \`none\`.
|
||||
- \`load-next\` and \`play-next\` create playback automations that remove operator control between events and can surprise operators. Do NOT add them just because events are scheduled back-to-back — a seamless schedule does not imply automated playback.
|
||||
- \`none\` (default): stops at end; operator manually starts the next event. Use this for essentially every event unless told otherwise.
|
||||
- \`load-next\`: pre-arms the next event; operator triggers start. Only set when the user explicitly asks for it.
|
||||
- \`play-next\`: automatically starts the next event. Only set when the user explicitly asks for an automatic playback chain; confirm they understand it removes operator control between those events.
|
||||
|
||||
Linking (linkStart):
|
||||
- \`linkStart\` controls schedule-change propagation through the rundown.
|
||||
@@ -203,5 +216,36 @@ Efficiency tip: plan moves in the direction of the target position to minimise r
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'manage_custom_fields') {
|
||||
return userPrompt(
|
||||
'List, create, rename, recolour, or delete custom field definitions',
|
||||
`Manage the project-level custom field definitions for this Ontime project: "${args.instruction}"
|
||||
|
||||
Custom fields are project-scoped. They appear as columns in the cuesheet and can be set on any event, milestone, or group. Changes apply to all rundowns in the project.
|
||||
|
||||
Steps:
|
||||
1. Call ontime_get_custom_fields to see what fields already exist. This is mandatory before creating — to avoid duplicates such as "Cam", "camera", and "Cameras".
|
||||
2. Carry out the instruction using the tools below. Confirm destructive changes (rename, delete) with the user before calling.
|
||||
|
||||
Creating a field:
|
||||
- Call ontime_create_custom_field with { label, type, colour }.
|
||||
- label: human-readable name (alphanumeric with spaces). The key is auto-derived: spaces → underscores (e.g. "Camera Angle" → "Camera_Angle"). Confirm the derived key with the user before creating.
|
||||
- type: "text" for short string values, "image" for image URLs. Cannot be changed after creation.
|
||||
- colour: hex colour (#RRGGBB) used to visually identify this column in the cuesheet. Ask the user what colour to use if not specified.
|
||||
- Returns: { key, customFields } — use the returned key when setting values on entries.
|
||||
|
||||
Renaming or recolouring a field:
|
||||
- Call ontime_update_custom_field with { key, label?, colour? }.
|
||||
- Warning: changing the label changes the derived key and cascades to all entry references in all rundowns. Confirm with the user before renaming.
|
||||
- The field type cannot be changed.
|
||||
|
||||
Deleting a field:
|
||||
- Call ontime_delete_custom_field with { key }.
|
||||
- Warning: this permanently removes the field definition and its values from every entry in all rundowns. Confirm with the user before deleting.
|
||||
|
||||
After any mutation, call ontime_get_custom_fields again to confirm the result and show the user the updated field list with their keys.`,
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(`Unknown prompt: ${name}`);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ export const EVENT_TIMER_FIELDS = {
|
||||
endAction: {
|
||||
type: 'string',
|
||||
enum: ['none', 'load-next', 'play-next'],
|
||||
description: 'Action when event ends: none = stop, load-next = cue next event, play-next = auto-start next event',
|
||||
description:
|
||||
'Action when the event ends. Defaults to none. load-next and play-next create playback automations (load-next arms the next event, play-next auto-starts it) that remove operator control between events and can surprise operators. Only set load-next or play-next when the user explicitly asks for automatic cueing or chaining; otherwise omit this field and leave it as none. Do not add end actions just because events are back-to-back.',
|
||||
},
|
||||
linkStart: {
|
||||
type: 'boolean',
|
||||
@@ -116,7 +117,7 @@ There are four entry types discriminated by \`type\`:
|
||||
dayOffset: number // runtime calculated day offset from the rundown start schedule, increments when the rundown crosses midnight
|
||||
gap: number // schedule gap between sequential playable events; negative gap means overlap
|
||||
timerType: 'count-down' | 'count-up' | 'clock' | 'none'
|
||||
endAction: 'none' | 'load-next' | 'play-next'
|
||||
endAction: 'none' | 'load-next' | 'play-next' // load-next/play-next create playback automations; leave as none unless the user explicitly asks for cueing/chaining
|
||||
linkStart: boolean // chain start to previous event's end
|
||||
countToEnd: boolean // advanced mode: counts to scheduled timeEnd instead of duration; confirm before enabling
|
||||
skip: boolean // event is skipped during playback
|
||||
@@ -177,8 +178,15 @@ Examples:
|
||||
## Custom fields
|
||||
Custom fields are project-scoped definitions. Get definitions at \`ontime://project/custom-fields\` or with \`ontime_get_custom_fields\`.
|
||||
Events, milestones and groups store values at \`entry.custom[fieldKey]\`.
|
||||
Only use existing field keys when setting \`custom\` values. Adding, renaming, or deleting custom field definitions is a separate project-level operation.
|
||||
When the user wants to assign custom values, show the existing custom field list first if there is any ambiguity, so you do not create duplicate concepts such as \`Cam\`, \`camera\`, and \`Cameras\`.
|
||||
|
||||
Managing field definitions:
|
||||
- Read: \`ontime_get_custom_fields\` — returns \`{ [key]: { label, type, colour } }\`
|
||||
- Create: \`ontime_create_custom_field { label, type, colour }\` — key is auto-derived from label (spaces → underscores). Confirm the derived key before creating. Returns \`{ key, customFields }\`.
|
||||
- Rename/recolour: \`ontime_update_custom_field { key, label?, colour? }\` — renaming the label changes the derived key and cascades to all entry references across all rundowns. Type cannot be changed.
|
||||
- Delete: \`ontime_delete_custom_field { key }\` — removes the field definition and its values from every entry in all rundowns. Destructive, confirm first.
|
||||
|
||||
When setting \`custom\` values on entries, only use existing field keys. If a key does not exist, create it with \`ontime_create_custom_field\` first.
|
||||
Show the existing field list before creating to avoid duplicates such as \`Cam\`, \`camera\`, and \`Cameras\`.
|
||||
|
||||
## Targeting rundowns
|
||||
Entry read/write tools accept an optional \`rundownId\`.
|
||||
|
||||
@@ -17,7 +17,10 @@ import { getCurrentRundown, getCurrentRundownId, getProjectCustomFields } from '
|
||||
import {
|
||||
addEntry,
|
||||
batchEditEntries,
|
||||
createCustomField,
|
||||
deleteCustomField,
|
||||
deleteEntries,
|
||||
editCustomField,
|
||||
editEntry,
|
||||
groupEntries,
|
||||
reorderEntry,
|
||||
@@ -103,8 +106,15 @@ export function assertKnownCustomFields(...customValues: Array<EntryFieldArgs['c
|
||||
}
|
||||
|
||||
if (unknownKeys.size > 0) {
|
||||
const keys = [...unknownKeys].join(', ');
|
||||
throw new Error(`Unknown custom field key(s): ${keys}. Call ontime_get_custom_fields to list available keys.`);
|
||||
const missing = [...unknownKeys].join(', ');
|
||||
const available = Object.keys(customFields);
|
||||
const hint = available.length > 0 ? `Available keys: ${available.join(', ')}.` : 'No custom fields are defined yet.';
|
||||
throw new Error(
|
||||
`Unknown custom field key(s): ${missing}. ${hint} ` +
|
||||
`Call ontime_create_custom_field with { label, type, colour } to create a missing field — ` +
|
||||
`the key is auto-derived from the label (spaces → underscores, e.g. label "Camera" → key "Camera"). ` +
|
||||
`Call ontime_get_custom_fields to list existing keys.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,3 +330,21 @@ export async function batchUpdateEntriesForMcp(args: TargetRundownArgs & { ids:
|
||||
const rundown = await batchEditEntries(rundownId, args.ids, args.data);
|
||||
return { target: getTargetMeta(rundownId), updated: args.ids, order: rundown.order };
|
||||
}
|
||||
|
||||
export async function createCustomFieldForMcp(args: { label: string; type: 'text' | 'image'; colour: string }) {
|
||||
const updated = await createCustomField({ label: args.label, type: args.type, colour: args.colour });
|
||||
const key = Object.keys(updated).find((k) => updated[k].label === args.label) ?? args.label;
|
||||
return { key, customFields: updated };
|
||||
}
|
||||
|
||||
export async function updateCustomFieldForMcp(args: { key: string; label?: string; colour?: string }) {
|
||||
const projectRundowns = getDataProvider().getProjectRundowns();
|
||||
const updated = await editCustomField(args.key, { label: args.label, colour: args.colour }, projectRundowns);
|
||||
return { customFields: updated };
|
||||
}
|
||||
|
||||
export async function deleteCustomFieldForMcp(args: { key: string }) {
|
||||
const projectRundowns = getDataProvider().getProjectRundowns();
|
||||
const updated = await deleteCustomField(args.key, projectRundowns);
|
||||
return { customFields: updated };
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ import { EVENT_WRITABLE_FIELDS, RUNDOWN_TARGET_FIELD } from './mcp.schema.js';
|
||||
import {
|
||||
batchCreateEntriesForMcp,
|
||||
batchUpdateEntriesForMcp,
|
||||
createCustomFieldForMcp,
|
||||
createEntryForMcp,
|
||||
deleteCustomFieldForMcp,
|
||||
deleteEntriesForMcp,
|
||||
findEntry,
|
||||
getRundownById,
|
||||
@@ -33,6 +35,7 @@ import {
|
||||
reorderEntryForMcp,
|
||||
toRundownList,
|
||||
ungroupEntryForMcp,
|
||||
updateCustomFieldForMcp,
|
||||
updateEntryForMcp,
|
||||
type BatchCreateEntryArgs,
|
||||
type CreateEntryArgs,
|
||||
@@ -371,6 +374,60 @@ export const TOOL_DEFINITIONS = [
|
||||
inputSchema: { type: 'object', properties: {} },
|
||||
annotations: READ,
|
||||
},
|
||||
{
|
||||
name: 'ontime_create_custom_field',
|
||||
description:
|
||||
'Create a new project-level custom field definition. Custom fields add typed columns to every entry in all rundowns. The key is auto-derived from the label (spaces → underscores, e.g. "Camera Angle" → "Camera_Angle"). After creation, use the key in entry.custom when creating or updating entries.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
required: ['label', 'type', 'colour'],
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Human-readable label (alphanumeric with spaces, e.g. "Camera"). Determines the key — ask the user to confirm before creating to avoid duplicates like "Cam", "camera", "Cameras".',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['text', 'image'],
|
||||
description: 'Field type — cannot be changed after creation. Use "text" for short text values; "image" for image URLs.',
|
||||
},
|
||||
colour: {
|
||||
type: 'string',
|
||||
description: 'Hex colour (#RRGGBB) used to visually identify this column in the cuesheet.',
|
||||
},
|
||||
},
|
||||
},
|
||||
annotations: WRITE,
|
||||
},
|
||||
{
|
||||
name: 'ontime_update_custom_field',
|
||||
description:
|
||||
'Update a custom field label or colour. Changing the label renames the derived key (spaces → underscores) and updates all entry references across all rundowns. Field type cannot be changed.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
required: ['key'],
|
||||
properties: {
|
||||
key: { type: 'string', description: 'Current field key (from ontime_get_custom_fields)' },
|
||||
label: { type: 'string', description: 'New human-readable label (optional). Changes the derived key and cascades to all entries.' },
|
||||
colour: { type: 'string', description: 'New hex colour (#RRGGBB) (optional)' },
|
||||
},
|
||||
},
|
||||
annotations: WRITE_IDEM,
|
||||
},
|
||||
{
|
||||
name: 'ontime_delete_custom_field',
|
||||
description:
|
||||
'Delete a custom field definition and remove its values from all entries in all rundowns. Destructive and cannot be undone — confirm with the user before calling.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
required: ['key'],
|
||||
properties: {
|
||||
key: { type: 'string', description: 'Field key to delete (from ontime_get_custom_fields)' },
|
||||
},
|
||||
},
|
||||
annotations: WRITE_DESTRUCTIVE,
|
||||
},
|
||||
// --- Project file management ---
|
||||
{
|
||||
name: 'ontime_list_projects',
|
||||
@@ -571,6 +628,18 @@ const TOOL_HANDLERS: Record<ToolName, (args: Record<string, unknown>) => Promise
|
||||
|
||||
ontime_get_custom_fields: async () => ok(getProjectCustomFields()),
|
||||
|
||||
ontime_create_custom_field: async (args) => {
|
||||
return ok(await createCustomFieldForMcp(args as { label: string; type: 'text' | 'image'; colour: string }));
|
||||
},
|
||||
|
||||
ontime_update_custom_field: async (args) => {
|
||||
return ok(await updateCustomFieldForMcp(args as { key: string; label?: string; colour?: string }));
|
||||
},
|
||||
|
||||
ontime_delete_custom_field: async (args) => {
|
||||
return ok(await deleteCustomFieldForMcp(args as { key: string }));
|
||||
},
|
||||
|
||||
ontime_list_projects: async () => ok(await getProjectList()),
|
||||
|
||||
ontime_load_project: async (args) => {
|
||||
|
||||
Reference in New Issue
Block a user