nested tag dropdown, more well-defined nested outputs, keyboard nav for context menus, etc

This commit is contained in:
waleed
2026-01-18 12:33:33 -08:00
parent 93bb547222
commit a5073aaffb
44 changed files with 3538 additions and 772 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -51,13 +51,17 @@ Retrieve a single response or list responses from a Google Form
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `response` | json | Operation response data |
| `formId` | string | Form ID |
| `title` | string | Form title |
| `responderUri` | string | Form responder URL |
| `items` | json | Form items |
| `responses` | json | Form responses |
| `watches` | json | Form watches |
| `responses` | array | Array of form responses \(when no responseId provided\) |
| ↳ `responseId` | string | Unique response ID |
| ↳ `createTime` | string | When the response was created |
| ↳ `lastSubmittedTime` | string | When the response was last submitted |
| ↳ `answers` | json | Map of question IDs to answer values |
| `response` | object | Single form response \(when responseId is provided\) |
| ↳ `responseId` | string | Unique response ID |
| ↳ `createTime` | string | When the response was created |
| ↳ `lastSubmittedTime` | string | When the response was last submitted |
| ↳ `answers` | json | Map of question IDs to answer values |
| `raw` | json | Raw API response data |
### `google_forms_get_form`
@@ -126,8 +130,48 @@ Apply multiple updates to a form (add items, update info, change settings, etc.)
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `replies` | array | The replies from each update request |
| `writeControl` | json | Write control information with revision IDs |
| `form` | json | The updated form \(if includeFormInResponse was true\) |
| `writeControl` | object | Write control information with revision IDs |
| ↳ `requiredRevisionId` | string | Required revision ID for conflict detection |
| ↳ `targetRevisionId` | string | Target revision ID |
| `form` | object | The updated form \(if includeFormInResponse was true\) |
| ↳ `formId` | string | The form ID |
| ↳ `info` | object | Form info containing title and description |
| ↳ `title` | string | The form title visible to responders |
| ↳ `description` | string | The form description |
| ↳ `documentTitle` | string | The document title visible in Drive |
| ↳ `title` | string | Item title |
| ↳ `description` | string | Item description |
| ↳ `documentTitle` | string | The document title visible in Drive |
| ↳ `settings` | object | Form settings |
| ↳ `quizSettings` | object | Quiz settings |
| ↳ `isQuiz` | boolean | Whether the form is a quiz |
| ↳ `isQuiz` | boolean | Whether the form is a quiz |
| ↳ `emailCollectionType` | string | Email collection type |
| ↳ `quizSettings` | object | Quiz settings |
| ↳ `isQuiz` | boolean | Whether the form is a quiz |
| ↳ `isQuiz` | boolean | Whether the form is a quiz |
| ↳ `emailCollectionType` | string | Email collection type |
| ↳ `itemId` | string | Item ID |
| ↳ `questionItem` | json | Question item configuration |
| ↳ `questionGroupItem` | json | Question group configuration |
| ↳ `pageBreakItem` | json | Page break configuration |
| ↳ `textItem` | json | Text item configuration |
| ↳ `imageItem` | json | Image item configuration |
| ↳ `videoItem` | json | Video item configuration |
| ↳ `revisionId` | string | The revision ID of the form |
| ↳ `responderUri` | string | The URI to share with responders |
| ↳ `linkedSheetId` | string | The ID of the linked Google Sheet |
| ↳ `publishSettings` | object | Form publish settings |
| ↳ `publishState` | object | Current publish state |
| ↳ `isPublished` | boolean | Whether the form is published |
| ↳ `isAcceptingResponses` | boolean | Whether the form is accepting responses |
| ↳ `isPublished` | boolean | Whether the form is published |
| ↳ `isAcceptingResponses` | boolean | Whether the form is accepting responses |
| ↳ `publishState` | object | Current publish state |
| ↳ `isPublished` | boolean | Whether the form is published |
| ↳ `isAcceptingResponses` | boolean | Whether the form is accepting responses |
| ↳ `isPublished` | boolean | Whether the form is published |
| ↳ `isAcceptingResponses` | boolean | Whether the form is accepting responses |
### `google_forms_set_publish_settings`

View File

@@ -194,9 +194,14 @@ Get detailed information about a specific slide/page in a Google Slides presenta
| --------- | ---- | ----------- |
| `objectId` | string | The object ID of the page |
| `pageType` | string | The type of page \(SLIDE, MASTER, LAYOUT, NOTES, NOTES_MASTER\) |
| `pageElements` | json | Array of page elements \(shapes, images, tables, etc.\) on this page |
| `slideProperties` | json | Properties specific to slides \(layout, master, notes\) |
| `metadata` | json | Operation metadata including presentation ID and URL |
| `pageElements` | array | Array of page elements \(shapes, images, tables, etc.\) on this page |
| `slideProperties` | object | Properties specific to slides \(layout, master, notes\) |
| ↳ `layoutObjectId` | string | Object ID of the layout this slide is based on |
| ↳ `masterObjectId` | string | Object ID of the master this slide is based on |
| ↳ `notesPage` | json | The notes page associated with the slide |
| `metadata` | object | Operation metadata including presentation ID and URL |
| ↳ `presentationId` | string | The presentation ID |
| ↳ `url` | string | URL to the presentation |
### `google_slides_delete_object`
@@ -215,7 +220,9 @@ Delete a page element (shape, image, table, etc.) or an entire slide from a Goog
| --------- | ---- | ----------- |
| `deleted` | boolean | Whether the object was successfully deleted |
| `objectId` | string | The object ID that was deleted |
| `metadata` | json | Operation metadata including presentation ID and URL |
| `metadata` | object | Operation metadata including presentation ID and URL |
| ↳ `presentationId` | string | The presentation ID |
| ↳ `url` | string | URL to the presentation |
### `google_slides_duplicate_object`
@@ -235,7 +242,10 @@ Duplicate an object (slide, shape, image, table, etc.) in a Google Slides presen
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `duplicatedObjectId` | string | The object ID of the newly created duplicate |
| `metadata` | json | Operation metadata including presentation ID and source object ID |
| `metadata` | object | Operation metadata including presentation ID and source object ID |
| ↳ `presentationId` | string | The presentation ID |
| ↳ `sourceObjectId` | string | The original object ID that was duplicated |
| ↳ `url` | string | URL to the presentation |
### `google_slides_update_slides_position`
@@ -256,7 +266,9 @@ Move one or more slides to a new position in a Google Slides presentation
| `moved` | boolean | Whether the slides were successfully moved |
| `slideObjectIds` | array | The slide object IDs that were moved |
| `insertionIndex` | number | The index where the slides were moved to |
| `metadata` | json | Operation metadata including presentation ID and URL |
| `metadata` | object | Operation metadata including presentation ID and URL |
| ↳ `presentationId` | string | The presentation ID |
| ↳ `url` | string | URL to the presentation |
### `google_slides_create_table`
@@ -282,7 +294,10 @@ Create a new table on a slide in a Google Slides presentation
| `tableId` | string | The object ID of the newly created table |
| `rows` | number | Number of rows in the table |
| `columns` | number | Number of columns in the table |
| `metadata` | json | Operation metadata including presentation ID and page object ID |
| `metadata` | object | Operation metadata including presentation ID and page object ID |
| ↳ `presentationId` | string | The presentation ID |
| ↳ `pageObjectId` | string | The page object ID where the table was created |
| ↳ `url` | string | URL to the presentation |
### `google_slides_create_shape`
@@ -306,7 +321,10 @@ Create a shape (rectangle, ellipse, text box, arrow, etc.) on a slide in a Googl
| --------- | ---- | ----------- |
| `shapeId` | string | The object ID of the newly created shape |
| `shapeType` | string | The type of shape that was created |
| `metadata` | json | Operation metadata including presentation ID and page object ID |
| `metadata` | object | Operation metadata including presentation ID and page object ID |
| ↳ `presentationId` | string | The presentation ID |
| ↳ `pageObjectId` | string | The page object ID where the shape was created |
| ↳ `url` | string | URL to the presentation |
### `google_slides_insert_text`
@@ -328,6 +346,8 @@ Insert text into a shape or table cell in a Google Slides presentation. Use this
| `inserted` | boolean | Whether the text was successfully inserted |
| `objectId` | string | The object ID where text was inserted |
| `text` | string | The text that was inserted |
| `metadata` | json | Operation metadata including presentation ID and URL |
| `metadata` | object | Operation metadata including presentation ID and URL |
| ↳ `presentationId` | string | The presentation ID |
| ↳ `url` | string | URL to the presentation |

View File

@@ -38,9 +38,11 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
'https://www.googleapis.com/auth/gmail.modify': 'View and manage email messages',
'https://www.googleapis.com/auth/drive.file': 'View and manage Google Drive files',
'https://www.googleapis.com/auth/drive': 'Access all Google Drive files',
'https://www.googleapis.com/auth/drive.readonly': 'View Google Drive files',
'https://www.googleapis.com/auth/calendar': 'View and manage calendar',
'https://www.googleapis.com/auth/userinfo.email': 'View email address',
'https://www.googleapis.com/auth/userinfo.profile': 'View basic profile info',
'https://www.googleapis.com/auth/forms.body': 'View and manage Google Forms',
'https://www.googleapis.com/auth/forms.responses.readonly': 'View responses to Google Forms',
'https://www.googleapis.com/auth/ediscovery': 'Access Google Vault for eDiscovery',
'https://www.googleapis.com/auth/devstorage.read_only': 'Read files from Google Cloud Storage',

View File

@@ -1,6 +1,7 @@
import { useEffect, useMemo } from 'react'
import { usePopoverContext } from '@/components/emcn'
import type { BlockTagGroup, NestedBlockTagGroup } from '../types'
import { useNestedNavigation } from '../tag-dropdown'
import type { BlockTagGroup, NestedBlockTagGroup, NestedTag } from '../types'
/**
* Keyboard navigation handler component that uses popover context
@@ -15,6 +16,96 @@ interface KeyboardNavigationHandlerProps {
handleTagSelect: (tag: string, group?: BlockTagGroup) => void
}
/**
* Recursively finds a folder in nested tags by its ID
*/
const findFolderInNested = (
nestedTags: NestedTag[],
blockId: string,
targetFolderId: string
): NestedTag | null => {
for (const nestedTag of nestedTags) {
const folderId = `${blockId}-${nestedTag.key}`
if (folderId === targetFolderId) {
return nestedTag
}
// Recursively search in nested children
if (nestedTag.nestedChildren) {
const found = findFolderInNested(nestedTag.nestedChildren, blockId, targetFolderId)
if (found) return found
}
}
return null
}
/**
* Recursively finds folder info for a tag that can be expanded.
* Returns both the folder metadata and the NestedTag object for navigation.
*/
const findFolderInfoForTag = (
nestedTags: NestedTag[],
targetTag: string,
group: NestedBlockTagGroup
): {
id: string
title: string
parentTag: string
group: NestedBlockTagGroup
nestedTag: NestedTag
} | null => {
for (const nestedTag of nestedTags) {
// Check if this tag is a folder (has children or nestedChildren)
if (
nestedTag.parentTag === targetTag &&
(nestedTag.children?.length || nestedTag.nestedChildren?.length)
) {
return {
id: `${group.blockId}-${nestedTag.key}`,
title: nestedTag.display,
parentTag: nestedTag.parentTag,
group,
nestedTag,
}
}
// Recursively search in nested children
if (nestedTag.nestedChildren) {
const found = findFolderInfoForTag(nestedTag.nestedChildren, targetTag, group)
if (found) return found
}
}
return null
}
/**
* Recursively checks if a tag is a child of any folder.
* This includes both leaf children and nested folder parent tags.
*/
const isChildOfAnyFolder = (nestedTags: NestedTag[], tag: string): boolean => {
for (const nestedTag of nestedTags) {
// Check in leaf children
if (nestedTag.children) {
for (const child of nestedTag.children) {
if (child.fullTag === tag) {
return true
}
}
}
// Check if this is a nested folder's parent tag (should be hidden at root)
if (nestedTag.nestedChildren) {
for (const nestedChild of nestedTag.nestedChildren) {
if (nestedChild.parentTag === tag) {
return true
}
}
// Recursively check deeper nested children
if (isChildOfAnyFolder(nestedTag.nestedChildren, tag)) {
return true
}
}
}
return false
}
export const KeyboardNavigationHandler: React.FC<KeyboardNavigationHandlerProps> = ({
visible,
selectedIndex,
@@ -23,31 +114,59 @@ export const KeyboardNavigationHandler: React.FC<KeyboardNavigationHandlerProps>
nestedBlockTagGroups,
handleTagSelect,
}) => {
const { openFolder, closeFolder, isInFolder, currentFolder } = usePopoverContext()
const { openFolder, closeFolder, isInFolder, currentFolder, setKeyboardNav } = usePopoverContext()
const nestedNav = useNestedNavigation()
const visibleIndices = useMemo(() => {
const indices: number[] = []
const nestedPath = nestedNav?.nestedPath ?? []
if (isInFolder && currentFolder) {
for (const group of nestedBlockTagGroups) {
for (const nestedTag of group.nestedTags) {
const folderId = `${group.blockId}-${nestedTag.key}`
if (folderId === currentFolder && nestedTag.children) {
// First, add the parent tag itself (so it's navigable as the first item)
if (nestedTag.parentTag) {
const parentIdx = flatTagList.findIndex((item) => item.tag === nestedTag.parentTag)
if (parentIdx >= 0) {
indices.push(parentIdx)
}
// Determine the current folder to show based on nested navigation
let currentNestedTag: NestedTag | null = null
if (nestedPath.length > 0) {
// We're in nested navigation - use the deepest nested tag
currentNestedTag = nestedPath[nestedPath.length - 1]
} else {
// At base folder level - find the folder from currentFolder ID
for (const group of nestedBlockTagGroups) {
const folder = findFolderInNested(group.nestedTags, group.blockId, currentFolder)
if (folder) {
currentNestedTag = folder
break
}
}
}
if (currentNestedTag) {
// First, add the parent tag itself (so it's navigable as the first item)
if (currentNestedTag.parentTag) {
const parentIdx = flatTagList.findIndex(
(item) => item.tag === currentNestedTag!.parentTag
)
if (parentIdx >= 0) {
indices.push(parentIdx)
}
}
// Add all leaf children
if (currentNestedTag.children) {
for (const child of currentNestedTag.children) {
const idx = flatTagList.findIndex((item) => item.tag === child.fullTag)
if (idx >= 0) {
indices.push(idx)
}
// Then add all children
for (const child of nestedTag.children) {
const idx = flatTagList.findIndex((item) => item.tag === child.fullTag)
}
}
// Add nested children parent tags (for subfolder navigation)
if (currentNestedTag.nestedChildren) {
for (const nestedChild of currentNestedTag.nestedChildren) {
if (nestedChild.parentTag) {
const idx = flatTagList.findIndex((item) => item.tag === nestedChild.parentTag)
if (idx >= 0) {
indices.push(idx)
}
}
break
}
}
}
@@ -60,18 +179,10 @@ export const KeyboardNavigationHandler: React.FC<KeyboardNavigationHandlerProps>
// Check if this is a child of a parent folder
let isChild = false
for (const group of nestedBlockTagGroups) {
for (const nestedTag of group.nestedTags) {
if (nestedTag.children) {
for (const child of nestedTag.children) {
if (child.fullTag === tag) {
isChild = true
break
}
}
}
if (isChild) break
if (isChildOfAnyFolder(group.nestedTags, tag)) {
isChild = true
break
}
if (isChild) break
}
if (!isChild) {
@@ -81,16 +192,20 @@ export const KeyboardNavigationHandler: React.FC<KeyboardNavigationHandlerProps>
}
return indices
}, [isInFolder, currentFolder, flatTagList, nestedBlockTagGroups])
}, [isInFolder, currentFolder, flatTagList, nestedBlockTagGroups, nestedNav])
// Auto-select first visible item when entering/exiting folders
// Track nested path length for dependency
const nestedPathLength = nestedNav?.nestedPath.length ?? 0
// Auto-select first visible item when entering/exiting folders or navigating nested
// This effect only runs when folder navigation state changes, not on every render
useEffect(() => {
if (!visible || visibleIndices.length === 0) return
if (!visibleIndices.includes(selectedIndex)) {
setSelectedIndex(visibleIndices[0])
}
}, [visible, isInFolder, currentFolder, visibleIndices, selectedIndex, setSelectedIndex])
// Select first visible item when entering a folder or navigating nested
setSelectedIndex(visibleIndices[0])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible, isInFolder, currentFolder, nestedPathLength])
useEffect(() => {
if (!visible || !flatTagList.length) return
@@ -117,27 +232,18 @@ export const KeyboardNavigationHandler: React.FC<KeyboardNavigationHandlerProps>
id: string
title: string
parentTag: string
group: BlockTagGroup
group: NestedBlockTagGroup
nestedTag: NestedTag
} | null = null
if (selected) {
// Find if selected tag can be expanded (is a folder)
for (const group of nestedBlockTagGroups) {
for (const nestedTag of group.nestedTags) {
if (
nestedTag.parentTag === selected.tag &&
nestedTag.children &&
nestedTag.children.length > 0
) {
currentFolderInfo = {
id: `${selected.group?.blockId}-${nestedTag.key}`,
title: nestedTag.display,
parentTag: nestedTag.parentTag,
group,
}
break
}
const folderInfo = findFolderInfoForTag(group.nestedTags, selected.tag, group)
if (folderInfo) {
currentFolderInfo = folderInfo
break
}
if (currentFolderInfo) break
}
}
@@ -145,6 +251,7 @@ export const KeyboardNavigationHandler: React.FC<KeyboardNavigationHandlerProps>
case 'ArrowDown':
e.preventDefault()
e.stopPropagation()
setKeyboardNav(true)
if (visibleIndices.length > 0) {
const currentVisibleIndex = visibleIndices.indexOf(selectedIndex)
if (currentVisibleIndex === -1) {
@@ -157,6 +264,7 @@ export const KeyboardNavigationHandler: React.FC<KeyboardNavigationHandlerProps>
case 'ArrowUp':
e.preventDefault()
e.stopPropagation()
setKeyboardNav(true)
if (visibleIndices.length > 0) {
const currentVisibleIndex = visibleIndices.indexOf(selectedIndex)
if (currentVisibleIndex === -1) {
@@ -170,36 +278,40 @@ export const KeyboardNavigationHandler: React.FC<KeyboardNavigationHandlerProps>
e.preventDefault()
e.stopPropagation()
if (selected && selectedIndex >= 0 && selectedIndex < flatTagList.length) {
if (currentFolderInfo && !isInFolder) {
// It's a folder, open it
// Always select the tag, even for folders
// Use Arrow Right to navigate into folders
handleTagSelect(selected.tag, selected.group)
}
break
case 'ArrowRight':
if (currentFolderInfo) {
e.preventDefault()
e.stopPropagation()
if (isInFolder && nestedNav) {
// Already in a folder - use nested navigation to go deeper
nestedNav.navigateIn(currentFolderInfo.nestedTag, currentFolderInfo.group)
} else {
// At root level - open the folder normally
openFolderWithSelection(
currentFolderInfo.id,
currentFolderInfo.title,
currentFolderInfo.parentTag,
currentFolderInfo.group
)
} else {
// Not a folder, select it
handleTagSelect(selected.tag, selected.group)
}
}
break
case 'ArrowRight':
if (currentFolderInfo && !isInFolder) {
e.preventDefault()
e.stopPropagation()
openFolderWithSelection(
currentFolderInfo.id,
currentFolderInfo.title,
currentFolderInfo.parentTag,
currentFolderInfo.group
)
}
break
case 'ArrowLeft':
if (isInFolder) {
e.preventDefault()
e.stopPropagation()
// Try to navigate back in nested path first
if (nestedNav?.navigateBack()) {
// Successfully navigated back one level in nested navigation
// Selection will be handled by the auto-select effect
return
}
// At root folder level, close the folder entirely
closeFolder()
let firstRootIndex = 0
for (let i = 0; i < flatTagList.length; i++) {
@@ -239,6 +351,8 @@ export const KeyboardNavigationHandler: React.FC<KeyboardNavigationHandlerProps>
isInFolder,
setSelectedIndex,
handleTagSelect,
nestedNav,
setKeyboardNav,
])
return null

View File

@@ -10,14 +10,27 @@ export interface BlockTagGroup {
}
/**
* Nested tag structure for hierarchical display
* Child tag within a nested structure
*/
export interface NestedTagChild {
key: string
display: string
fullTag: string
}
/**
* Nested tag structure for hierarchical display.
* Supports recursive nesting for deeply nested object structures.
*/
export interface NestedTag {
key: string
display: string
fullTag?: string
parentTag?: string // Tag for the parent object when it has children
children?: Array<{ key: string; display: string; fullTag: string }>
parentTag?: string
/** Leaf children (no further nesting) */
children?: NestedTagChild[]
/** Recursively nested folders */
nestedChildren?: NestedTag[]
}
/**

View File

@@ -1,6 +1,6 @@
'use client'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Check } from 'lucide-react'
import {
Button,
@@ -11,10 +11,113 @@ import {
PopoverDivider,
PopoverFolder,
PopoverItem,
usePopoverContext,
} from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { WORKFLOW_COLORS } from '@/lib/workflows/colors'
const GRID_COLUMNS = 6
/**
* Color grid with keyboard navigation support.
* Uses roving tabindex pattern for accessibility.
*/
function ColorGrid({
hexInput,
setHexInput,
}: {
hexInput: string
setHexInput: (color: string) => void
}) {
const { isInFolder } = usePopoverContext()
const [focusedIndex, setFocusedIndex] = useState(-1)
const gridRef = useRef<HTMLDivElement>(null)
const buttonRefs = useRef<(HTMLButtonElement | null)[]>([])
// Focus the grid when entering the folder
useEffect(() => {
if (isInFolder && gridRef.current) {
// Find the currently selected color or default to first
const selectedIndex = WORKFLOW_COLORS.findIndex(
({ color }) => color.toLowerCase() === hexInput.toLowerCase()
)
const initialIndex = selectedIndex >= 0 ? selectedIndex : 0
setFocusedIndex(initialIndex)
// Small delay to let the folder animation complete
setTimeout(() => {
buttonRefs.current[initialIndex]?.focus()
}, 50)
}
}, [isInFolder, hexInput])
const handleKeyDown = useCallback(
(e: React.KeyboardEvent, index: number) => {
const totalItems = WORKFLOW_COLORS.length
let newIndex = index
switch (e.key) {
case 'ArrowRight':
e.preventDefault()
newIndex = index + 1 < totalItems ? index + 1 : index
break
case 'ArrowLeft':
e.preventDefault()
newIndex = index - 1 >= 0 ? index - 1 : index
break
case 'ArrowDown':
e.preventDefault()
newIndex = index + GRID_COLUMNS < totalItems ? index + GRID_COLUMNS : index
break
case 'ArrowUp':
e.preventDefault()
newIndex = index - GRID_COLUMNS >= 0 ? index - GRID_COLUMNS : index
break
case 'Enter':
case ' ':
e.preventDefault()
setHexInput(WORKFLOW_COLORS[index].color)
return
default:
return
}
if (newIndex !== index) {
setFocusedIndex(newIndex)
buttonRefs.current[newIndex]?.focus()
}
},
[setHexInput]
)
return (
<div ref={gridRef} className='grid grid-cols-6 gap-[4px]' role='grid'>
{WORKFLOW_COLORS.map(({ color, name }, index) => (
<button
key={color}
ref={(el) => {
buttonRefs.current[index] = el
}}
type='button'
role='gridcell'
title={name}
tabIndex={focusedIndex === index ? 0 : -1}
onClick={(e) => {
e.stopPropagation()
setHexInput(color)
}}
onKeyDown={(e) => handleKeyDown(e, index)}
onFocus={() => setFocusedIndex(index)}
className={cn(
'h-[20px] w-[20px] rounded-[4px] focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-1 focus:ring-offset-[#1b1b1b]',
hexInput.toLowerCase() === color.toLowerCase() && 'ring-1 ring-white'
)}
style={{ backgroundColor: color }}
/>
))}
</div>
)
}
/**
* Validates a hex color string.
* Accepts 3 or 6 character hex codes with or without #.
@@ -349,25 +452,8 @@ export function ContextMenu({
className={disableColorChange ? 'pointer-events-none opacity-50' : ''}
>
<div className='flex w-[140px] flex-col gap-[8px] p-[2px]'>
{/* Preset colors */}
<div className='grid grid-cols-6 gap-[4px]'>
{WORKFLOW_COLORS.map(({ color, name }) => (
<button
key={color}
type='button'
title={name}
onClick={(e) => {
e.stopPropagation()
setHexInput(color)
}}
className={cn(
'h-[20px] w-[20px] rounded-[4px]',
hexInput.toLowerCase() === color.toLowerCase() && 'ring-1 ring-white'
)}
style={{ backgroundColor: color }}
/>
))}
</div>
{/* Preset colors with keyboard navigation */}
<ColorGrid hexInput={hexInput} setHexInput={setHexInput} />
{/* Hex input */}
<div className='flex items-center gap-[4px]'>

View File

@@ -39,19 +39,40 @@ export const GoogleFormsBlock: BlockConfig = {
requiredScopes: [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/forms.body',
'https://www.googleapis.com/auth/forms.responses.readonly',
],
placeholder: 'Select Google account',
},
// Form ID - required for most operations except create_form
// Form selector (basic mode)
{
id: 'formId',
title: 'Select Form',
type: 'file-selector',
canonicalParamId: 'formId',
serviceId: 'google-forms',
requiredScopes: [],
mimeType: 'application/vnd.google-apps.form',
placeholder: 'Select a form',
dependsOn: ['credential'],
mode: 'basic',
condition: {
field: 'operation',
value: 'create_form',
not: true,
},
},
// Manual form ID input (advanced mode)
{
id: 'manualFormId',
title: 'Form ID',
type: 'short-input',
canonicalParamId: 'formId',
required: true,
placeholder: 'Enter the Google Form ID',
dependsOn: ['credential'],
mode: 'advanced',
condition: {
field: 'operation',
value: 'create_form',
@@ -214,6 +235,7 @@ Example for "Add a required multiple choice question about favorite color":
credential,
operation,
formId,
manualFormId,
responseId,
pageSize,
title,
@@ -230,7 +252,7 @@ Example for "Add a required multiple choice question about favorite color":
} = params
const baseParams = { ...rest, credential }
const effectiveFormId = formId ? String(formId).trim() : undefined
const effectiveFormId = (formId || manualFormId || '').toString().trim() || undefined
switch (operation) {
case 'get_responses':
@@ -299,7 +321,8 @@ Example for "Add a required multiple choice question about favorite color":
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
credential: { type: 'string', description: 'Google OAuth credential' },
formId: { type: 'string', description: 'Google Form ID' },
formId: { type: 'string', description: 'Google Form ID (from selector)' },
manualFormId: { type: 'string', description: 'Google Form ID (manual entry)' },
responseId: { type: 'string', description: 'Specific response ID' },
pageSize: { type: 'string', description: 'Max responses to retrieve' },
title: { type: 'string', description: 'Form title for creation' },
@@ -314,13 +337,132 @@ Example for "Add a required multiple choice question about favorite color":
watchId: { type: 'string', description: 'Watch ID' },
},
outputs: {
response: { type: 'json', description: 'Operation response data' },
formId: { type: 'string', description: 'Form ID' },
title: { type: 'string', description: 'Form title' },
responderUri: { type: 'string', description: 'Form responder URL' },
items: { type: 'json', description: 'Form items' },
responses: { type: 'json', description: 'Form responses' },
watches: { type: 'json', description: 'Form watches' },
responses: {
type: 'json',
description: 'Array of form responses',
condition: {
field: 'operation',
value: 'get_responses',
and: { field: 'responseId', value: ['', undefined, null] },
},
},
response: {
type: 'json',
description: 'Single form response',
condition: {
field: 'operation',
value: 'get_responses',
and: { field: 'responseId', value: ['', undefined, null], not: true },
},
},
// Get Form outputs
formId: {
type: 'string',
description: 'Form ID',
condition: { field: 'operation', value: ['get_form', 'create_form', 'set_publish_settings'] },
},
title: {
type: 'string',
description: 'Form title',
condition: { field: 'operation', value: ['get_form', 'create_form'] },
},
description: {
type: 'string',
description: 'Form description',
condition: { field: 'operation', value: 'get_form' },
},
documentTitle: {
type: 'string',
description: 'Document title in Drive',
condition: { field: 'operation', value: ['get_form', 'create_form'] },
},
responderUri: {
type: 'string',
description: 'Form responder URL',
condition: { field: 'operation', value: ['get_form', 'create_form'] },
},
linkedSheetId: {
type: 'string',
description: 'Linked Google Sheet ID',
condition: { field: 'operation', value: 'get_form' },
},
revisionId: {
type: 'string',
description: 'Form revision ID',
condition: { field: 'operation', value: ['get_form', 'create_form'] },
},
items: {
type: 'json',
description: 'Form items (questions, sections, etc.)',
condition: { field: 'operation', value: 'get_form' },
},
settings: {
type: 'json',
description: 'Form settings',
condition: { field: 'operation', value: 'get_form' },
},
publishSettings: {
type: 'json',
description: 'Form publish settings',
condition: { field: 'operation', value: ['get_form', 'set_publish_settings'] },
},
// Batch Update outputs
replies: {
type: 'json',
description: 'Replies from each update request',
condition: { field: 'operation', value: 'batch_update' },
},
writeControl: {
type: 'json',
description: 'Write control with revision IDs',
condition: { field: 'operation', value: 'batch_update' },
},
form: {
type: 'json',
description: 'Updated form (if includeFormInResponse is true)',
condition: { field: 'operation', value: 'batch_update' },
},
// Watch outputs
watches: {
type: 'json',
description: 'Array of form watches',
condition: { field: 'operation', value: 'list_watches' },
},
id: {
type: 'string',
description: 'Watch ID',
condition: { field: 'operation', value: ['create_watch', 'renew_watch'] },
},
eventType: {
type: 'string',
description: 'Watch event type',
condition: { field: 'operation', value: ['create_watch', 'renew_watch'] },
},
topicName: {
type: 'string',
description: 'Cloud Pub/Sub topic',
condition: { field: 'operation', value: 'create_watch' },
},
createTime: {
type: 'string',
description: 'Watch creation time',
condition: { field: 'operation', value: 'create_watch' },
},
expireTime: {
type: 'string',
description: 'Watch expiration time',
condition: { field: 'operation', value: ['create_watch', 'renew_watch'] },
},
state: {
type: 'string',
description: 'Watch state (ACTIVE, SUSPENDED)',
condition: { field: 'operation', value: ['create_watch', 'renew_watch'] },
},
deleted: {
type: 'boolean',
description: 'Whether the watch was deleted',
condition: { field: 'operation', value: 'delete_watch' },
},
},
triggers: {
enabled: true,

View File

@@ -135,7 +135,13 @@ export interface OutputCondition {
not?: boolean
and?: {
field: string
value: string | number | boolean | Array<string | number | boolean> | undefined
value:
| string
| number
| boolean
| Array<string | number | boolean | undefined | null>
| undefined
| null
not?: boolean
}
}

View File

@@ -170,6 +170,18 @@ interface PopoverContextValue {
/** ID of the last hovered item (for hover submenus) */
lastHoveredItem: string | null
setLastHoveredItem: (id: string | null) => void
/** Whether keyboard navigation is active. When true, hover styles are suppressed. */
isKeyboardNav: boolean
setKeyboardNav: (value: boolean) => void
/** Currently selected item index for keyboard navigation */
selectedIndex: number
setSelectedIndex: (index: number) => void
/** Register a menu item and get its index. Returns a cleanup function. */
registerItem: (id: string) => number
/** Unregister a menu item */
unregisterItem: (id: string) => void
/** Get the total number of registered items */
itemCount: number
}
const PopoverContext = React.createContext<PopoverContextValue | null>(null)
@@ -220,6 +232,23 @@ const Popover: React.FC<PopoverProps> = ({
const [onFolderSelect, setOnFolderSelect] = React.useState<(() => void) | null>(null)
const [searchQuery, setSearchQuery] = React.useState<string>('')
const [lastHoveredItem, setLastHoveredItem] = React.useState<string | null>(null)
const [isKeyboardNav, setIsKeyboardNav] = React.useState(false)
const [selectedIndex, setSelectedIndex] = React.useState(-1)
const registeredItemsRef = React.useRef<string[]>([])
const registerItem = React.useCallback((id: string) => {
if (!registeredItemsRef.current.includes(id)) {
registeredItemsRef.current.push(id)
}
return registeredItemsRef.current.indexOf(id)
}, [])
const unregisterItem = React.useCallback((id: string) => {
const index = registeredItemsRef.current.indexOf(id)
if (index !== -1) {
registeredItemsRef.current.splice(index, 1)
}
}, [])
React.useEffect(() => {
if (open === false) {
@@ -228,6 +257,9 @@ const Popover: React.FC<PopoverProps> = ({
setOnFolderSelect(null)
setSearchQuery('')
setLastHoveredItem(null)
setIsKeyboardNav(false)
setSelectedIndex(-1)
registeredItemsRef.current = []
}
}, [open])
@@ -249,6 +281,12 @@ const Popover: React.FC<PopoverProps> = ({
setOnFolderSelect(null)
}, [])
const setKeyboardNav = React.useCallback((value: boolean) => {
setIsKeyboardNav(value)
}, [])
const itemCount = registeredItemsRef.current.length
const contextValue = React.useMemo<PopoverContextValue>(
() => ({
openFolder,
@@ -264,6 +302,13 @@ const Popover: React.FC<PopoverProps> = ({
setSearchQuery,
lastHoveredItem,
setLastHoveredItem,
isKeyboardNav,
setKeyboardNav,
selectedIndex,
setSelectedIndex,
registerItem,
unregisterItem,
itemCount,
}),
[
openFolder,
@@ -276,6 +321,12 @@ const Popover: React.FC<PopoverProps> = ({
colorScheme,
searchQuery,
lastHoveredItem,
isKeyboardNav,
setKeyboardNav,
selectedIndex,
registerItem,
unregisterItem,
itemCount,
]
)
@@ -382,6 +433,101 @@ const PopoverContent = React.forwardRef<
const effectiveSideOffset = sideOffset ?? (side === 'top' ? 20 : 14)
// Switch to mouse mode when mouse moves
const handleMouseMove = React.useCallback(() => {
if (context?.isKeyboardNav) {
context.setKeyboardNav(false)
}
}, [context])
// Track menu items for keyboard navigation
const contentRef = React.useRef<HTMLDivElement>(null)
// Merge refs
const mergedRef = React.useCallback(
(node: HTMLDivElement | null) => {
contentRef.current = node
if (typeof ref === 'function') {
ref(node)
} else if (ref) {
ref.current = node
}
},
[ref]
)
// Keyboard navigation handler - use window listener to ensure events are captured
React.useEffect(() => {
if (!context) return
const handleKeyDown = (e: KeyboardEvent) => {
// Get content element inside handler to ensure it's current
const content = contentRef.current
if (!content) return
const items = content.querySelectorAll<HTMLElement>(
'[role="menuitem"]:not([aria-disabled="true"])'
)
if (items.length === 0) return
const currentIndex = context.selectedIndex
switch (e.key) {
case 'ArrowDown':
e.preventDefault()
e.stopPropagation()
context.setKeyboardNav(true)
// If no selection, start at first item; otherwise move down
if (currentIndex < 0) {
context.setSelectedIndex(0)
} else {
context.setSelectedIndex(currentIndex < items.length - 1 ? currentIndex + 1 : 0)
}
break
case 'ArrowUp':
e.preventDefault()
e.stopPropagation()
context.setKeyboardNav(true)
// If no selection, start at last item; otherwise move up
if (currentIndex < 0) {
context.setSelectedIndex(items.length - 1)
} else {
context.setSelectedIndex(currentIndex > 0 ? currentIndex - 1 : items.length - 1)
}
break
case 'Enter':
case ' ':
if (currentIndex >= 0) {
e.preventDefault()
e.stopPropagation()
const selectedItem = items[currentIndex]
if (selectedItem) {
selectedItem.click()
}
}
break
}
}
// Use capture phase to ensure we get the event
window.addEventListener('keydown', handleKeyDown, true)
return () => window.removeEventListener('keydown', handleKeyDown, true)
}, [context])
// Scroll selected item into view
React.useEffect(() => {
const content = contentRef.current
if (!content || !context?.isKeyboardNav || context.selectedIndex < 0) return
const items = content.querySelectorAll<HTMLElement>(
'[role="menuitem"]:not([aria-disabled="true"])'
)
const selectedItem = items[context.selectedIndex]
if (selectedItem) {
selectedItem.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
}
}, [context?.selectedIndex, context?.isKeyboardNav])
const hasUserWidthConstraint =
maxWidth !== undefined ||
minWidth !== undefined ||
@@ -425,7 +571,7 @@ const PopoverContent = React.forwardRef<
const content = (
<PopoverPrimitive.Content
ref={ref}
ref={mergedRef}
side={side}
align={align}
sideOffset={effectiveSideOffset}
@@ -434,6 +580,7 @@ const PopoverContent = React.forwardRef<
sticky='partial'
hideWhenDetached={false}
onWheel={handleWheel}
onMouseMove={handleMouseMove}
onOpenAutoFocus={handleOpenAutoFocus}
onCloseAutoFocus={handleCloseAutoFocus}
{...restProps}
@@ -534,6 +681,31 @@ const PopoverItem = React.forwardRef<HTMLDivElement, PopoverItemProps>(
const variant = context?.variant || 'default'
const size = context?.size || 'md'
const colorScheme = context?.colorScheme || 'default'
const itemRef = React.useRef<HTMLDivElement>(null)
const [itemIndex, setItemIndex] = React.useState(-1)
// Merge refs
const mergedRef = React.useCallback(
(node: HTMLDivElement | null) => {
itemRef.current = node
if (typeof ref === 'function') {
ref(node)
} else if (ref) {
ref.current = node
}
},
[ref]
)
// Calculate item index on mount and when siblings change
React.useEffect(() => {
if (!itemRef.current) return
const content = itemRef.current.closest('[data-radix-popper-content-wrapper]')
if (!content) return
const items = content.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])')
const index = Array.from(items).indexOf(itemRef.current)
setItemIndex(index)
}, [])
if (rootOnly && context?.isInFolder) return null
@@ -548,22 +720,36 @@ const PopoverItem = React.forwardRef<HTMLDivElement, PopoverItemProps>(
const handleMouseEnter = (e: React.MouseEvent<HTMLDivElement>) => {
// Clear last hovered item to close any open hover submenus
context?.setLastHoveredItem(null)
// Update selected index to this item's position
if (itemIndex >= 0 && context) {
context.setSelectedIndex(itemIndex)
}
onMouseEnter?.(e)
}
// Determine if this item is active:
// - Use explicit `active` prop if provided
// - Otherwise use context selectedIndex match
const isActive =
active !== undefined ? active : itemIndex >= 0 && context?.selectedIndex === itemIndex
// Suppress hover when in keyboard mode to prevent dual highlights
const suppressHover = context?.isKeyboardNav && !isActive
return (
<div
ref={mergedRef}
className={cn(
STYLES.itemBase,
STYLES.colorScheme[colorScheme].text,
STYLES.size[size].item,
getItemStateClasses(variant, colorScheme, !!active),
getItemStateClasses(variant, colorScheme, !!isActive),
suppressHover && 'hover:!bg-transparent',
disabled && 'pointer-events-none cursor-not-allowed opacity-50',
className
)}
ref={ref}
role='menuitem'
aria-selected={active}
aria-selected={isActive}
aria-disabled={disabled}
onClick={handleClick}
onMouseEnter={handleMouseEnter}
@@ -666,12 +852,16 @@ const PopoverFolder = React.forwardRef<HTMLDivElement, PopoverFolderProps>(
colorScheme,
lastHoveredItem,
setLastHoveredItem,
isKeyboardNav,
selectedIndex,
setSelectedIndex,
} = usePopoverContext()
const [submenuPosition, setSubmenuPosition] = React.useState<{ top: number; left: number }>({
top: 0,
left: 0,
})
const triggerRef = React.useRef<HTMLDivElement>(null)
const [itemIndex, setItemIndex] = React.useState(-1)
// Submenu is open when this folder is the last hovered item (for expandOnHover mode)
const isHoverOpen = expandOnHover && lastHoveredItem === id
@@ -689,6 +879,16 @@ const PopoverFolder = React.forwardRef<HTMLDivElement, PopoverFolderProps>(
[ref]
)
// Calculate item index on mount
React.useEffect(() => {
if (!triggerRef.current) return
const content = triggerRef.current.closest('[data-radix-popper-content-wrapper]')
if (!content) return
const items = content.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])')
const index = Array.from(items).indexOf(triggerRef.current)
setItemIndex(index)
}, [])
// If we're in a folder and this isn't the current one, hide
if (isInFolder && currentFolder !== id) return null
// If this folder is open via click (inline mode), render children directly
@@ -708,6 +908,11 @@ const PopoverFolder = React.forwardRef<HTMLDivElement, PopoverFolderProps>(
}
const handleMouseEnter = () => {
// Update selected index for keyboard navigation
if (itemIndex >= 0) {
setSelectedIndex(itemIndex)
}
if (!expandOnHover) return
// Calculate position for submenu
@@ -727,6 +932,12 @@ const PopoverFolder = React.forwardRef<HTMLDivElement, PopoverFolderProps>(
onOpen?.()
}
// Determine if this folder is active (for keyboard navigation highlight)
const isActive = active !== undefined ? active : itemIndex >= 0 && selectedIndex === itemIndex
// Suppress hover when in keyboard mode to prevent dual highlights
const suppressHover = isKeyboardNav && !isActive && !isHoverOpen
return (
<>
<div
@@ -735,12 +946,14 @@ const PopoverFolder = React.forwardRef<HTMLDivElement, PopoverFolderProps>(
STYLES.itemBase,
STYLES.colorScheme[colorScheme].text,
STYLES.size[size].item,
getItemStateClasses(variant, colorScheme, !!active || isHoverOpen),
getItemStateClasses(variant, colorScheme, isActive || isHoverOpen),
suppressHover && 'hover:!bg-transparent',
className
)}
role='menuitem'
aria-haspopup='true'
aria-expanded={isHoverOpen}
aria-selected={isActive}
onClick={handleClick}
onMouseEnter={handleMouseEnter}
{...props}

View File

@@ -110,6 +110,8 @@ function resolveFileSelector(
return { key: 'google.drive', context, allowSearch: true }
case 'google-slides':
return { key: 'google.drive', context, allowSearch: true }
case 'google-forms':
return { key: 'google.drive', context, allowSearch: true }
case 'onedrive': {
const key: SelectorKey = subBlock.mimeType === 'file' ? 'onedrive.files' : 'onedrive.folders'
return { key, context, allowSearch: true }

View File

@@ -884,6 +884,8 @@ export const auth = betterAuth({
scopes: [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/forms.body',
'https://www.googleapis.com/auth/forms.responses.readonly',
],
prompt: 'consent',

View File

@@ -96,13 +96,15 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
},
'google-forms': {
name: 'Google Forms',
description: 'Retrieve Google Form responses.',
description: 'Create, modify, and read Google Forms.',
providerId: 'google-forms',
icon: GoogleFormsIcon,
baseProviderIcon: GoogleIcon,
scopes: [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/forms.body',
'https://www.googleapis.com/auth/forms.responses.readonly',
],
},

View File

@@ -62,8 +62,11 @@ function evaluateOutputCondition(
let andMatches: boolean
if (Array.isArray(condition.and.value)) {
andMatches =
const primitiveMatch =
isConditionPrimitive(andFieldValue) && condition.and.value.includes(andFieldValue)
const undefinedMatch = andFieldValue === undefined && condition.and.value.includes(undefined)
const nullMatch = andFieldValue === null && condition.and.value.includes(null)
andMatches = primitiveMatch || undefinedMatch || nullMatch
} else {
andMatches = andFieldValue === condition.and.value
}
@@ -95,7 +98,9 @@ function filterOutputsByCondition(
}
const condition = value.condition as OutputCondition | undefined
if (!condition || evaluateOutputCondition(condition, subBlocks)) {
const passes = !condition || evaluateOutputCondition(condition, subBlocks)
if (passes) {
const { condition: _, ...rest } = value
filtered[key] = rest
}
@@ -565,11 +570,32 @@ export function getToolOutputs(blockConfig: BlockConfig, operation: string): Rec
*
* @param blockConfig - The block configuration containing tools config
* @param operation - The selected operation for the tool
* @param subBlocks - Optional subBlock values for condition evaluation
* @returns Array of output paths for the tool, or empty array on error
*/
export function getToolOutputPaths(blockConfig: BlockConfig, operation: string): string[] {
export function getToolOutputPaths(
blockConfig: BlockConfig,
operation: string,
subBlocks?: Record<string, SubBlockWithValue>
): string[] {
const outputs = getToolOutputs(blockConfig, operation)
if (!outputs || Object.keys(outputs).length === 0) return []
if (subBlocks && blockConfig.outputs) {
const filteredBlockOutputs = filterOutputsByCondition(blockConfig.outputs, subBlocks)
const allowedKeys = new Set(Object.keys(filteredBlockOutputs))
const filteredOutputs: Record<string, any> = {}
for (const [key, value] of Object.entries(outputs)) {
if (allowedKeys.has(key)) {
filteredOutputs[key] = value
}
}
return generateOutputPaths(filteredOutputs)
}
return generateOutputPaths(outputs)
}

View File

@@ -241,16 +241,94 @@ export const compareCommitsV2Tool: ToolConfig<CompareCommitsParams, any> = {
},
outputs: {
status: { type: 'string', description: 'Comparison status' },
ahead_by: { type: 'number', description: 'Commits ahead' },
behind_by: { type: 'number', description: 'Commits behind' },
total_commits: { type: 'number', description: 'Total commits' },
html_url: { type: 'string', description: 'Web URL' },
diff_url: { type: 'string', description: 'Diff URL' },
patch_url: { type: 'string', description: 'Patch URL' },
base_commit: { type: 'object', description: 'Base commit' },
merge_base_commit: { type: 'object', description: 'Merge base' },
commits: { type: 'array', description: 'Commits between' },
files: { type: 'array', description: 'Changed files' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'GitHub web URL' },
permalink_url: { type: 'string', description: 'Permanent link URL' },
diff_url: { type: 'string', description: 'Diff download URL' },
patch_url: { type: 'string', description: 'Patch download URL' },
status: {
type: 'string',
description: 'Comparison status (ahead, behind, identical, diverged)',
},
ahead_by: { type: 'number', description: 'Commits head is ahead of base' },
behind_by: { type: 'number', description: 'Commits head is behind base' },
total_commits: { type: 'number', description: 'Total commits in comparison' },
base_commit: {
type: 'object',
description: 'Base commit object',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
html_url: { type: 'string', description: 'Web URL' },
commit: {
type: 'object',
description: 'Commit data',
properties: {
message: { type: 'string', description: 'Commit message' },
author: { type: 'object', description: 'Git author (name, email, date)' },
committer: { type: 'object', description: 'Git committer (name, email, date)' },
},
},
author: { type: 'object', description: 'GitHub user (author)', optional: true },
committer: { type: 'object', description: 'GitHub user (committer)', optional: true },
},
},
merge_base_commit: {
type: 'object',
description: 'Merge base commit object',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
html_url: { type: 'string', description: 'Web URL' },
},
},
commits: {
type: 'array',
description: 'Commits between base and head',
items: {
type: 'object',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
html_url: { type: 'string', description: 'Web URL' },
commit: {
type: 'object',
description: 'Commit data',
properties: {
message: { type: 'string', description: 'Commit message' },
author: { type: 'object', description: 'Git author (name, email, date)' },
committer: { type: 'object', description: 'Git committer (name, email, date)' },
},
},
author: { type: 'object', description: 'GitHub user', optional: true },
committer: { type: 'object', description: 'GitHub user', optional: true },
},
},
},
files: {
type: 'array',
description: 'Changed files (diff entries)',
items: {
type: 'object',
properties: {
sha: { type: 'string', description: 'Blob SHA', optional: true },
filename: { type: 'string', description: 'File path' },
status: {
type: 'string',
description:
'Change status (added, removed, modified, renamed, copied, changed, unchanged)',
},
additions: { type: 'number', description: 'Lines added' },
deletions: { type: 'number', description: 'Lines deleted' },
changes: { type: 'number', description: 'Total changes' },
blob_url: { type: 'string', description: 'Blob URL' },
raw_url: { type: 'string', description: 'Raw file URL' },
contents_url: { type: 'string', description: 'Contents API URL' },
patch: { type: 'string', description: 'Diff patch', optional: true },
previous_filename: {
type: 'string',
description: 'Previous filename (for renames)',
optional: true,
},
},
},
},
},
}

View File

@@ -131,8 +131,26 @@ export const createCommentReactionV2Tool: ToolConfig<CreateCommentReactionParams
outputs: {
id: { type: 'number', description: 'Reaction ID' },
user: { type: 'object', description: 'User who reacted' },
content: { type: 'string', description: 'Reaction type' },
created_at: { type: 'string', description: 'Creation date' },
node_id: { type: 'string', description: 'GraphQL node ID' },
content: {
type: 'string',
description: 'Reaction type (+1, -1, laugh, confused, heart, hooray, rocket, eyes)',
},
created_at: { type: 'string', description: 'Creation timestamp' },
user: {
type: 'object',
description: 'User who reacted',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
}

View File

@@ -170,14 +170,39 @@ export const createGistV2Tool: ToolConfig<CreateGistParams, any> = {
outputs: {
id: { type: 'string', description: 'Gist ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Web URL' },
forks_url: { type: 'string', description: 'Forks API URL' },
commits_url: { type: 'string', description: 'Commits API URL' },
git_pull_url: { type: 'string', description: 'Git pull URL' },
git_push_url: { type: 'string', description: 'Git push URL' },
description: { type: 'string', description: 'Description', optional: true },
public: { type: 'boolean', description: 'Is public' },
created_at: { type: 'string', description: 'Creation date' },
updated_at: { type: 'string', description: 'Update date' },
files: { type: 'object', description: 'Files in gist' },
owner: { type: 'object', description: 'Owner info' },
description: { type: 'string', description: 'Gist description', optional: true },
public: { type: 'boolean', description: 'Whether gist is public' },
truncated: { type: 'boolean', description: 'Whether files are truncated' },
comments: { type: 'number', description: 'Number of comments' },
comments_url: { type: 'string', description: 'Comments API URL' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
files: {
type: 'object',
description:
'Files in the gist (object with filenames as keys, each containing filename, type, language, raw_url, size, truncated, content)',
},
owner: {
type: 'object',
description: 'Gist owner',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
}

View File

@@ -131,8 +131,26 @@ export const createIssueReactionV2Tool: ToolConfig<CreateIssueReactionParams, an
outputs: {
id: { type: 'number', description: 'Reaction ID' },
user: { type: 'object', description: 'User who reacted' },
content: { type: 'string', description: 'Reaction type' },
created_at: { type: 'string', description: 'Creation date' },
node_id: { type: 'string', description: 'GraphQL node ID' },
content: {
type: 'string',
description: 'Reaction type (+1, -1, laugh, confused, heart, hooray, rocket, eyes)',
},
created_at: { type: 'string', description: 'Creation timestamp' },
user: {
type: 'object',
description: 'User who reacted',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
}

View File

@@ -167,13 +167,63 @@ export const forkRepoV2Tool: ToolConfig<ForkRepoParams, any> = {
outputs: {
id: { type: 'number', description: 'Repository ID' },
full_name: { type: 'string', description: 'Full name' },
html_url: { type: 'string', description: 'Web URL' },
clone_url: { type: 'string', description: 'Clone URL' },
ssh_url: { type: 'string', description: 'SSH URL' },
default_branch: { type: 'string', description: 'Default branch' },
fork: { type: 'boolean', description: 'Is a fork' },
parent: { type: 'object', description: 'Parent repository', optional: true },
owner: { type: 'object', description: 'Owner' },
node_id: { type: 'string', description: 'GraphQL node ID' },
name: { type: 'string', description: 'Repository name' },
full_name: { type: 'string', description: 'Full name (owner/repo)' },
private: { type: 'boolean', description: 'Whether repository is private' },
description: { type: 'string', description: 'Repository description', optional: true },
html_url: { type: 'string', description: 'GitHub web URL' },
url: { type: 'string', description: 'API URL' },
clone_url: { type: 'string', description: 'HTTPS clone URL' },
ssh_url: { type: 'string', description: 'SSH clone URL' },
git_url: { type: 'string', description: 'Git protocol URL' },
default_branch: { type: 'string', description: 'Default branch name' },
fork: { type: 'boolean', description: 'Whether this is a fork' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
pushed_at: { type: 'string', description: 'Last push timestamp', optional: true },
owner: {
type: 'object',
description: 'Fork owner',
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
parent: {
type: 'object',
description: 'Parent repository (source of the fork)',
optional: true,
properties: {
id: { type: 'number', description: 'Repository ID' },
full_name: { type: 'string', description: 'Full name' },
html_url: { type: 'string', description: 'Web URL' },
description: { type: 'string', description: 'Description', optional: true },
owner: {
type: 'object',
description: 'Parent owner',
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
},
},
},
},
source: {
type: 'object',
description: 'Source repository (ultimate origin)',
optional: true,
properties: {
id: { type: 'number', description: 'Repository ID' },
full_name: { type: 'string', description: 'Full name' },
html_url: { type: 'string', description: 'Web URL' },
},
},
},
}

View File

@@ -191,12 +191,128 @@ export const getCommitV2Tool: ToolConfig<GetCommitParams, any> = {
outputs: {
sha: { type: 'string', description: 'Commit SHA' },
html_url: { type: 'string', description: 'Web URL' },
commit: { type: 'object', description: 'Commit data' },
author: { type: 'object', description: 'GitHub user', optional: true },
committer: { type: 'object', description: 'GitHub user', optional: true },
stats: { type: 'object', description: 'Change stats', optional: true },
files: { type: 'array', description: 'Changed files' },
parents: { type: 'array', description: 'Parent commits' },
node_id: { type: 'string', description: 'GraphQL node ID' },
html_url: { type: 'string', description: 'GitHub web URL' },
url: { type: 'string', description: 'API URL' },
comments_url: { type: 'string', description: 'Comments API URL' },
commit: {
type: 'object',
description: 'Core commit data',
properties: {
url: { type: 'string', description: 'Commit API URL' },
message: { type: 'string', description: 'Commit message' },
comment_count: { type: 'number', description: 'Number of comments' },
author: {
type: 'object',
description: 'Git author',
properties: {
name: { type: 'string', description: 'Author name' },
email: { type: 'string', description: 'Author email' },
date: { type: 'string', description: 'Author date (ISO 8601)' },
},
},
committer: {
type: 'object',
description: 'Git committer',
properties: {
name: { type: 'string', description: 'Committer name' },
email: { type: 'string', description: 'Committer email' },
date: { type: 'string', description: 'Commit date (ISO 8601)' },
},
},
tree: {
type: 'object',
description: 'Tree object',
properties: {
sha: { type: 'string', description: 'Tree SHA' },
url: { type: 'string', description: 'Tree API URL' },
},
},
verification: {
type: 'object',
description: 'Signature verification',
properties: {
verified: { type: 'boolean', description: 'Whether signature is verified' },
reason: { type: 'string', description: 'Verification reason' },
signature: { type: 'string', description: 'GPG signature', optional: true },
payload: { type: 'string', description: 'Signed payload', optional: true },
},
},
},
},
author: {
type: 'object',
description: 'GitHub user (author)',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
avatar_url: { type: 'string', description: 'Avatar URL' },
html_url: { type: 'string', description: 'Profile URL' },
type: { type: 'string', description: 'User or Organization' },
},
},
committer: {
type: 'object',
description: 'GitHub user (committer)',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
avatar_url: { type: 'string', description: 'Avatar URL' },
html_url: { type: 'string', description: 'Profile URL' },
type: { type: 'string', description: 'User or Organization' },
},
},
stats: {
type: 'object',
description: 'Change statistics',
optional: true,
properties: {
additions: { type: 'number', description: 'Lines added' },
deletions: { type: 'number', description: 'Lines deleted' },
total: { type: 'number', description: 'Total changes' },
},
},
files: {
type: 'array',
description: 'Changed files (diff entries)',
items: {
type: 'object',
properties: {
sha: { type: 'string', description: 'Blob SHA', optional: true },
filename: { type: 'string', description: 'File path' },
status: {
type: 'string',
description:
'Change status (added, removed, modified, renamed, copied, changed, unchanged)',
},
additions: { type: 'number', description: 'Lines added' },
deletions: { type: 'number', description: 'Lines deleted' },
changes: { type: 'number', description: 'Total changes' },
blob_url: { type: 'string', description: 'Blob URL' },
raw_url: { type: 'string', description: 'Raw file URL' },
contents_url: { type: 'string', description: 'Contents API URL' },
patch: { type: 'string', description: 'Diff patch', optional: true },
previous_filename: {
type: 'string',
description: 'Previous filename (for renames)',
optional: true,
},
},
},
},
parents: {
type: 'array',
description: 'Parent commits',
items: {
type: 'object',
properties: {
sha: { type: 'string', description: 'Parent SHA' },
url: { type: 'string', description: 'Parent API URL' },
html_url: { type: 'string', description: 'Parent web URL' },
},
},
},
},
}

View File

@@ -165,13 +165,52 @@ export const getGistV2Tool: ToolConfig<GetGistParams, any> = {
outputs: {
id: { type: 'string', description: 'Gist ID' },
html_url: { type: 'string', description: 'Web URL' },
description: { type: 'string', description: 'Description', optional: true },
public: { type: 'boolean', description: 'Is public' },
created_at: { type: 'string', description: 'Creation date' },
updated_at: { type: 'string', description: 'Update date' },
files: { type: 'object', description: 'Files with content' },
owner: { type: 'object', description: 'Owner info' },
comments: { type: 'number', description: 'Comment count' },
node_id: { type: 'string', description: 'GraphQL node ID' },
html_url: { type: 'string', description: 'GitHub web URL' },
url: { type: 'string', description: 'API URL' },
forks_url: { type: 'string', description: 'Forks API URL' },
commits_url: { type: 'string', description: 'Commits API URL' },
git_pull_url: { type: 'string', description: 'Git clone URL' },
git_push_url: { type: 'string', description: 'Git push URL' },
description: { type: 'string', description: 'Gist description', optional: true },
public: { type: 'boolean', description: 'Whether gist is public' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
comments: { type: 'number', description: 'Number of comments' },
comments_url: { type: 'string', description: 'Comments API URL' },
truncated: { type: 'boolean', description: 'Whether content is truncated' },
files: {
type: 'object',
description: 'Files in the gist (keyed by filename)',
properties: {
'[filename]': {
type: 'object',
description: 'File object',
properties: {
filename: { type: 'string', description: 'File name' },
type: { type: 'string', description: 'MIME type' },
language: { type: 'string', description: 'Programming language', optional: true },
raw_url: { type: 'string', description: 'Raw file URL' },
size: { type: 'number', description: 'File size in bytes' },
truncated: { type: 'boolean', description: 'Whether content is truncated' },
content: { type: 'string', description: 'File content' },
},
},
},
},
owner: {
type: 'object',
description: 'Gist owner',
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
}

View File

@@ -153,15 +153,35 @@ export const getMilestoneV2Tool: ToolConfig<GetMilestoneParams, any> = {
},
outputs: {
id: { type: 'number', description: 'Milestone ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
number: { type: 'number', description: 'Milestone number' },
title: { type: 'string', description: 'Title' },
description: { type: 'string', description: 'Description', optional: true },
state: { type: 'string', description: 'State' },
html_url: { type: 'string', description: 'Web URL' },
due_on: { type: 'string', description: 'Due date', optional: true },
open_issues: { type: 'number', description: 'Open issues' },
closed_issues: { type: 'number', description: 'Closed issues' },
closed_at: { type: 'string', description: 'Close date', optional: true },
creator: { type: 'object', description: 'Creator' },
title: { type: 'string', description: 'Milestone title' },
description: { type: 'string', description: 'Milestone description', optional: true },
state: { type: 'string', description: 'State (open or closed)' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'GitHub web URL' },
labels_url: { type: 'string', description: 'Labels API URL' },
due_on: { type: 'string', description: 'Due date (ISO 8601)', optional: true },
open_issues: { type: 'number', description: 'Number of open issues' },
closed_issues: { type: 'number', description: 'Number of closed issues' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
closed_at: { type: 'string', description: 'Close timestamp', optional: true },
creator: {
type: 'object',
description: 'Milestone creator',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
}

View File

@@ -230,11 +230,97 @@ export const listCommitsV2Tool: ToolConfig<ListCommitsParams, any> = {
type: 'object',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
node_id: { type: 'string', description: 'GraphQL node ID' },
html_url: { type: 'string', description: 'Web URL' },
commit: { type: 'object', description: 'Commit data' },
author: { type: 'object', description: 'GitHub user', optional: true },
committer: { type: 'object', description: 'GitHub user', optional: true },
parents: { type: 'array', description: 'Parent commits' },
url: { type: 'string', description: 'API URL' },
comments_url: { type: 'string', description: 'Comments API URL' },
commit: {
type: 'object',
description: 'Core commit data',
properties: {
url: { type: 'string', description: 'Commit API URL' },
message: { type: 'string', description: 'Commit message' },
comment_count: { type: 'number', description: 'Number of comments' },
author: {
type: 'object',
description: 'Git author',
properties: {
name: { type: 'string', description: 'Author name' },
email: { type: 'string', description: 'Author email' },
date: { type: 'string', description: 'Author date (ISO 8601)' },
},
},
committer: {
type: 'object',
description: 'Git committer',
properties: {
name: { type: 'string', description: 'Committer name' },
email: { type: 'string', description: 'Committer email' },
date: { type: 'string', description: 'Commit date (ISO 8601)' },
},
},
tree: {
type: 'object',
description: 'Tree object',
properties: {
sha: { type: 'string', description: 'Tree SHA' },
url: { type: 'string', description: 'Tree API URL' },
},
},
verification: {
type: 'object',
description: 'Signature verification',
properties: {
verified: { type: 'boolean', description: 'Whether signature is verified' },
reason: { type: 'string', description: 'Verification reason' },
signature: { type: 'string', description: 'GPG signature', optional: true },
payload: { type: 'string', description: 'Signed payload', optional: true },
},
},
},
},
author: {
type: 'object',
description: 'GitHub user (author)',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
committer: {
type: 'object',
description: 'GitHub user (committer)',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
parents: {
type: 'array',
description: 'Parent commits',
items: {
type: 'object',
properties: {
sha: { type: 'string', description: 'Parent SHA' },
url: { type: 'string', description: 'Parent API URL' },
html_url: { type: 'string', description: 'Parent web URL' },
},
},
},
},
},
},

View File

@@ -188,11 +188,41 @@ export const listForksV2Tool: ToolConfig<ListForksParams, any> = {
type: 'object',
properties: {
id: { type: 'number', description: 'Repository ID' },
full_name: { type: 'string', description: 'Full name' },
html_url: { type: 'string', description: 'Web URL' },
owner: { type: 'object', description: 'Owner' },
stargazers_count: { type: 'number', description: 'Stars' },
forks_count: { type: 'number', description: 'Forks' },
node_id: { type: 'string', description: 'GraphQL node ID' },
name: { type: 'string', description: 'Repository name' },
full_name: { type: 'string', description: 'Full name (owner/repo)' },
private: { type: 'boolean', description: 'Whether repository is private' },
description: { type: 'string', description: 'Repository description', optional: true },
html_url: { type: 'string', description: 'GitHub web URL' },
url: { type: 'string', description: 'API URL' },
fork: { type: 'boolean', description: 'Whether this is a fork' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
pushed_at: { type: 'string', description: 'Last push timestamp', optional: true },
size: { type: 'number', description: 'Repository size in KB' },
stargazers_count: { type: 'number', description: 'Number of stars' },
watchers_count: { type: 'number', description: 'Number of watchers' },
forks_count: { type: 'number', description: 'Number of forks' },
open_issues_count: { type: 'number', description: 'Number of open issues' },
language: { type: 'string', description: 'Primary programming language', optional: true },
default_branch: { type: 'string', description: 'Default branch name' },
visibility: { type: 'string', description: 'Repository visibility' },
archived: { type: 'boolean', description: 'Whether repository is archived' },
disabled: { type: 'boolean', description: 'Whether repository is disabled' },
owner: {
type: 'object',
description: 'Fork owner',
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
},
},

View File

@@ -188,11 +188,40 @@ export const listGistsV2Tool: ToolConfig<ListGistsParams, any> = {
type: 'object',
properties: {
id: { type: 'string', description: 'Gist ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Web URL' },
description: { type: 'string', description: 'Description', optional: true },
public: { type: 'boolean', description: 'Is public' },
files: { type: 'object', description: 'Files' },
owner: { type: 'object', description: 'Owner' },
forks_url: { type: 'string', description: 'Forks API URL' },
commits_url: { type: 'string', description: 'Commits API URL' },
git_pull_url: { type: 'string', description: 'Git pull URL' },
git_push_url: { type: 'string', description: 'Git push URL' },
description: { type: 'string', description: 'Gist description', optional: true },
public: { type: 'boolean', description: 'Whether gist is public' },
truncated: { type: 'boolean', description: 'Whether files are truncated' },
comments: { type: 'number', description: 'Number of comments' },
comments_url: { type: 'string', description: 'Comments API URL' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
files: {
type: 'object',
description:
'Files in the gist (object with filenames as keys, each containing filename, type, language, raw_url, size)',
},
owner: {
type: 'object',
description: 'Gist owner',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
},
},

View File

@@ -212,12 +212,36 @@ export const listMilestonesV2Tool: ToolConfig<ListMilestonesParams, any> = {
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Milestone ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
number: { type: 'number', description: 'Milestone number' },
title: { type: 'string', description: 'Title' },
state: { type: 'string', description: 'State' },
html_url: { type: 'string', description: 'Web URL' },
open_issues: { type: 'number', description: 'Open issues' },
closed_issues: { type: 'number', description: 'Closed issues' },
title: { type: 'string', description: 'Milestone title' },
description: { type: 'string', description: 'Milestone description', optional: true },
state: { type: 'string', description: 'State (open or closed)' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'GitHub web URL' },
labels_url: { type: 'string', description: 'Labels API URL' },
due_on: { type: 'string', description: 'Due date (ISO 8601)', optional: true },
open_issues: { type: 'number', description: 'Number of open issues' },
closed_issues: { type: 'number', description: 'Number of closed issues' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
closed_at: { type: 'string', description: 'Close timestamp', optional: true },
creator: {
type: 'object',
description: 'Milestone creator',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
},
},

View File

@@ -161,9 +161,18 @@ export const listStargazersV2Tool: ToolConfig<ListStargazersParams, any> = {
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
avatar_url: { type: 'string', description: 'Avatar URL' },
html_url: { type: 'string', description: 'Profile URL' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
gravatar_id: { type: 'string', description: 'Gravatar ID' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
followers_url: { type: 'string', description: 'Followers API URL' },
following_url: { type: 'string', description: 'Following API URL' },
gists_url: { type: 'string', description: 'Gists API URL' },
starred_url: { type: 'string', description: 'Starred API URL' },
repos_url: { type: 'string', description: 'Repos API URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},

View File

@@ -202,8 +202,67 @@ export const searchCodeV2Tool: ToolConfig<SearchCodeParams, any> = {
name: { type: 'string', description: 'File name' },
path: { type: 'string', description: 'File path' },
sha: { type: 'string', description: 'Blob SHA' },
url: { type: 'string', description: 'API URL' },
git_url: { type: 'string', description: 'Git blob URL' },
html_url: { type: 'string', description: 'GitHub web URL' },
repository: { type: 'object', description: 'Repository object' },
score: { type: 'number', description: 'Search relevance score' },
repository: {
type: 'object',
description: 'Repository containing the code',
properties: {
id: { type: 'number', description: 'Repository ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
name: { type: 'string', description: 'Repository name' },
full_name: { type: 'string', description: 'Full name (owner/repo)' },
private: { type: 'boolean', description: 'Whether repository is private' },
html_url: { type: 'string', description: 'GitHub web URL' },
description: {
type: 'string',
description: 'Repository description',
optional: true,
},
fork: { type: 'boolean', description: 'Whether this is a fork' },
url: { type: 'string', description: 'API URL' },
owner: {
type: 'object',
description: 'Repository owner',
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
},
text_matches: {
type: 'array',
description: 'Text matches showing context',
items: {
type: 'object',
properties: {
object_url: { type: 'string', description: 'Object URL' },
object_type: { type: 'string', description: 'Object type', optional: true },
property: { type: 'string', description: 'Property matched' },
fragment: { type: 'string', description: 'Text fragment with match' },
matches: {
type: 'array',
description: 'Match indices',
items: {
type: 'object',
properties: {
text: { type: 'string', description: 'Matched text' },
indices: { type: 'array', description: 'Start and end indices' },
},
},
},
},
},
},
},
},
},

View File

@@ -212,11 +212,119 @@ export const searchCommitsV2Tool: ToolConfig<SearchCommitsParams, any> = {
type: 'object',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
node_id: { type: 'string', description: 'GraphQL node ID' },
html_url: { type: 'string', description: 'Web URL' },
commit: { type: 'object', description: 'Commit data' },
author: { type: 'object', description: 'GitHub user', optional: true },
committer: { type: 'object', description: 'GitHub user', optional: true },
repository: { type: 'object', description: 'Repository' },
url: { type: 'string', description: 'API URL' },
comments_url: { type: 'string', description: 'Comments API URL' },
score: { type: 'number', description: 'Search relevance score' },
commit: {
type: 'object',
description: 'Core commit data',
properties: {
url: { type: 'string', description: 'Commit API URL' },
message: { type: 'string', description: 'Commit message' },
comment_count: { type: 'number', description: 'Number of comments' },
author: {
type: 'object',
description: 'Git author',
properties: {
name: { type: 'string', description: 'Author name' },
email: { type: 'string', description: 'Author email' },
date: { type: 'string', description: 'Author date (ISO 8601)' },
},
},
committer: {
type: 'object',
description: 'Git committer',
properties: {
name: { type: 'string', description: 'Committer name' },
email: { type: 'string', description: 'Committer email' },
date: { type: 'string', description: 'Commit date (ISO 8601)' },
},
},
tree: {
type: 'object',
description: 'Tree object',
properties: {
sha: { type: 'string', description: 'Tree SHA' },
url: { type: 'string', description: 'Tree API URL' },
},
},
},
},
author: {
type: 'object',
description: 'GitHub user (author)',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
committer: {
type: 'object',
description: 'GitHub user (committer)',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
repository: {
type: 'object',
description: 'Repository containing the commit',
properties: {
id: { type: 'number', description: 'Repository ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
name: { type: 'string', description: 'Repository name' },
full_name: { type: 'string', description: 'Full name (owner/repo)' },
private: { type: 'boolean', description: 'Whether repository is private' },
html_url: { type: 'string', description: 'GitHub web URL' },
description: {
type: 'string',
description: 'Repository description',
optional: true,
},
owner: {
type: 'object',
description: 'Repository owner',
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
},
parents: {
type: 'array',
description: 'Parent commits',
items: {
type: 'object',
properties: {
sha: { type: 'string', description: 'Parent SHA' },
url: { type: 'string', description: 'Parent API URL' },
html_url: { type: 'string', description: 'Parent web URL' },
},
},
},
},
},
},

View File

@@ -222,17 +222,110 @@ export const searchIssuesV2Tool: ToolConfig<SearchIssuesParams, any> = {
type: 'object',
properties: {
id: { type: 'number', description: 'Issue ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
number: { type: 'number', description: 'Issue number' },
title: { type: 'string', description: 'Title' },
state: { type: 'string', description: 'State' },
state: { type: 'string', description: 'State (open or closed)' },
locked: { type: 'boolean', description: 'Whether issue is locked' },
html_url: { type: 'string', description: 'Web URL' },
url: { type: 'string', description: 'API URL' },
repository_url: { type: 'string', description: 'Repository API URL' },
comments_url: { type: 'string', description: 'Comments API URL' },
body: { type: 'string', description: 'Body text', optional: true },
user: { type: 'object', description: 'Author' },
labels: { type: 'array', description: 'Labels' },
assignees: { type: 'array', description: 'Assignees' },
created_at: { type: 'string', description: 'Creation date' },
updated_at: { type: 'string', description: 'Update date' },
closed_at: { type: 'string', description: 'Close date', optional: true },
comments: { type: 'number', description: 'Number of comments' },
score: { type: 'number', description: 'Search relevance score' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
closed_at: { type: 'string', description: 'Close timestamp', optional: true },
user: {
type: 'object',
description: 'Issue author',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
labels: {
type: 'array',
description: 'Issue labels',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Label ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
url: { type: 'string', description: 'API URL' },
name: { type: 'string', description: 'Label name' },
description: { type: 'string', description: 'Label description', optional: true },
color: { type: 'string', description: 'Hex color code' },
default: { type: 'boolean', description: 'Whether this is a default label' },
},
},
},
assignee: {
type: 'object',
description: 'Primary assignee',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
assignees: {
type: 'array',
description: 'All assignees',
items: {
type: 'object',
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
milestone: {
type: 'object',
description: 'Associated milestone',
optional: true,
properties: {
id: { type: 'number', description: 'Milestone ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
number: { type: 'number', description: 'Milestone number' },
title: { type: 'string', description: 'Milestone title' },
description: { type: 'string', description: 'Milestone description', optional: true },
state: { type: 'string', description: 'State (open or closed)' },
html_url: { type: 'string', description: 'Web URL' },
due_on: { type: 'string', description: 'Due date', optional: true },
},
},
pull_request: {
type: 'object',
description: 'Pull request details (if this is a PR)',
optional: true,
properties: {
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Web URL' },
diff_url: { type: 'string', description: 'Diff URL' },
patch_url: { type: 'string', description: 'Patch URL' },
},
},
},
},
},

View File

@@ -210,15 +210,50 @@ export const searchReposV2Tool: ToolConfig<SearchReposParams, any> = {
type: 'object',
properties: {
id: { type: 'number', description: 'Repository ID' },
full_name: { type: 'string', description: 'Full name' },
description: { type: 'string', description: 'Description', optional: true },
html_url: { type: 'string', description: 'Web URL' },
stargazers_count: { type: 'number', description: 'Stars' },
forks_count: { type: 'number', description: 'Forks' },
open_issues_count: { type: 'number', description: 'Open issues' },
language: { type: 'string', description: 'Language', optional: true },
topics: { type: 'array', description: 'Topics' },
owner: { type: 'object', description: 'Owner' },
node_id: { type: 'string', description: 'GraphQL node ID' },
name: { type: 'string', description: 'Repository name' },
full_name: { type: 'string', description: 'Full name (owner/repo)' },
private: { type: 'boolean', description: 'Whether repository is private' },
description: { type: 'string', description: 'Repository description', optional: true },
html_url: { type: 'string', description: 'GitHub web URL' },
url: { type: 'string', description: 'API URL' },
fork: { type: 'boolean', description: 'Whether this is a fork' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
pushed_at: { type: 'string', description: 'Last push timestamp', optional: true },
size: { type: 'number', description: 'Repository size in KB' },
stargazers_count: { type: 'number', description: 'Number of stars' },
watchers_count: { type: 'number', description: 'Number of watchers' },
forks_count: { type: 'number', description: 'Number of forks' },
open_issues_count: { type: 'number', description: 'Number of open issues' },
language: { type: 'string', description: 'Primary programming language', optional: true },
default_branch: { type: 'string', description: 'Default branch name' },
score: { type: 'number', description: 'Search relevance score' },
topics: { type: 'array', description: 'Repository topics' },
license: {
type: 'object',
description: 'License information',
optional: true,
properties: {
key: { type: 'string', description: 'License key (e.g., mit)' },
name: { type: 'string', description: 'License name' },
spdx_id: { type: 'string', description: 'SPDX identifier' },
},
},
owner: {
type: 'object',
description: 'Repository owner',
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
},
},

View File

@@ -181,11 +181,21 @@ export const searchUsersV2Tool: ToolConfig<SearchUsersParams, any> = {
type: 'object',
properties: {
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
login: { type: 'string', description: 'Username' },
html_url: { type: 'string', description: 'Profile URL' },
avatar_url: { type: 'string', description: 'Avatar URL' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
gravatar_id: { type: 'string', description: 'Gravatar ID' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
followers_url: { type: 'string', description: 'Followers API URL' },
following_url: { type: 'string', description: 'Following API URL' },
gists_url: { type: 'string', description: 'Gists API URL' },
starred_url: { type: 'string', description: 'Starred API URL' },
repos_url: { type: 'string', description: 'Repos API URL' },
organizations_url: { type: 'string', description: 'Organizations API URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'Is site admin' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
score: { type: 'number', description: 'Search relevance score' },
},
},
},

View File

@@ -155,10 +155,39 @@ export const updateGistV2Tool: ToolConfig<UpdateGistParams, any> = {
outputs: {
id: { type: 'string', description: 'Gist ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Web URL' },
description: { type: 'string', description: 'Description', optional: true },
public: { type: 'boolean', description: 'Is public' },
updated_at: { type: 'string', description: 'Update date' },
files: { type: 'object', description: 'Current files' },
forks_url: { type: 'string', description: 'Forks API URL' },
commits_url: { type: 'string', description: 'Commits API URL' },
git_pull_url: { type: 'string', description: 'Git pull URL' },
git_push_url: { type: 'string', description: 'Git push URL' },
description: { type: 'string', description: 'Gist description', optional: true },
public: { type: 'boolean', description: 'Whether gist is public' },
truncated: { type: 'boolean', description: 'Whether files are truncated' },
comments: { type: 'number', description: 'Number of comments' },
comments_url: { type: 'string', description: 'Comments API URL' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
files: {
type: 'object',
description:
'Files in the gist (object with filenames as keys, each containing filename, type, language, raw_url, size, truncated, content)',
},
owner: {
type: 'object',
description: 'Gist owner',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
}

View File

@@ -174,13 +174,35 @@ export const updateMilestoneV2Tool: ToolConfig<UpdateMilestoneParams, any> = {
},
outputs: {
id: { type: 'number', description: 'Milestone ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
number: { type: 'number', description: 'Milestone number' },
title: { type: 'string', description: 'Title' },
description: { type: 'string', description: 'Description', optional: true },
state: { type: 'string', description: 'State' },
html_url: { type: 'string', description: 'Web URL' },
due_on: { type: 'string', description: 'Due date', optional: true },
open_issues: { type: 'number', description: 'Open issues' },
closed_issues: { type: 'number', description: 'Closed issues' },
title: { type: 'string', description: 'Milestone title' },
description: { type: 'string', description: 'Milestone description', optional: true },
state: { type: 'string', description: 'State (open or closed)' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'GitHub web URL' },
labels_url: { type: 'string', description: 'Labels API URL' },
due_on: { type: 'string', description: 'Due date (ISO 8601)', optional: true },
open_issues: { type: 'number', description: 'Number of open issues' },
closed_issues: { type: 'number', description: 'Number of closed issues' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
closed_at: { type: 'string', description: 'Close timestamp', optional: true },
creator: {
type: 'object',
description: 'Milestone creator',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
}

View File

@@ -105,14 +105,85 @@ export const batchUpdateTool: ToolConfig<
},
},
writeControl: {
type: 'json',
type: 'object',
description: 'Write control information with revision IDs',
optional: true,
properties: {
requiredRevisionId: {
type: 'string',
description: 'Required revision ID for conflict detection',
},
targetRevisionId: { type: 'string', description: 'Target revision ID' },
},
},
form: {
type: 'json',
type: 'object',
description: 'The updated form (if includeFormInResponse was true)',
optional: true,
properties: {
formId: { type: 'string', description: 'The form ID' },
info: {
type: 'object',
description: 'Form info containing title and description',
properties: {
title: { type: 'string', description: 'The form title visible to responders' },
description: { type: 'string', description: 'The form description' },
documentTitle: { type: 'string', description: 'The document title visible in Drive' },
},
},
settings: {
type: 'object',
description: 'Form settings',
properties: {
quizSettings: {
type: 'object',
description: 'Quiz settings',
properties: {
isQuiz: { type: 'boolean', description: 'Whether the form is a quiz' },
},
},
emailCollectionType: { type: 'string', description: 'Email collection type' },
},
},
items: {
type: 'array',
description: 'The form items (questions, sections, etc.)',
items: {
type: 'object',
properties: {
itemId: { type: 'string', description: 'Item ID' },
title: { type: 'string', description: 'Item title' },
description: { type: 'string', description: 'Item description' },
questionItem: { type: 'json', description: 'Question item configuration' },
questionGroupItem: { type: 'json', description: 'Question group configuration' },
pageBreakItem: { type: 'json', description: 'Page break configuration' },
textItem: { type: 'json', description: 'Text item configuration' },
imageItem: { type: 'json', description: 'Image item configuration' },
videoItem: { type: 'json', description: 'Video item configuration' },
},
},
},
revisionId: { type: 'string', description: 'The revision ID of the form' },
responderUri: { type: 'string', description: 'The URI to share with responders' },
linkedSheetId: { type: 'string', description: 'The ID of the linked Google Sheet' },
publishSettings: {
type: 'object',
description: 'Form publish settings',
properties: {
publishState: {
type: 'object',
description: 'Current publish state',
properties: {
isPublished: { type: 'boolean', description: 'Whether the form is published' },
isAcceptingResponses: {
type: 'boolean',
description: 'Whether the form is accepting responses',
},
},
},
},
},
},
},
},
}

View File

@@ -165,4 +165,43 @@ export const getResponsesTool: ToolConfig<GoogleFormsGetResponsesParams> = {
} as unknown as Record<string, unknown>,
}
},
outputs: {
responses: {
type: 'array',
description: 'Array of form responses (when no responseId provided)',
items: {
type: 'object',
properties: {
responseId: { type: 'string', description: 'Unique response ID' },
createTime: { type: 'string', description: 'When the response was created' },
lastSubmittedTime: {
type: 'string',
description: 'When the response was last submitted',
},
answers: {
type: 'json',
description: 'Map of question IDs to answer values',
},
},
},
},
response: {
type: 'object',
description: 'Single form response (when responseId is provided)',
properties: {
responseId: { type: 'string', description: 'Unique response ID' },
createTime: { type: 'string', description: 'When the response was created' },
lastSubmittedTime: { type: 'string', description: 'When the response was last submitted' },
answers: {
type: 'json',
description: 'Map of question IDs to answer values',
},
},
},
raw: {
type: 'json',
description: 'Raw API response data',
},
},
}

View File

@@ -351,8 +351,16 @@ export const createShapeTool: ToolConfig<CreateShapeParams, CreateShapeResponse>
description: 'The type of shape that was created',
},
metadata: {
type: 'json',
type: 'object',
description: 'Operation metadata including presentation ID and page object ID',
properties: {
presentationId: { type: 'string', description: 'The presentation ID' },
pageObjectId: {
type: 'string',
description: 'The page object ID where the shape was created',
},
url: { type: 'string', description: 'URL to the presentation' },
},
},
},
}

View File

@@ -220,8 +220,16 @@ export const createTableTool: ToolConfig<CreateTableParams, CreateTableResponse>
description: 'Number of columns in the table',
},
metadata: {
type: 'json',
type: 'object',
description: 'Operation metadata including presentation ID and page object ID',
properties: {
presentationId: { type: 'string', description: 'The presentation ID' },
pageObjectId: {
type: 'string',
description: 'The page object ID where the table was created',
},
url: { type: 'string', description: 'URL to the presentation' },
},
},
},
}

View File

@@ -124,8 +124,12 @@ export const deleteObjectTool: ToolConfig<DeleteObjectParams, DeleteObjectRespon
description: 'The object ID that was deleted',
},
metadata: {
type: 'json',
type: 'object',
description: 'Operation metadata including presentation ID and URL',
properties: {
presentationId: { type: 'string', description: 'The presentation ID' },
url: { type: 'string', description: 'URL to the presentation' },
},
},
},
}

View File

@@ -145,8 +145,16 @@ export const duplicateObjectTool: ToolConfig<DuplicateObjectParams, DuplicateObj
description: 'The object ID of the newly created duplicate',
},
metadata: {
type: 'json',
type: 'object',
description: 'Operation metadata including presentation ID and source object ID',
properties: {
presentationId: { type: 'string', description: 'The presentation ID' },
sourceObjectId: {
type: 'string',
description: 'The original object ID that was duplicated',
},
url: { type: 'string', description: 'URL to the presentation' },
},
},
},
}

View File

@@ -126,17 +126,39 @@ export const getPageTool: ToolConfig<GetPageParams, GetPageResponse> = {
description: 'The type of page (SLIDE, MASTER, LAYOUT, NOTES, NOTES_MASTER)',
},
pageElements: {
type: 'json',
type: 'array',
description: 'Array of page elements (shapes, images, tables, etc.) on this page',
items: {
type: 'json',
},
},
slideProperties: {
type: 'json',
type: 'object',
description: 'Properties specific to slides (layout, master, notes)',
optional: true,
properties: {
layoutObjectId: {
type: 'string',
description: 'Object ID of the layout this slide is based on',
},
masterObjectId: {
type: 'string',
description: 'Object ID of the master this slide is based on',
},
notesPage: {
type: 'json',
description: 'The notes page associated with the slide',
optional: true,
},
},
},
metadata: {
type: 'json',
type: 'object',
description: 'Operation metadata including presentation ID and URL',
properties: {
presentationId: { type: 'string', description: 'The presentation ID' },
url: { type: 'string', description: 'URL to the presentation' },
},
},
},
}

View File

@@ -152,8 +152,12 @@ export const insertTextTool: ToolConfig<InsertTextParams, InsertTextResponse> =
description: 'The text that was inserted',
},
metadata: {
type: 'json',
type: 'object',
description: 'Operation metadata including presentation ID and URL',
properties: {
presentationId: { type: 'string', description: 'The presentation ID' },
url: { type: 'string', description: 'URL to the presentation' },
},
},
},
}

View File

@@ -160,8 +160,12 @@ export const updateSlidesPositionTool: ToolConfig<
description: 'The index where the slides were moved to',
},
metadata: {
type: 'json',
type: 'object',
description: 'Operation metadata including presentation ID and URL',
properties: {
presentationId: { type: 'string', description: 'The presentation ID' },
url: { type: 'string', description: 'URL to the presentation' },
},
},
},
}