mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-21 04:48:00 -05:00
nested tag dropdown, more well-defined nested outputs, keyboard nav for context menus, etc
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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`
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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]'>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user