veryfront 0.1.560 → 0.1.562

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -197,12 +197,12 @@ export default {
197
197
  ".env.example": "# Atlassian OAuth credentials\n# Get these from: https://developer.atlassian.com/console/myapps/\nATLASSIAN_CLIENT_ID=your_client_id_here\nATLASSIAN_CLIENT_SECRET=your_client_secret_here\n",
198
198
  "app/api/auth/confluence/callback/route.ts": "import { confluenceConfig, createOAuthCallbackHandler } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\nimport { oauthMemoryTokenStore } from \"../../../../../lib/oauth-memory-store.ts\";\n\nconst hybridTokenStore = {\n getTokens(serviceId: string, userId: string) {\n return tokenStore.getToken(userId, serviceId);\n },\n async setTokens(\n serviceId: string,\n userId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(userId, serviceId, tokens);\n },\n async clearTokens(serviceId: string, userId: string) {\n await tokenStore.revokeToken(userId, serviceId);\n },\n setState(\n state: string,\n meta: {\n userId: string;\n serviceId: string;\n codeVerifier?: string;\n redirectUri?: string;\n scopes?: string[];\n createdAt: number;\n },\n ) {\n return oauthMemoryTokenStore.setState(state, meta);\n },\n consumeState(state: string) {\n return oauthMemoryTokenStore.consumeState(state);\n },\n};\n\nexport const GET = createOAuthCallbackHandler(confluenceConfig, { tokenStore: hybridTokenStore });\n",
199
199
  "app/api/auth/confluence/route.ts": "import { confluenceConfig, createOAuthInitHandler } from \"veryfront/oauth\";\nimport { oauthMemoryTokenStore } from \"../../../../../lib/oauth-memory-store.ts\";\nimport { requireUserIdFromRequest } from \"../../../../../lib/user-id.ts\";\n\nfunction getUserId(request: Request): string {\n return requireUserIdFromRequest(request);\n}\n\nexport const GET = createOAuthInitHandler(confluenceConfig, {\n tokenStore: oauthMemoryTokenStore,\n getUserId,\n});",
200
- "lib/confluence-client.ts": "import { getAccessToken, getCloudId } from \"./token-store.ts\";\n\nconst CONFLUENCE_API_BASE = \"https://api.atlassian.com/ex/confluence\";\n\ninterface ConfluenceResponse<T> {\n results: T[];\n size: number;\n start?: number;\n limit?: number;\n _links?: {\n next?: string;\n base?: string;\n };\n}\n\nexport interface ConfluenceSpace {\n id: string;\n key: string;\n name: string;\n type: string;\n status: string;\n _links: {\n webui: string;\n };\n}\n\nexport interface ConfluencePage {\n id: string;\n type: \"page\" | \"blogpost\";\n status: string;\n title: string;\n spaceId?: string;\n parentId?: string;\n version: {\n number: number;\n message?: string;\n };\n body?: {\n storage?: {\n value: string;\n representation: \"storage\";\n };\n view?: {\n value: string;\n representation: \"view\";\n };\n };\n _links: {\n webui: string;\n tinyui?: string;\n };\n}\n\nexport interface ConfluenceSearchResult {\n content: {\n id: string;\n type: string;\n status: string;\n title: string;\n space?: {\n id: string;\n key: string;\n name: string;\n };\n history?: {\n lastUpdated: {\n when: string;\n };\n };\n _links: {\n webui: string;\n };\n };\n excerpt?: string;\n url: string;\n resultGlobalContainer?: {\n title: string;\n };\n}\n\nasync function confluenceFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const [token, cloudId] = await Promise.all([getAccessToken(), getCloudId()]);\n\n if (!token || !cloudId) {\n throw new Error(\"Not authenticated with Confluence. Please connect your Atlassian account.\");\n }\n\n const url = `${CONFLUENCE_API_BASE}/${cloudId}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({}))) as { message?: string };\n throw new Error(`Confluence API error: ${response.status} ${error.message ?? response.statusText}`);\n }\n\n return response.json() as Promise<T>;\n}\n\nfunction buildEndpoint(path: string, params?: URLSearchParams): string {\n const query = params?.toString();\n return `${path}${query ? `?${query}` : \"\"}`;\n}\n\nexport async function listSpaces(options?: {\n limit?: number;\n type?: \"global\" | \"personal\";\n}): Promise<ConfluenceSpace[]> {\n const params = new URLSearchParams();\n\n if (options?.limit) params.set(\"limit\", options.limit.toString());\n if (options?.type) params.set(\"type\", options.type);\n\n const response = await confluenceFetch<ConfluenceResponse<ConfluenceSpace>>(\n buildEndpoint(\"/wiki/rest/api/space\", params),\n );\n\n return response.results ?? [];\n}\n\nexport async function searchContent(\n query: string,\n options?: {\n cql?: string;\n limit?: number;\n spaceKey?: string;\n },\n): Promise<ConfluenceSearchResult[]> {\n const params = new URLSearchParams();\n\n let cqlQuery = options?.cql ?? `title ~ \"${query}\" OR text ~ \"${query}\"`;\n if (options?.spaceKey) cqlQuery += ` AND space = \"${options.spaceKey}\"`;\n\n params.set(\"cql\", cqlQuery);\n if (options?.limit) params.set(\"limit\", options.limit.toString());\n\n const response = await confluenceFetch<ConfluenceResponse<ConfluenceSearchResult>>(\n buildEndpoint(\"/wiki/rest/api/search\", params),\n );\n\n return response.results ?? [];\n}\n\nexport function getPage(pageId: string, expand?: string[]): Promise<ConfluencePage> {\n const params = new URLSearchParams();\n if (expand?.length) params.set(\"expand\", expand.join(\",\"));\n\n return confluenceFetch<ConfluencePage>(buildEndpoint(`/wiki/rest/api/content/${pageId}`, params));\n}\n\nexport function getPageContent(pageId: string): Promise<ConfluencePage> {\n return getPage(pageId, [\"body.storage\", \"body.view\", \"version\", \"space\"]);\n}\n\nexport function createPage(options: {\n spaceKey: string;\n title: string;\n content: string;\n parentId?: string;\n type?: \"page\" | \"blogpost\";\n}): Promise<ConfluencePage> {\n const body = {\n type: options.type ?? \"page\",\n title: options.title,\n space: { key: options.spaceKey },\n body: {\n storage: {\n value: options.content,\n representation: \"storage\" as const,\n },\n },\n ...(options.parentId ? { ancestors: [{ id: options.parentId }] } : {}),\n };\n\n return confluenceFetch<ConfluencePage>(\"/wiki/rest/api/content\", {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\nexport async function updatePage(\n pageId: string,\n options: {\n title?: string;\n content?: string;\n version: number;\n versionMessage?: string;\n },\n): Promise<ConfluencePage> {\n await getPage(pageId, [\"version\"]);\n\n const body: Record<string, unknown> = {\n version: {\n number: options.version,\n message: options.versionMessage,\n },\n type: \"page\",\n };\n\n if (options.title) body.title = options.title;\n\n if (options.content) {\n body.body = {\n storage: {\n value: options.content,\n representation: \"storage\",\n },\n };\n }\n\n return confluenceFetch<ConfluencePage>(`/wiki/rest/api/content/${pageId}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n });\n}\n\nexport function extractPlainText(storageHtml: string): string {\n return storageHtml\n .replace(/<[^>]*>/g, \" \")\n .replace(/&nbsp;/g, \" \")\n .replace(/&amp;/g, \"&\")\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nexport function formatAsStorage(text: string): string {\n const paragraphs = text.split(\"\\n\\n\").filter((p) => p.trim());\n return paragraphs.map((p) => `<p>${escapeHtml(p.trim())}</p>`).join(\"\\n\");\n}\n\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n",
201
- "tools/create-page.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { createPage, formatAsStorage } from \"../../lib/confluence-client.ts\";\n\nexport default tool({\n id: \"create-page\",\n description:\n \"Create a new page in a Confluence space. Can optionally be created as a child of an existing page.\",\n inputSchema: defineSchema((v) => v.object({\n spaceKey: v\n .string()\n .describe('The key of the space to create the page in (e.g., \"TEAM\", \"DEV\")'),\n title: v.string().describe(\"Title of the new page\"),\n content: v\n .string()\n .describe(\n \"Content for the page (can be plain text or Confluence storage format HTML)\",\n ),\n parentId: v\n .string()\n .optional()\n .describe(\"Optional ID of the parent page to create this as a child page\"),\n type: v\n .enum([\"page\", \"blogpost\"])\n .default(\"page\")\n .describe(\"Type of content to create\"),\n }))(),\n async execute({ spaceKey, title, content, parentId, type }) {\n const trimmedContent = content.trim();\n const storageContent = trimmedContent.startsWith(\"<\")\n ? trimmedContent\n : formatAsStorage(trimmedContent);\n\n const page = await createPage({\n spaceKey,\n title,\n content: storageContent,\n parentId,\n type,\n });\n\n return {\n id: page.id,\n title: page.title,\n type: page.type,\n url: page._links.webui,\n version: page.version.number,\n spaceId: page.spaceId,\n };\n },\n});\n",
202
- "tools/get-page.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { extractPlainText, getPageContent } from \"../../lib/confluence-client.ts\";\n\nexport default tool({\n id: \"get-page\",\n description:\n \"Get the content of a specific Confluence page. Returns the page title, content, and metadata.\",\n inputSchema: defineSchema((v) => v.object({\n pageId: v.string().describe(\"The ID of the Confluence page to retrieve\"),\n }))(),\n async execute({ pageId }) {\n const page = await getPageContent(pageId);\n\n const htmlContent = page.body?.storage?.value ?? page.body?.view?.value ?? \"\";\n const content = extractPlainText(htmlContent);\n\n return {\n id: page.id,\n type: page.type,\n title: page.title,\n content,\n htmlContent,\n version: page.version.number,\n url: page._links.webui,\n spaceId: page.spaceId,\n parentId: page.parentId,\n };\n },\n});\n",
200
+ "lib/confluence-client.ts": "import { getAccessToken, getCloudId } from \"./token-store.ts\";\n\nconst CONFLUENCE_API_BASE = \"https://api.atlassian.com/ex/confluence\";\n\ninterface ConfluenceResponse<T> {\n results: T[];\n size: number;\n start?: number;\n limit?: number;\n _links?: {\n next?: string;\n base?: string;\n };\n}\n\nexport interface ConfluenceSpace {\n id: string;\n key: string;\n name: string;\n type: string;\n status: string;\n _links: {\n webui: string;\n };\n}\n\nexport interface ConfluencePage {\n id: string;\n type?: \"page\" | \"blogpost\";\n status: string;\n title: string;\n spaceId?: string;\n parentId?: string;\n version: {\n number: number;\n message?: string;\n };\n body?: {\n storage?: {\n value: string;\n representation: \"storage\";\n };\n view?: {\n value: string;\n representation: \"view\";\n };\n };\n _links: {\n webui: string;\n tinyui?: string;\n };\n}\n\nexport interface ConfluenceSearchResult {\n content: {\n id: string;\n type: string;\n status: string;\n title: string;\n space?: {\n id: string;\n key: string;\n name: string;\n };\n history?: {\n lastUpdated: {\n when: string;\n };\n };\n _links: {\n webui: string;\n };\n };\n excerpt?: string;\n url: string;\n resultGlobalContainer?: {\n title: string;\n };\n}\n\nasync function confluenceFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const [token, cloudId] = await Promise.all([getAccessToken(), getCloudId()]);\n\n if (!token || !cloudId) {\n throw new Error(\"Not authenticated with Confluence. Please connect your Atlassian account.\");\n }\n\n const url = `${CONFLUENCE_API_BASE}/${cloudId}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({}))) as { message?: string };\n throw new Error(`Confluence API error: ${response.status} ${error.message ?? response.statusText}`);\n }\n\n return response.json() as Promise<T>;\n}\n\nfunction buildEndpoint(path: string, params?: URLSearchParams): string {\n const query = params?.toString();\n return `${path}${query ? `?${query}` : \"\"}`;\n}\n\nexport async function listSpaces(options?: {\n limit?: number;\n type?: \"global\" | \"personal\";\n}): Promise<ConfluenceSpace[]> {\n const params = new URLSearchParams();\n\n if (options?.limit) params.set(\"limit\", options.limit.toString());\n if (options?.type) params.set(\"type\", options.type);\n\n const response = await confluenceFetch<ConfluenceResponse<ConfluenceSpace>>(\n buildEndpoint(\"/wiki/rest/api/space\", params),\n );\n\n return response.results ?? [];\n}\n\nasync function getSpaceIdByKey(spaceKey: string): Promise<string> {\n const spaces = await listSpaces({ limit: 250 });\n const space = spaces.find((s) => s.key === spaceKey);\n if (!space) {\n throw new Error(`Confluence space not found: ${spaceKey}`);\n }\n return space.id;\n}\n\nexport async function searchContent(\n query: string,\n options?: {\n cql?: string;\n limit?: number;\n spaceKey?: string;\n },\n): Promise<ConfluenceSearchResult[]> {\n const params = new URLSearchParams();\n\n let cqlQuery = options?.cql ?? `title ~ \"${query}\" OR text ~ \"${query}\"`;\n if (options?.spaceKey) cqlQuery += ` AND space = \"${options.spaceKey}\"`;\n\n params.set(\"cql\", cqlQuery);\n if (options?.limit) params.set(\"limit\", options.limit.toString());\n\n const response = await confluenceFetch<ConfluenceResponse<ConfluenceSearchResult>>(\n buildEndpoint(\"/wiki/rest/api/search\", params),\n );\n\n return response.results ?? [];\n}\n\n// Uses Confluence v2 API — v1 /wiki/rest/api/content is deprecated and returns 410 on newer instances\nexport function getPage(pageId: string, _expand?: string[]): Promise<ConfluencePage> {\n return confluenceFetch<ConfluencePage>(`/wiki/api/v2/pages/${pageId}?body-format=storage`);\n}\n\nexport function getPageContent(pageId: string): Promise<ConfluencePage> {\n return getPage(pageId);\n}\n\nexport async function createPage(options: {\n spaceKey: string;\n title: string;\n content: string;\n parentId?: string;\n type?: \"page\" | \"blogpost\";\n}): Promise<ConfluencePage> {\n const spaceId = await getSpaceIdByKey(options.spaceKey);\n\n const body: Record<string, unknown> = {\n spaceId,\n title: options.title,\n status: \"current\",\n body: {\n representation: \"storage\",\n value: options.content,\n },\n };\n\n if (options.parentId) body.parentId = options.parentId;\n\n return confluenceFetch<ConfluencePage>(\"/wiki/api/v2/pages\", {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\nexport function updatePage(\n pageId: string,\n options: {\n title?: string;\n content?: string;\n version: number;\n versionMessage?: string;\n },\n): Promise<ConfluencePage> {\n const body: Record<string, unknown> = {\n id: pageId,\n status: \"current\",\n version: {\n number: options.version,\n message: options.versionMessage,\n },\n };\n\n if (options.title) body.title = options.title;\n\n if (options.content) {\n body.body = {\n representation: \"storage\",\n value: options.content,\n };\n }\n\n return confluenceFetch<ConfluencePage>(`/wiki/api/v2/pages/${pageId}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n });\n}\n\nexport function extractPlainText(storageHtml: string): string {\n return storageHtml\n .replace(/<[^>]*>/g, \" \")\n .replace(/&nbsp;/g, \" \")\n .replace(/&amp;/g, \"&\")\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nexport function formatAsStorage(text: string): string {\n const paragraphs = text.split(\"\\n\\n\").filter((p) => p.trim());\n return paragraphs.map((p) => `<p>${escapeHtml(p.trim())}</p>`).join(\"\\n\");\n}\n\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n",
201
+ "tools/create-page.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { createPage, formatAsStorage } from \"../../lib/confluence-client.ts\";\n\nexport default tool({\n id: \"create-page\",\n description:\n \"Create a new page in a Confluence space. Can optionally be created as a child of an existing page.\",\n inputSchema: defineSchema((v) => v.object({\n spaceKey: v\n .string()\n .describe('The key of the space to create the page in (e.g., \"TEAM\", \"DEV\")'),\n title: v.string().describe(\"Title of the new page\"),\n content: v\n .string()\n .describe(\n \"Content for the page (can be plain text or Confluence storage format HTML)\",\n ),\n parentId: v\n .string()\n .optional()\n .describe(\"Optional ID of the parent page to create this as a child page\"),\n type: v\n .enum([\"page\", \"blogpost\"])\n .default(\"page\")\n .describe(\"Type of content to create\"),\n }))(),\n async execute({ spaceKey, title, content, parentId, type }) {\n const trimmedContent = content.trim();\n const storageContent = trimmedContent.startsWith(\"<\")\n ? trimmedContent\n : formatAsStorage(trimmedContent);\n\n const page = await createPage({\n spaceKey,\n title,\n content: storageContent,\n parentId,\n type,\n });\n\n return {\n id: page.id,\n title: page.title,\n type: page.type ?? \"page\",\n url: page._links.webui,\n version: page.version.number,\n spaceId: page.spaceId,\n };\n },\n});\n",
202
+ "tools/get-page.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { extractPlainText, getPageContent } from \"../../lib/confluence-client.ts\";\n\nexport default tool({\n id: \"get-page\",\n description:\n \"Get the content of a specific Confluence page. Returns the page title, content, and metadata.\",\n inputSchema: defineSchema((v) => v.object({\n pageId: v.string().describe(\"The ID of the Confluence page to retrieve\"),\n }))(),\n async execute({ pageId }) {\n const page = await getPageContent(pageId);\n\n const htmlContent = page.body?.storage?.value ?? page.body?.view?.value ?? \"\";\n const content = extractPlainText(htmlContent);\n\n return {\n id: page.id,\n type: page.type ?? \"page\",\n title: page.title,\n content,\n htmlContent,\n version: page.version.number,\n url: page._links.webui,\n spaceId: page.spaceId,\n parentId: page.parentId,\n };\n },\n});\n",
203
203
  "tools/list-spaces.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { listSpaces } from \"../../lib/confluence-client.ts\";\n\nexport default tool({\n id: \"list-spaces\",\n description: \"List all accessible Confluence spaces. Returns space keys, names, and links.\",\n inputSchema: defineSchema((v) => v.object({\n type: v\n .enum([\"global\", \"personal\", \"all\"])\n .default(\"all\")\n .describe(\"Type of spaces to list (global, personal, or all)\"),\n limit: v\n .number()\n .min(1)\n .max(100)\n .default(25)\n .describe(\"Maximum number of spaces to return\"),\n }))(),\n async execute({ type, limit }) {\n const spaces = await listSpaces({\n type: type === \"all\" ? undefined : type,\n limit,\n });\n\n return spaces.map(({ id, key, name, type, status, _links }) => ({\n id,\n key,\n name,\n type,\n status,\n url: _links.webui,\n }));\n },\n});\n",
204
204
  "tools/search-content.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { searchContent } from \"../../lib/confluence-client.ts\";\n\nexport default tool({\n id: \"search-content\",\n description:\n \"Search for pages and blog posts in Confluence. Returns matching content with titles, excerpts, and links.\",\n inputSchema: defineSchema((v) => v.object({\n query: v.string().describe(\"Search query to find pages or blog posts\"),\n spaceKey: v\n .string()\n .optional()\n .describe(\"Optional space key to limit search to a specific space\"),\n limit: v\n .number()\n .min(1)\n .max(50)\n .default(10)\n .describe(\"Maximum number of results to return\"),\n }))(),\n async execute({ query, spaceKey, limit }) {\n const results = await searchContent(query, { spaceKey, limit });\n\n return results.map((result) => {\n const { content, excerpt, url } = result;\n const space = content.space;\n\n return {\n id: content.id,\n type: content.type,\n title: content.title,\n excerpt,\n url,\n space: space\n ? {\n id: space.id,\n key: space.key,\n name: space.name,\n }\n : undefined,\n lastUpdated: content.history?.lastUpdated.when,\n };\n });\n },\n});\n",
205
- "tools/update-page.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { formatAsStorage, getPage, updatePage } from \"../../lib/confluence-client.ts\";\n\nfunction toStorageContent(content?: string): string | undefined {\n if (!content) return undefined;\n\n const trimmed = content.trim();\n if (trimmed.startsWith(\"<\")) return content;\n\n return formatAsStorage(content);\n}\n\nexport default tool({\n id: \"update-page\",\n description:\n \"Update the content or title of an existing Confluence page. Requires the current version number.\",\n inputSchema: defineSchema((v) => v.object({\n pageId: v.string().describe(\"The ID of the page to update\"),\n title: v\n .string()\n .optional()\n .describe(\"New title for the page (leave empty to keep current title)\"),\n content: v\n .string()\n .optional()\n .describe(\"New content for the page (can be plain text or Confluence storage format HTML)\"),\n versionMessage: v\n .string()\n .optional()\n .describe(\"Optional message describing the changes made\"),\n }))(),\n async execute({ pageId, title, content, versionMessage }) {\n const currentPage = await getPage(pageId, [\"version\"]);\n const storageContent = toStorageContent(content);\n\n const updatedPage = await updatePage(pageId, {\n title,\n content: storageContent,\n version: currentPage.version.number + 1,\n versionMessage,\n });\n\n return {\n id: updatedPage.id,\n title: updatedPage.title,\n type: updatedPage.type,\n url: updatedPage._links.webui,\n version: updatedPage.version.number,\n versionMessage: updatedPage.version.message,\n };\n },\n});\n"
205
+ "tools/update-page.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { formatAsStorage, getPage, updatePage } from \"../../lib/confluence-client.ts\";\n\nfunction toStorageContent(content?: string): string | undefined {\n if (!content) return undefined;\n\n const trimmed = content.trim();\n if (trimmed.startsWith(\"<\")) return content;\n\n return formatAsStorage(content);\n}\n\nexport default tool({\n id: \"update-page\",\n description:\n \"Update the content or title of an existing Confluence page. Requires the current version number.\",\n inputSchema: defineSchema((v) => v.object({\n pageId: v.string().describe(\"The ID of the page to update\"),\n title: v\n .string()\n .optional()\n .describe(\"New title for the page (leave empty to keep current title)\"),\n content: v\n .string()\n .optional()\n .describe(\"New content for the page (can be plain text or Confluence storage format HTML)\"),\n versionMessage: v\n .string()\n .optional()\n .describe(\"Optional message describing the changes made\"),\n }))(),\n async execute({ pageId, title, content, versionMessage }) {\n const currentPage = await getPage(pageId, [\"version\"]);\n const storageContent = toStorageContent(content);\n\n const updatedPage = await updatePage(pageId, {\n title,\n content: storageContent,\n version: currentPage.version.number + 1,\n versionMessage,\n });\n\n return {\n id: updatedPage.id,\n title: updatedPage.title,\n type: updatedPage.type ?? \"page\",\n url: updatedPage._links.webui,\n version: updatedPage.version.number,\n versionMessage: updatedPage.version.message,\n };\n },\n});\n"
206
206
  }
207
207
  },
208
208
  "integration:discord": {
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.560",
3
+ "version": "0.1.562",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "workspace": [
@@ -334,7 +334,7 @@ export default {
334
334
  "typecheck": "deno task generate:manifests:check && deno check src/index.ts cli/main.ts src/server/index.ts src/routing/api/index.ts src/rendering/index.ts src/platform/index.ts src/platform/adapters/index.ts src/build/index.ts src/build/production-build/index.ts src/transforms/index.ts src/config/index.ts src/utils/index.ts src/data/index.ts src/security/index.ts src/middleware/index.ts src/server/handlers/dev/index.ts src/server/handlers/request/api/index.ts src/rendering/cache/index.ts src/rendering/cache/stores/index.ts src/rendering/rsc/actions/index.ts src/html/index.ts src/modules/index.ts src/proxy/main.ts src/chat/index.ts src/markdown/index.ts src/mdx/index.ts src/fs/index.ts src/oauth/index.ts src/agent/index.ts src/agent/service/route-export.check.ts src/tool/index.ts src/workflow/index.ts src/prompt/index.ts src/resource/index.ts src/jobs/index.ts src/mcp/index.ts src/provider/index.ts",
335
335
  "verify": "deno task generate:manifests:check && deno fmt --check src/ cli/ react/ && DENO_NO_PACKAGE_JSON=1 deno lint src/ cli/ react/ && deno task lint:style && deno task lint:cli-boundary && deno task lint:wildcard-exports && deno task lint:barrel-jsdoc && deno task lint:ban-zod && deno task lint:core-deps && deno task lint:dependency-boundaries && deno task lint:extension-contracts && deno task lint:extension-capabilities && deno task docs:validate && deno task typecheck && deno task test && deno task test:e2e:binary",
336
336
  "verify:quick": "deno task generate:manifests:check && deno fmt --check src/ cli/ react/ && DENO_NO_PACKAGE_JSON=1 deno lint src/ cli/ react/ && deno task lint:style && deno task lint:cli-boundary && deno task lint:wildcard-exports && deno task lint:barrel-jsdoc && deno task lint:ban-zod && deno task lint:core-deps && deno task lint:dependency-boundaries && deno task lint:extension-contracts && deno task lint:extension-capabilities && deno task docs:validate && deno task typecheck",
337
- "docs": "deno run --allow-read --allow-write --allow-run scripts/docs/generate-api-reference.ts",
337
+ "docs": "deno run --allow-read --allow-write --allow-run --allow-env scripts/docs/generate-api-reference.ts",
338
338
  "docs:coverage": "deno run --allow-read scripts/docs/docs-coverage.ts",
339
339
  "docs:copy": "rm -rf ../../docs/docs/code/reference && cp -r docs/reference/ ../../docs/docs/code/reference/",
340
340
  "docs:validate": "deno run --allow-read scripts/docs/validate-api-reference.ts && deno run --allow-read scripts/docs/validate-guides.ts && deno test --config=scripts/test.deno.json --no-check --allow-read scripts/docs/docs-coverage.test.ts && deno test --no-check --allow-read tests/docs/guide-contracts.test.ts tests/docs/guide-content.test.ts && deno test --no-check --allow-all tests/docs/guide-examples.test.ts tests/docs/guide-code-examples.test.ts && deno run -A scripts/lint/check-doc-links.ts",
@@ -862,7 +862,7 @@ export async function createConversationAgentRun(input) {
862
862
  : {}),
863
863
  }
864
864
  : {
865
- mode: "default_chat",
865
+ mode: "agent",
866
866
  agent_id: input.agentId,
867
867
  initial_status: "running",
868
868
  ...(targets.sourceTargetKind ? { source_target_kind: targets.sourceTargetKind } : {}),
@@ -1,7 +1,7 @@
1
1
  /** Default value for project steering paths. */
2
2
  export declare const DEFAULT_PROJECT_STEERING_PATHS: {
3
3
  readonly instructions: readonly ["AGENTS.md"];
4
- readonly skills: readonly [".veryfront/skills"];
4
+ readonly skills: readonly ["skills", ".veryfront/skills"];
5
5
  };
6
6
  /** Shared project steering file mutation tool names value. */
7
7
  export declare const PROJECT_STEERING_FILE_MUTATION_TOOL_NAMES: readonly ["create_file", "update_file", "delete_file", "move_file"];
@@ -1,7 +1,7 @@
1
1
  /** Default value for project steering paths. */
2
2
  export const DEFAULT_PROJECT_STEERING_PATHS = {
3
3
  instructions: ["AGENTS.md"],
4
- skills: [".veryfront/skills"],
4
+ skills: ["skills", ".veryfront/skills"],
5
5
  };
6
6
  /** Shared project steering file mutation tool names value. */
7
7
  export const PROJECT_STEERING_FILE_MUTATION_TOOL_NAMES = [
@@ -1 +1 @@
1
- {"version":3,"file":"project-skill-loader.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/runtime/project-skill-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EACV,4BAA4B,EAC5B,kBAAkB,EAClB,0BAA0B,EAC1B,6BAA6B,EAC9B,MAAM,2BAA2B,CAAC;AAGnC,yCAAyC;AACzC,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,4DAA4D;AAC5D,MAAM,MAAM,yBAAyB,GAAG;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF,mEAAmE;AACnE,MAAM,MAAM,+BAA+B,GAAG;IAC5C,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACtE,CAAC;AAEF,wDAAwD;AACxD,MAAM,MAAM,gCAAgC,GAAG;IAC7C,cAAc,EAAE,CAAC,OAAO,EAAE,4BAA4B,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IAC9F,eAAe,EAAE,CACf,OAAO,EAAE,6BAA6B,KACnC,OAAO,CAAC,0BAA0B,EAAE,CAAC,CAAC;IAC3C,aAAa,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;IACrD,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAClD,MAAM,CAAC,EAAE,+BAA+B,CAAC;CAC1C,CAAC;AAEF,4DAA4D;AAC5D,MAAM,MAAM,yBAAyB,GAAG;IACtC,0BAA0B,EAAE,CAC1B,OAAO,EAAE,0BAA0B,EACnC,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvB,gBAAgB,EAAE,CAChB,OAAO,EAAE,0BAA0B,EACnC,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAAC;IAC/C,yBAAyB,EAAE,CACzB,OAAO,EAAE,0BAA0B,EACnC,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,KACnB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC7B,CAAC;AA2JF,2CAA2C;AAC3C,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,yBAAyB,CAQ3B"}
1
+ {"version":3,"file":"project-skill-loader.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/runtime/project-skill-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EACV,4BAA4B,EAC5B,kBAAkB,EAClB,0BAA0B,EAC1B,6BAA6B,EAC9B,MAAM,2BAA2B,CAAC;AAGnC,yCAAyC;AACzC,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,4DAA4D;AAC5D,MAAM,MAAM,yBAAyB,GAAG;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF,mEAAmE;AACnE,MAAM,MAAM,+BAA+B,GAAG;IAC5C,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACtE,CAAC;AAEF,wDAAwD;AACxD,MAAM,MAAM,gCAAgC,GAAG;IAC7C,cAAc,EAAE,CAAC,OAAO,EAAE,4BAA4B,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IAC9F,eAAe,EAAE,CACf,OAAO,EAAE,6BAA6B,KACnC,OAAO,CAAC,0BAA0B,EAAE,CAAC,CAAC;IAC3C,aAAa,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;IACrD,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAClD,MAAM,CAAC,EAAE,+BAA+B,CAAC;CAC1C,CAAC;AAEF,4DAA4D;AAC5D,MAAM,MAAM,yBAAyB,GAAG;IACtC,0BAA0B,EAAE,CAC1B,OAAO,EAAE,0BAA0B,EACnC,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvB,gBAAgB,EAAE,CAChB,OAAO,EAAE,0BAA0B,EACnC,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAAC;IAC/C,yBAAyB,EAAE,CACzB,OAAO,EAAE,0BAA0B,EACnC,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,KACnB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC7B,CAAC;AAuNF,2CAA2C;AAC3C,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,yBAAyB,CAQ3B"}
@@ -6,35 +6,73 @@ function getSkillPaths(options) {
6
6
  function isAccessDeniedError(error, options) {
7
7
  return options.isAccessDeniedError?.(error) ?? false;
8
8
  }
9
+ async function findProjectSkillSource(input) {
10
+ const projectId = input.context.projectId;
11
+ if (!projectId) {
12
+ return null;
13
+ }
14
+ for (const skillsPath of getSkillPaths(input.options)) {
15
+ const directorySkill = await input.options.getProjectFile({
16
+ projectId,
17
+ authToken: input.context.authToken,
18
+ branchId: input.context.branchId,
19
+ path: `${skillsPath}/${input.skillId}/SKILL.md`,
20
+ });
21
+ if (directorySkill?.content) {
22
+ return { kind: "directory", skillsPath };
23
+ }
24
+ const flatSkill = await input.options.getProjectFile({
25
+ projectId,
26
+ authToken: input.context.authToken,
27
+ branchId: input.context.branchId,
28
+ path: `${skillsPath}/${input.skillId}.md`,
29
+ });
30
+ if (flatSkill?.content) {
31
+ return { kind: "flat", skillsPath };
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+ function collectProjectSkillReferences(input) {
37
+ const skillPrefix = `${input.skillsPath}/${input.skillId}/`;
38
+ const refsPrefix = `${skillPrefix}references/`;
39
+ const references = new Set();
40
+ for (const file of input.allFiles) {
41
+ if (!file.path.startsWith(refsPrefix)) {
42
+ continue;
43
+ }
44
+ const relativePath = file.path.slice(skillPrefix.length);
45
+ if (!relativePath.includes("/")) {
46
+ continue;
47
+ }
48
+ const normalizedReference = normalizeRuntimeSkillReferencePath(relativePath);
49
+ if (normalizedReference) {
50
+ references.add(normalizedReference);
51
+ }
52
+ }
53
+ return [...references].sort();
54
+ }
9
55
  async function listProjectSkillReferences(input) {
10
56
  const projectId = input.context.projectId;
11
57
  if (!projectId) {
12
58
  return [];
13
59
  }
60
+ const source = input.skillsPath
61
+ ? { kind: "directory", skillsPath: input.skillsPath }
62
+ : await findProjectSkillSource(input);
63
+ if (source?.kind !== "directory") {
64
+ return [];
65
+ }
14
66
  const allFiles = await input.options.getProjectFiles({
15
67
  projectId,
16
68
  authToken: input.context.authToken,
17
69
  branchId: input.context.branchId,
18
70
  });
19
- const references = new Set();
20
- for (const skillsPath of getSkillPaths(input.options)) {
21
- const skillPrefix = `${skillsPath}/${input.skillId}/`;
22
- const refsPrefix = `${skillPrefix}references/`;
23
- for (const file of allFiles) {
24
- if (!file.path.startsWith(refsPrefix)) {
25
- continue;
26
- }
27
- const relativePath = file.path.slice(skillPrefix.length);
28
- if (!relativePath.includes("/")) {
29
- continue;
30
- }
31
- const normalizedReference = normalizeRuntimeSkillReferencePath(relativePath);
32
- if (normalizedReference) {
33
- references.add(normalizedReference);
34
- }
35
- }
36
- }
37
- return [...references].sort();
71
+ return collectProjectSkillReferences({
72
+ allFiles,
73
+ skillsPath: source.skillsPath,
74
+ skillId: input.skillId,
75
+ });
38
76
  }
39
77
  async function loadProjectSkill(input) {
40
78
  const projectId = input.context.projectId;
@@ -52,7 +90,7 @@ async function loadProjectSkill(input) {
52
90
  if (directorySkill?.content) {
53
91
  return {
54
92
  instructions: directorySkill.content,
55
- references: await listProjectSkillReferences(input),
93
+ references: await listProjectSkillReferences({ ...input, skillsPath }),
56
94
  };
57
95
  }
58
96
  const flatSkill = await input.options.getProjectFile({
@@ -88,16 +126,18 @@ async function loadProjectSkillReference(input) {
88
126
  return null;
89
127
  }
90
128
  try {
91
- for (const skillsPath of getSkillPaths(input.options)) {
92
- const projectFile = await input.options.getProjectFile({
93
- projectId,
94
- authToken: input.context.authToken,
95
- branchId: input.context.branchId,
96
- path: `${skillsPath}/${input.skillId}/${input.normalizedFile}`,
97
- });
98
- if (projectFile?.content) {
99
- return projectFile.content;
100
- }
129
+ const source = await findProjectSkillSource(input);
130
+ if (source?.kind !== "directory") {
131
+ return null;
132
+ }
133
+ const projectFile = await input.options.getProjectFile({
134
+ projectId,
135
+ authToken: input.context.authToken,
136
+ branchId: input.context.branchId,
137
+ path: `${source.skillsPath}/${input.skillId}/${input.normalizedFile}`,
138
+ });
139
+ if (projectFile?.content) {
140
+ return projectFile.content;
101
141
  }
102
142
  }
103
143
  catch (error) {
@@ -122,7 +122,7 @@ function buildCreateRootRunBody(config, input) {
122
122
  },
123
123
  public_id: input.runId,
124
124
  request: {
125
- mode: "default_chat",
125
+ mode: "agent",
126
126
  agent_id: config.agentId,
127
127
  initial_status: "pending",
128
128
  ...buildCreateRootRunTargetFields(config),
@@ -138,7 +138,7 @@ function buildStartRunBody(config, input) {
138
138
  },
139
139
  public_id: input.runId,
140
140
  request: {
141
- mode: "default_chat",
141
+ mode: "agent",
142
142
  agent_id: config.agentId,
143
143
  input: {
144
144
  messages: [
@@ -5,7 +5,7 @@ export const connectors = [
5
5
  { "name": "aws", "displayName": "Amazon Web Services", "icon": "aws.svg", "description": "Integration with AWS services including S3, EC2, and Lambda", "auth": { "type": "api-key", "fields": [{ "name": "accessKeyId", "label": "AWS Access Key ID", "type": "string", "required": true, "envVar": "AWS_ACCESS_KEY_ID" }, { "name": "secretAccessKey", "label": "AWS Secret Access Key", "type": "password", "required": true, "envVar": "AWS_SECRET_ACCESS_KEY" }, { "name": "region", "label": "AWS Region", "type": "string", "required": true, "envVar": "AWS_REGION", "default": "us-east-1" }] }, "envVars": [{ "name": "AWS_ACCESS_KEY_ID", "description": "AWS Access Key ID", "required": true }, { "name": "AWS_SECRET_ACCESS_KEY", "description": "AWS Secret Access Key", "required": true, "sensitive": true }, { "name": "AWS_REGION", "description": "AWS Region (e.g. us-east-1)", "required": true, "default": "us-east-1" }], "tools": [{ "name": "list-s3-buckets", "description": "List all S3 buckets in your AWS account", "file": "tools/list-s3-buckets.ts" }, { "name": "list-s3-objects", "description": "List objects in a specific S3 bucket", "file": "tools/list-s3-objects.ts" }, { "name": "get-s3-object", "description": "Get the contents of an object from S3", "file": "tools/get-s3-object.ts" }, { "name": "list-ec2-instances", "description": "List EC2 instances in your AWS account", "file": "tools/list-ec2-instances.ts" }, { "name": "list-lambda-functions", "description": "List Lambda functions in your AWS account", "file": "tools/list-lambda-functions.ts" }], "dependencies": { "@aws-sdk/client-s3": "^3.600.0", "@aws-sdk/client-ec2": "^3.600.0", "@aws-sdk/client-lambda": "^3.600.0", "@aws-sdk/credential-providers": "^3.600.0" } },
6
6
  { "name": "bitbucket", "displayName": "Bitbucket", "icon": "bitbucket.svg", "description": "Manage repositories, pull requests, and issues on Bitbucket", "auth": { "type": "oauth2", "provider": "bitbucket", "authorizationUrl": "https://bitbucket.org/site/oauth2/authorize", "tokenUrl": "https://bitbucket.org/site/oauth2/access_token", "scopes": ["repository", "pullrequest", "issue", "account"] }, "envVars": [{ "name": "BITBUCKET_CLIENT_ID", "description": "Bitbucket OAuth Consumer Key", "required": true, "sensitive": false, "docsUrl": "https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/" }, { "name": "BITBUCKET_CLIENT_SECRET", "description": "Bitbucket OAuth Consumer Secret", "required": true, "sensitive": true, "docsUrl": "https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/" }], "tools": [{ "id": "list_repositories", "name": "List Repositories", "description": "Get list of user's repositories", "requiresWrite": false }, { "id": "list_pull_requests", "name": "List Pull Requests", "description": "Get pull requests for a repository", "requiresWrite": false }, { "id": "create_pull_request", "name": "Create Pull Request", "description": "Create a new pull request", "requiresWrite": true }, { "id": "list_issues", "name": "List Issues", "description": "Get issues for a repository", "requiresWrite": false }], "prompts": [{ "id": "review_prs", "title": "Review my pull requests", "prompt": "Show me my open pull requests on Bitbucket and help me review them. Summarize the changes and any comments.", "category": "development", "icon": "git-pull-request" }, { "id": "list_repos", "title": "List my repositories", "prompt": "Show me all my Bitbucket repositories with their details and recent activity.", "category": "development", "icon": "folder" }, { "id": "check_issues", "title": "Check repository issues", "prompt": "Show me the open issues in my repositories and help me prioritize them.", "category": "development", "icon": "bug" }], "suggestedWith": ["github", "gitlab", "jira"] },
7
7
  { "name": "calendar", "displayName": "Google Calendar", "icon": "calendar.svg", "description": "Manage events, find free time, and schedule meetings", "auth": { "type": "oauth2", "provider": "google", "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", "tokenUrl": "https://oauth2.googleapis.com/token", "scopes": ["https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/calendar.events"], "requiredApis": [{ "name": "Google Calendar API", "enableUrl": "https://console.cloud.google.com/apis/library/calendar-json.googleapis.com" }] }, "envVars": [{ "name": "GOOGLE_CLIENT_ID", "description": "Google OAuth Client ID", "required": true, "sensitive": false, "docsUrl": "https://console.cloud.google.com/apis/credentials" }, { "name": "GOOGLE_CLIENT_SECRET", "description": "Google OAuth Client Secret", "required": true, "sensitive": true, "docsUrl": "https://console.cloud.google.com/apis/credentials" }], "tools": [{ "id": "list_events", "name": "List Events", "description": "Get upcoming calendar events", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://www.googleapis.com/calendar/v3/calendars/{calendarId}/events", "params": { "calendarId": { "type": "string", "in": "path", "description": "Calendar ID (use 'primary' for main calendar)", "required": true, "default": "primary" }, "timeMin": { "type": "string", "in": "query", "description": "Start time (RFC3339)" }, "timeMax": { "type": "string", "in": "query", "description": "End time (RFC3339)" }, "maxResults": { "type": "number", "in": "query", "description": "Maximum events", "default": 10 }, "orderBy": { "type": "string", "in": "query", "description": "Order by: startTime or updated", "default": "startTime" }, "singleEvents": { "type": "boolean", "in": "query", "description": "Expand recurring events", "default": true } }, "response": { "transform": "items" } } }, { "id": "create_event", "name": "Create Event", "description": "Schedule a new calendar event", "requiresWrite": true, "endpoint": { "method": "POST", "url": "https://www.googleapis.com/calendar/v3/calendars/{calendarId}/events", "params": { "calendarId": { "type": "string", "in": "path", "description": "Calendar ID", "required": true, "default": "primary" } }, "body": { "summary": { "type": "string", "description": "Event title", "required": true }, "description": { "type": "string", "description": "Event description" }, "start": { "type": "object", "description": "Start time: {dateTime: 'RFC3339', timeZone: 'TZ'}", "required": true }, "end": { "type": "object", "description": "End time: {dateTime: 'RFC3339', timeZone: 'TZ'}", "required": true }, "attendees": { "type": "array", "description": "Array of {email: string} objects" }, "location": { "type": "string", "description": "Event location" } } } }, { "id": "get_event", "name": "Get Event", "description": "Get details of a specific calendar event", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://www.googleapis.com/calendar/v3/calendars/{calendarId}/events/{eventId}", "params": { "calendarId": { "type": "string", "in": "path", "description": "Calendar ID", "required": true, "default": "primary" }, "eventId": { "type": "string", "in": "path", "description": "Event ID", "required": true } } } }, { "id": "update_event", "name": "Update Event", "description": "Update an existing calendar event", "requiresWrite": true, "endpoint": { "method": "PATCH", "url": "https://www.googleapis.com/calendar/v3/calendars/{calendarId}/events/{eventId}", "params": { "calendarId": { "type": "string", "in": "path", "description": "Calendar ID", "required": true, "default": "primary" }, "eventId": { "type": "string", "in": "path", "description": "Event ID to update", "required": true }, "sendUpdates": { "type": "string", "in": "query", "description": "Whether to send update notifications: all, externalOnly, or none", "default": "none" } }, "body": { "summary": { "type": "string", "description": "Updated event title" }, "description": { "type": "string", "description": "Updated event description" }, "start": { "type": "object", "description": "Updated start time: {dateTime: 'RFC3339', timeZone: 'TZ'}" }, "end": { "type": "object", "description": "Updated end time: {dateTime: 'RFC3339', timeZone: 'TZ'}" }, "attendees": { "type": "array", "description": "Updated array of {email: string} attendees" }, "location": { "type": "string", "description": "Updated event location" } } } }, { "id": "delete_event", "name": "Delete Event", "description": "Delete a calendar event by ID", "requiresWrite": true, "endpoint": { "method": "DELETE", "url": "https://www.googleapis.com/calendar/v3/calendars/{calendarId}/events/{eventId}", "params": { "calendarId": { "type": "string", "in": "path", "description": "Calendar ID", "required": true, "default": "primary" }, "eventId": { "type": "string", "in": "path", "description": "Event ID to delete", "required": true }, "sendUpdates": { "type": "string", "in": "query", "description": "Whether to send cancellation notifications: all, externalOnly, or none", "default": "none" } } } }, { "id": "find_free_time", "name": "Find Free Time", "description": "Find available time slots in calendar", "requiresWrite": false, "endpoint": { "method": "POST", "url": "https://www.googleapis.com/calendar/v3/freeBusy", "body": { "timeMin": { "type": "string", "description": "Start of window (RFC3339)", "required": true }, "timeMax": { "type": "string", "description": "End of window (RFC3339)", "required": true }, "items": { "type": "array", "description": "Array of {id: calendarId} to check", "required": true } } } }], "prompts": [{ "id": "block_deep_work", "title": "Block time for deep work", "prompt": "Find a 2-hour block for focused work this week and add it to my calendar.", "category": "productivity", "icon": "clock" }, { "id": "schedule_meeting", "title": "Schedule a meeting", "prompt": "Help me schedule a meeting. Find available time slots and create the calendar event.", "category": "productivity", "icon": "users" }, { "id": "today_agenda", "title": "What's on my calendar today?", "prompt": "Show me my calendar for today and summarize my schedule.", "category": "productivity", "icon": "calendar" }], "suggestedWith": ["gmail", "slack"] },
8
- { "name": "confluence", "displayName": "Confluence", "icon": "confluence.svg", "description": "Search, read, and create documentation in Confluence", "auth": { "type": "oauth2", "provider": "atlassian", "authorizationUrl": "https://auth.atlassian.com/authorize", "tokenUrl": "https://auth.atlassian.com/oauth/token", "scopes": ["read:confluence-content.all", "write:confluence-content"], "tokenAuthMethod": "client_secret_post", "requiredApis": [{ "name": "Atlassian OAuth 2.0 App", "enableUrl": "https://developer.atlassian.com/console/myapps/" }], "additionalParams": { "audience": "api.atlassian.com", "prompt": "consent" }, "additionalAuthParams": { "audience": "api.atlassian.com", "prompt": "consent" } }, "envVars": [{ "name": "ATLASSIAN_CLIENT_ID", "description": "Atlassian OAuth Client ID (from your OAuth 2.0 app)", "required": true, "sensitive": false, "docsUrl": "https://developer.atlassian.com/console/myapps/" }, { "name": "ATLASSIAN_CLIENT_SECRET", "description": "Atlassian OAuth Client Secret", "required": true, "sensitive": true, "docsUrl": "https://developer.atlassian.com/console/myapps/" }], "tools": [{ "id": "list_sites", "name": "List Atlassian Sites", "description": "List Atlassian cloud sites/resources the OAuth token can access; use the returned id as cloudId for Jira and Confluence tools", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://api.atlassian.com/oauth/token/accessible-resources", "response": { "transform": "" } } }, { "id": "search_content", "name": "Search Confluence", "description": "Search for pages and blog posts in Confluence", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://api.atlassian.com/ex/confluence/{cloudId}/wiki/rest/api/content/search", "params": { "cloudId": { "type": "string", "in": "path", "description": "Atlassian cloud ID from accessible-resources", "required": true }, "cql": { "type": "string", "in": "query", "description": "Confluence Query Language expression", "required": true }, "limit": { "type": "number", "in": "query", "description": "Maximum results to return", "default": 25 }, "start": { "type": "number", "in": "query", "description": "Pagination offset", "default": 0 }, "expand": { "type": "string", "in": "query", "description": "Comma-separated expansions", "default": "space,version" } }, "response": { "transform": "results" } } }, { "id": "get_page", "name": "Get Page", "description": "Get the content of a specific Confluence page", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://api.atlassian.com/ex/confluence/{cloudId}/wiki/rest/api/content/{pageId}", "params": { "cloudId": { "type": "string", "in": "path", "description": "Atlassian cloud ID from accessible-resources", "required": true }, "pageId": { "type": "string", "in": "path", "description": "Confluence page ID", "required": true }, "expand": { "type": "string", "in": "query", "description": "Comma-separated expansions", "default": "body.storage,version" } } } }, { "id": "create_page", "name": "Create Page", "description": "Create a new page in a Confluence space", "requiresWrite": true, "endpoint": { "method": "POST", "url": "https://api.atlassian.com/ex/confluence/{cloudId}/wiki/rest/api/content", "params": { "cloudId": { "type": "string", "in": "path", "description": "Atlassian cloud ID from accessible-resources", "required": true } }, "body": { "type": { "type": "string", "description": "Confluence content type", "default": "page" }, "title": { "type": "string", "description": "Page title", "required": true }, "space": { "type": "object", "description": "Space object, e.g. {key: 'ENG'}", "required": true }, "ancestors": { "type": "array", "description": "Parent page references" }, "body": { "type": "object", "description": "Confluence body payload, typically storage representation", "required": true } } } }, { "id": "update_page", "name": "Update Page", "description": "Update the content of an existing Confluence page", "requiresWrite": true, "endpoint": { "method": "PUT", "url": "https://api.atlassian.com/ex/confluence/{cloudId}/wiki/rest/api/content/{pageId}", "params": { "cloudId": { "type": "string", "in": "path", "description": "Atlassian cloud ID from accessible-resources", "required": true }, "pageId": { "type": "string", "in": "path", "description": "Confluence page ID", "required": true } }, "body": { "type": { "type": "string", "description": "Confluence content type", "default": "page" }, "title": { "type": "string", "description": "Page title", "required": true }, "version": { "type": "object", "description": "Version object with incremented number", "required": true }, "body": { "type": "object", "description": "Updated Confluence body payload", "required": true } } } }, { "id": "list_spaces", "name": "List Spaces", "description": "List all accessible Confluence spaces", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://api.atlassian.com/ex/confluence/{cloudId}/wiki/rest/api/space", "params": { "cloudId": { "type": "string", "in": "path", "description": "Atlassian cloud ID from accessible-resources", "required": true }, "limit": { "type": "number", "in": "query", "description": "Maximum spaces to return", "default": 25 }, "start": { "type": "number", "in": "query", "description": "Pagination offset", "default": 0 } }, "response": { "transform": "results" } } }], "prompts": [{ "id": "search_docs", "title": "Search documentation", "prompt": "Search Confluence for documentation about a specific topic or feature.", "category": "productivity", "icon": "search" }, { "id": "summarize_page", "title": "Summarize a page", "prompt": "Read and summarize a Confluence page. Extract key information and action items.", "category": "productivity", "icon": "document" }, { "id": "create_doc", "title": "Create documentation", "prompt": "Create a new documentation page in Confluence with structured content.", "category": "productivity", "icon": "plus" }, { "id": "update_doc", "title": "Update documentation", "prompt": "Update an existing Confluence page with new information while preserving existing content.", "category": "productivity", "icon": "edit" }], "suggestedWith": ["jira", "slack", "notion"] },
8
+ { "name": "confluence", "displayName": "Confluence", "icon": "confluence.svg", "description": "Search, read, and create documentation in Confluence", "auth": { "type": "oauth2", "provider": "atlassian", "authorizationUrl": "https://auth.atlassian.com/authorize", "tokenUrl": "https://auth.atlassian.com/oauth/token", "scopes": ["read:confluence-content.all", "write:confluence-content"], "tokenAuthMethod": "client_secret_post", "requiredApis": [{ "name": "Atlassian OAuth 2.0 App", "enableUrl": "https://developer.atlassian.com/console/myapps/" }], "additionalParams": { "audience": "api.atlassian.com", "prompt": "consent" }, "additionalAuthParams": { "audience": "api.atlassian.com", "prompt": "consent" } }, "envVars": [{ "name": "ATLASSIAN_CLIENT_ID", "description": "Atlassian OAuth Client ID (from your OAuth 2.0 app)", "required": true, "sensitive": false, "docsUrl": "https://developer.atlassian.com/console/myapps/" }, { "name": "ATLASSIAN_CLIENT_SECRET", "description": "Atlassian OAuth Client Secret", "required": true, "sensitive": true, "docsUrl": "https://developer.atlassian.com/console/myapps/" }], "tools": [{ "id": "list_sites", "name": "List Atlassian Sites", "description": "List Atlassian cloud sites/resources the OAuth token can access; use the returned id as cloudId for Jira and Confluence tools", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://api.atlassian.com/oauth/token/accessible-resources", "response": { "transform": "" } } }, { "id": "search_content", "name": "Search Confluence", "description": "Search for pages and blog posts in Confluence", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://api.atlassian.com/ex/confluence/{cloudId}/wiki/rest/api/content/search", "params": { "cloudId": { "type": "string", "in": "path", "description": "Atlassian cloud ID from accessible-resources", "required": true }, "cql": { "type": "string", "in": "query", "description": "Confluence Query Language expression", "required": true }, "limit": { "type": "number", "in": "query", "description": "Maximum results to return", "default": 25 }, "start": { "type": "number", "in": "query", "description": "Pagination offset", "default": 0 }, "expand": { "type": "string", "in": "query", "description": "Comma-separated expansions", "default": "space,version" } }, "response": { "transform": "results" } } }, { "id": "get_page", "name": "Get Page", "description": "Get the content of a specific Confluence page (uses v2 API)", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://api.atlassian.com/ex/confluence/{cloudId}/wiki/api/v2/pages/{pageId}", "params": { "cloudId": { "type": "string", "in": "path", "description": "Atlassian cloud ID from accessible-resources", "required": true }, "pageId": { "type": "string", "in": "path", "description": "Confluence page ID", "required": true }, "body-format": { "type": "string", "in": "query", "description": "Body representation format", "default": "storage" } } } }, { "id": "create_page", "name": "Create Page", "description": "Create a new page in a Confluence space (uses v2 API; requires spaceId from list_spaces)", "requiresWrite": true, "endpoint": { "method": "POST", "url": "https://api.atlassian.com/ex/confluence/{cloudId}/wiki/api/v2/pages", "params": { "cloudId": { "type": "string", "in": "path", "description": "Atlassian cloud ID from accessible-resources", "required": true } }, "body": { "spaceId": { "type": "string", "description": "Numeric space ID (use list_spaces to get the id field)", "required": true }, "title": { "type": "string", "description": "Page title", "required": true }, "status": { "type": "string", "description": "Page status", "default": "current" }, "parentId": { "type": "string", "description": "Parent page ID (optional)" }, "body": { "type": "object", "description": "Page body, e.g. {representation: 'storage', value: '<p>content</p>'}", "required": true } } } }, { "id": "update_page", "name": "Update Page", "description": "Update the content of an existing Confluence page (uses v2 API; version.number must be current+1)", "requiresWrite": true, "endpoint": { "method": "PUT", "url": "https://api.atlassian.com/ex/confluence/{cloudId}/wiki/api/v2/pages/{pageId}", "params": { "cloudId": { "type": "string", "in": "path", "description": "Atlassian cloud ID from accessible-resources", "required": true }, "pageId": { "type": "string", "in": "path", "description": "Confluence page ID", "required": true } }, "body": { "id": { "type": "string", "description": "Page ID (must match pageId path param)", "required": true }, "status": { "type": "string", "description": "Page status", "default": "current" }, "title": { "type": "string", "description": "New page title (omit to keep existing)" }, "version": { "type": "object", "description": "Version object; number must be current version + 1", "required": true }, "body": { "type": "object", "description": "Updated body, e.g. {representation: 'storage', value: '<p>...</p>'}" } } } }, { "id": "list_spaces", "name": "List Spaces", "description": "List all accessible Confluence spaces", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://api.atlassian.com/ex/confluence/{cloudId}/wiki/rest/api/space", "params": { "cloudId": { "type": "string", "in": "path", "description": "Atlassian cloud ID from accessible-resources", "required": true }, "limit": { "type": "number", "in": "query", "description": "Maximum spaces to return", "default": 25 }, "start": { "type": "number", "in": "query", "description": "Pagination offset", "default": 0 } }, "response": { "transform": "results" } } }], "prompts": [{ "id": "search_docs", "title": "Search documentation", "prompt": "Search Confluence for documentation about a specific topic or feature.", "category": "productivity", "icon": "search" }, { "id": "summarize_page", "title": "Summarize a page", "prompt": "Read and summarize a Confluence page. Extract key information and action items.", "category": "productivity", "icon": "document" }, { "id": "create_doc", "title": "Create documentation", "prompt": "Create a new documentation page in Confluence with structured content.", "category": "productivity", "icon": "plus" }, { "id": "update_doc", "title": "Update documentation", "prompt": "Update an existing Confluence page with new information while preserving existing content.", "category": "productivity", "icon": "edit" }], "suggestedWith": ["jira", "slack", "notion"] },
9
9
  { "name": "discord", "displayName": "Discord", "icon": "discord.svg", "description": "Read messages, send messages, and interact with Discord servers", "auth": { "type": "oauth2", "provider": "discord", "authorizationUrl": "https://discord.com/api/oauth2/authorize", "tokenUrl": "https://discord.com/api/oauth2/token", "scopes": ["identify", "guilds", "guilds.members.read", "messages.read"], "tokenAuthMethod": "body", "requiredApis": [{ "name": "Discord Application", "enableUrl": "https://discord.com/developers/applications" }] }, "envVars": [{ "name": "DISCORD_CLIENT_ID", "description": "Discord OAuth Client ID (from your application)", "required": true, "sensitive": false, "docsUrl": "https://discord.com/developers/applications" }, { "name": "DISCORD_CLIENT_SECRET", "description": "Discord OAuth Client Secret", "required": true, "sensitive": true, "docsUrl": "https://discord.com/developers/applications" }, { "name": "DISCORD_BOT_TOKEN", "description": "Discord Bot Token (optional, for advanced bot features)", "required": false, "sensitive": true, "docsUrl": "https://discord.com/developers/applications" }], "tools": [{ "id": "list_guilds", "name": "List Guilds", "description": "List Discord servers (guilds) the user is a member of", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://discord.com/api/v10/users/@me/guilds", "params": { "limit": { "type": "number", "in": "query", "description": "Maximum guilds to return", "default": 100 }, "before": { "type": "string", "in": "query", "description": "Return guilds before this guild ID" }, "after": { "type": "string", "in": "query", "description": "Return guilds after this guild ID" } } } }, { "id": "list_channels", "name": "List Channels", "description": "List channels in a Discord server", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://discord.com/api/v10/guilds/{guildId}/channels", "params": { "guildId": { "type": "string", "in": "path", "description": "Discord guild/server ID", "required": true } }, "response": { "transform": "value" } } }, { "id": "get_messages", "name": "Get Messages", "description": "Get recent messages from a Discord channel", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://discord.com/api/v10/channels/{channelId}/messages", "params": { "channelId": { "type": "string", "in": "path", "description": "Discord channel ID", "required": true }, "limit": { "type": "number", "in": "query", "description": "Maximum messages to return", "default": 50 }, "before": { "type": "string", "in": "query", "description": "Only messages before this message ID" }, "after": { "type": "string", "in": "query", "description": "Only messages after this message ID" } } } }, { "id": "send_message", "name": "Send Message", "description": "Send a message to a Discord channel", "requiresWrite": true, "endpoint": { "method": "POST", "url": "https://discord.com/api/v10/channels/{channelId}/messages", "params": { "channelId": { "type": "string", "in": "path", "description": "Discord channel ID", "required": true } }, "body": { "content": { "type": "string", "description": "Message content, max 2000 characters", "required": true }, "tts": { "type": "boolean", "description": "Whether to send as text-to-speech", "default": false } } } }, { "id": "get_user", "name": "Get User", "description": "Get information about the authenticated Discord user", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://discord.com/api/v10/users/@me" } }], "prompts": [{ "id": "check_messages", "title": "Check my Discord messages", "prompt": "Check my recent Discord messages across all servers and summarize any important updates or mentions.", "category": "communication", "icon": "message" }, { "id": "send_announcement", "title": "Send an announcement", "prompt": "Send an announcement message to a specific Discord channel.", "category": "communication", "icon": "megaphone" }, { "id": "list_servers", "title": "List my servers", "prompt": "Show me all the Discord servers I'm a member of with their details.", "category": "communication", "icon": "list" }], "suggestedWith": ["slack", "github", "notion"] },
10
10
  { "name": "docs-google", "displayName": "Google Docs", "icon": "docs-google.svg", "description": "Read, create, and manage Google Docs documents", "auth": { "type": "oauth2", "provider": "google", "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", "tokenUrl": "https://oauth2.googleapis.com/token", "scopes": ["https://www.googleapis.com/auth/documents.readonly", "https://www.googleapis.com/auth/documents", "https://www.googleapis.com/auth/drive.readonly"], "requiredApis": [{ "name": "Google Docs API", "enableUrl": "https://console.cloud.google.com/apis/library/docs.googleapis.com" }, { "name": "Google Drive API", "enableUrl": "https://console.cloud.google.com/apis/library/drive.googleapis.com" }] }, "envVars": [{ "name": "GOOGLE_CLIENT_ID", "description": "Google OAuth Client ID", "required": true, "sensitive": false, "docsUrl": "https://console.cloud.google.com/apis/credentials" }, { "name": "GOOGLE_CLIENT_SECRET", "description": "Google OAuth Client Secret", "required": true, "sensitive": true, "docsUrl": "https://console.cloud.google.com/apis/credentials" }], "tools": [{ "id": "list_documents", "name": "List Documents", "description": "List recent Google Docs documents from Drive", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://www.googleapis.com/drive/v3/files", "params": { "q": { "type": "string", "in": "query", "description": "Drive query limited to Google Docs documents", "default": "mimeType='application/vnd.google-apps.document' and trashed=false" }, "pageSize": { "type": "number", "in": "query", "description": "Maximum number of documents to return", "default": 100 }, "pageToken": { "type": "string", "in": "query", "description": "Pagination token" }, "fields": { "type": "string", "in": "query", "description": "Partial response field selector", "default": "nextPageToken, files(id, name, webViewLink, modifiedTime)" } }, "response": { "transform": "files" } } }, { "id": "get_document", "name": "Get Document", "description": "Get document content and metadata", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://docs.googleapis.com/v1/documents/{documentId}", "params": { "documentId": { "type": "string", "in": "path", "description": "Google Docs document ID", "required": true }, "suggestionsViewMode": { "type": "string", "in": "query", "description": "Suggestions view mode to use when reading the document" } } } }, { "id": "create_document", "name": "Create Document", "description": "Create a new document with optional initial content", "requiresWrite": true, "endpoint": { "method": "POST", "url": "https://docs.googleapis.com/v1/documents", "body": { "title": { "type": "string", "description": "Document title", "required": true } } } }, { "id": "update_document", "name": "Update Document", "description": "Update document content using batch requests", "requiresWrite": true, "endpoint": { "method": "POST", "url": "https://docs.googleapis.com/v1/documents/{documentId}:batchUpdate", "params": { "documentId": { "type": "string", "in": "path", "description": "Google Docs document ID", "required": true } }, "body": { "requests": { "type": "array", "description": "Google Docs batchUpdate requests, e.g. insertText/updateTextStyle requests", "required": true }, "writeControl": { "type": "object", "description": "Optional Google Docs write control" } } } }, { "id": "search_documents", "name": "Search Documents", "description": "Search for documents by query string", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://www.googleapis.com/drive/v3/files", "params": { "q": { "type": "string", "in": "query", "description": "Drive query expression for Google Docs documents", "required": true }, "pageSize": { "type": "number", "in": "query", "description": "Maximum number of documents to return", "default": 100 }, "pageToken": { "type": "string", "in": "query", "description": "Pagination token" }, "fields": { "type": "string", "in": "query", "description": "Partial response field selector", "default": "nextPageToken, files(id, name, webViewLink, modifiedTime)" } }, "response": { "transform": "files" } } }], "prompts": [{ "id": "summarize_doc", "title": "Summarize a document", "prompt": "Read a Google Docs document and provide a concise summary of its contents, key points, and main themes.", "category": "productivity", "icon": "file-text" }, { "id": "create_report", "title": "Create a report document", "prompt": "Create a new Google Docs document with a well-formatted report including headings, bullet points, and structured content.", "category": "productivity", "icon": "plus" }, { "id": "edit_document", "title": "Edit a document", "prompt": "Update an existing Google Docs document with new content, formatting changes, or corrections.", "category": "productivity", "icon": "edit" }], "suggestedWith": ["gmail", "calendar", "drive", "sheets"] },
11
11
  { "name": "drive", "displayName": "Google Drive", "icon": "drive.svg", "description": "Access, search, and manage files and folders in Google Drive", "auth": { "type": "oauth2", "provider": "google", "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth", "tokenUrl": "https://oauth2.googleapis.com/token", "scopes": ["https://www.googleapis.com/auth/drive.readonly", "https://www.googleapis.com/auth/drive.file"], "requiredApis": [{ "name": "Google Drive API", "enableUrl": "https://console.cloud.google.com/apis/library/drive.googleapis.com" }] }, "envVars": [{ "name": "GOOGLE_CLIENT_ID", "description": "Google OAuth Client ID", "required": true, "sensitive": false, "docsUrl": "https://console.cloud.google.com/apis/credentials" }, { "name": "GOOGLE_CLIENT_SECRET", "description": "Google OAuth Client Secret", "required": true, "sensitive": true, "docsUrl": "https://console.cloud.google.com/apis/credentials" }], "tools": [{ "id": "list_files", "name": "List Files", "description": "List files and folders in a Google Drive folder or root", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://www.googleapis.com/drive/v3/files", "params": { "q": { "type": "string", "in": "query", "description": "Optional Drive query expression" }, "pageSize": { "type": "number", "in": "query", "description": "Maximum number of files to return", "default": 100 }, "pageToken": { "type": "string", "in": "query", "description": "Pagination token" }, "fields": { "type": "string", "in": "query", "description": "Partial response field selector", "default": "nextPageToken, files(id, name, mimeType, webViewLink, modifiedTime, size, parents)" } }, "response": { "transform": "files" } } }, { "id": "get_file", "name": "Get File", "description": "Get metadata and details about a specific file or folder", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://www.googleapis.com/drive/v3/files/{fileId}", "params": { "fileId": { "type": "string", "in": "path", "description": "Google Drive file ID", "required": true }, "fields": { "type": "string", "in": "query", "description": "Partial response field selector", "default": "id, name, mimeType, webViewLink, modifiedTime, size, parents" } } } }, { "id": "search_files", "name": "Search Files", "description": "Search for files and folders using queries", "requiresWrite": false, "endpoint": { "method": "GET", "url": "https://www.googleapis.com/drive/v3/files", "params": { "q": { "type": "string", "in": "query", "description": "Drive query expression used to search files", "required": true }, "pageSize": { "type": "number", "in": "query", "description": "Maximum number of files to return", "default": 100 }, "pageToken": { "type": "string", "in": "query", "description": "Pagination token" }, "fields": { "type": "string", "in": "query", "description": "Partial response field selector", "default": "nextPageToken, files(id, name, mimeType, webViewLink, modifiedTime, size, parents)" } }, "response": { "transform": "files" } } }, { "id": "create_folder", "name": "Create Folder", "description": "Create a new folder in Google Drive", "requiresWrite": true, "endpoint": { "method": "POST", "url": "https://www.googleapis.com/drive/v3/files", "body": { "name": { "type": "string", "description": "Folder name", "required": true }, "mimeType": { "type": "string", "description": "Google Drive MIME type for folders", "default": "application/vnd.google-apps.folder" }, "parents": { "type": "array", "description": "Optional parent folder IDs" } } } }, { "id": "upload_file", "name": "Upload File", "description": "Upload or create a file in Google Drive", "requiresWrite": true, "endpoint": { "method": "POST", "url": "https://www.googleapis.com/upload/drive/v3/files", "params": { "uploadType": { "type": "string", "in": "query", "description": "Google Drive upload mode", "default": "media" }, "fields": { "type": "string", "in": "query", "description": "Partial response field selector", "default": "id, name, mimeType, webViewLink, modifiedTime, size, parents" } }, "body": { "content": { "type": "string", "description": "Text content to upload", "required": true }, "mimeType": { "type": "string", "description": "Content MIME type", "default": "text/plain" }, "name": { "type": "string", "description": "Desired file name; use create_folder for folders", "required": false }, "parents": { "type": "array", "description": "Optional parent folder IDs" } } } }], "prompts": [{ "id": "organize_files", "title": "Organize Drive files", "prompt": "Help me organize files in Google Drive by creating folders and moving files based on file types or names.", "category": "productivity", "icon": "folder" }, { "id": "find_document", "title": "Find a document", "prompt": "Search Google Drive for a specific file or document by name, type, or content.", "category": "productivity", "icon": "search" }, { "id": "backup_files", "title": "Create backup structure", "prompt": "Create a backup folder structure in Google Drive and organize important files.", "category": "productivity", "icon": "upload" }], "suggestedWith": ["gmail", "calendar", "sheets"] },
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/src/tool/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,KAAK,EAAE,UAAU,EAAU,MAAM,+BAA+B,CAAC;AAyJxE,sCAAsC;AACtC,wBAAgB,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,EACtD,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAqCvB;AAED,0CAA0C;AAC1C,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,UAAU,CAAC;IAC7B,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxF,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAC7C,GAAG,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;CACzB;AAED,wCAAwC;AACxC,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CA4B7E"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/src/tool/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,KAAK,EAAE,UAAU,EAAU,MAAM,+BAA+B,CAAC;AAyJxE,sCAAsC;AACtC,wBAAgB,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,EACtD,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CA+CvB;AAED,0CAA0C;AAC1C,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,UAAU,CAAC;IAC7B,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxF,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAC7C,GAAG,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;CACzB;AAED,wCAAwC;AACxC,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CA4B7E"}
@@ -90,12 +90,17 @@ export function tool(config) {
90
90
  const explicitId = typeof config.id === "string" && config.id.length > 0 ? config.id : undefined;
91
91
  const id = explicitId ?? generateToolId();
92
92
  const inputSchemaJson = convertSchemaToJson(config.inputSchema, id, "TOOL", config.allowUnknownSchema ?? false);
93
+ const outputSchemaJson = config.outputSchema
94
+ ? convertSchemaToJson(config.outputSchema, id, "TOOL_OUTPUT", config.allowUnknownSchema ?? false)
95
+ : undefined;
93
96
  const createdTool = {
94
97
  id,
95
98
  type: "function",
96
99
  description: config.description,
97
100
  inputSchema: config.inputSchema,
98
101
  inputSchemaJson,
102
+ outputSchema: config.outputSchema,
103
+ outputSchemaJson,
99
104
  execute: async (input, context) => {
100
105
  if (hasSchemaParse(config.inputSchema)) {
101
106
  try {
@@ -18,6 +18,11 @@ export interface ToolConfig<TInput = any, TOutput = any> {
18
18
  * and seeds the JSON Schema exposed to AI providers.
19
19
  */
20
20
  inputSchema: Schema<TInput>;
21
+ /**
22
+ * Optional output schema. Hosts can use this to document or validate
23
+ * structured tool results.
24
+ */
25
+ outputSchema?: Schema<TOutput>;
21
26
  /**
22
27
  * Allow unknown/non-contract schemas to fall back to a permissive JSON
23
28
  * schema. Use only for truly dynamic tools; prefer `v.unknown()` or
@@ -111,6 +116,10 @@ export interface Tool<TInput = any, TOutput = any> {
111
116
  * This is generated at tool creation time to avoid bundling issues
112
117
  */
113
118
  inputSchemaJson?: JsonSchema;
119
+ /** Optional pre-converted JSON Schema for tool outputs. */
120
+ outputSchemaJson?: JsonSchema;
121
+ /** Optional output schema produced by `defineSchema`. */
122
+ outputSchema?: Schema<TOutput>;
114
123
  /**
115
124
  * Execute the tool
116
125
  */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/src/tool/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/src/tool/types.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  /** Shared version value. */
2
- export declare const VERSION = "0.1.560";
2
+ export declare const VERSION = "0.1.562";
3
3
  //# sourceMappingURL=version-constant.d.ts.map
@@ -1,4 +1,4 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
3
  /** Shared version value. */
4
- export const VERSION = "0.1.560";
4
+ export const VERSION = "0.1.562";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.560",
3
+ "version": "0.1.562",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",