veryfront 0.1.565 → 0.1.566

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.
@@ -13,6 +13,7 @@ export interface ProjectSourceExecutionContext {
13
13
  projectId?: string;
14
14
  proxyContext?: ProxyProjectSourceContext;
15
15
  }
16
+ export declare function getProxyProjectSourceContext(): ProxyProjectSourceContext | null;
16
17
  export declare function withProjectSourceContext<T>(projectDir: string, run: (context: ProjectSourceExecutionContext) => Promise<T>): Promise<T>;
17
18
  export {};
18
19
  //# sourceMappingURL=project-source-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-source-context.d.ts","sourceRoot":"","sources":["../../../src/cli/shared/project-source-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5E,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,6BAA6B,CAAC;AAErC,UAAU,yBAAyB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,yBAAyB,CAAC;CAC1C;AA8BD,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,CAAC,OAAO,EAAE,6BAA6B,KAAK,OAAO,CAAC,CAAC,CAAC,GAC1D,OAAO,CAAC,CAAC,CAAC,CAwCZ"}
1
+ {"version":3,"file":"project-source-context.d.ts","sourceRoot":"","sources":["../../../src/cli/shared/project-source-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5E,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,6BAA6B,CAAC;AAErC,UAAU,yBAAyB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,yBAAyB,CAAC;CAC1C;AAED,wBAAgB,4BAA4B,IAAI,yBAAyB,GAAG,IAAI,CAkB/E;AAWD,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,CAAC,OAAO,EAAE,6BAA6B,KAAK,OAAO,CAAC,CAAC,CAAC,GAC1D,OAAO,CAAC,CAAC,CAAC,CAwCZ"}
@@ -1,13 +1,14 @@
1
1
  import { getConfig } from "../../src/config/index.js";
2
2
  import { enhanceAdapterWithFS, getEnv, isExtendedFSAdapter, runtime, } from "../../src/platform/index.js";
3
- function getProxyProjectSourceContext() {
3
+ export function getProxyProjectSourceContext() {
4
4
  const projectSlug = getEnv("VERYFRONT_PROJECT_SLUG")?.trim();
5
5
  const token = getEnv("VERYFRONT_API_TOKEN")?.trim();
6
6
  if (!projectSlug || !token) {
7
7
  return null;
8
8
  }
9
9
  const projectId = getEnv("VERYFRONT_PROJECT_ID")?.trim();
10
- const branchRef = getEnv("VERYFRONT_BRANCH_REF")?.trim();
10
+ const branchRef = getEnv("VERYFRONT_BRANCH_REF")?.trim() ||
11
+ getEnv("TENANT_BRANCH_ID")?.trim();
11
12
  return {
12
13
  projectSlug,
13
14
  token,
@@ -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\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",
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 type ConfluencePageType = \"page\" | \"blogpost\";\n\nexport interface ConfluencePage {\n id: string;\n type?: ConfluencePageType;\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\nexport class ConfluenceApiError extends Error {\n constructor(public readonly status: number, message: string) {\n super(message);\n this.name = \"ConfluenceApiError\";\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 ConfluenceApiError(\n response.status,\n `Confluence API error: ${response.status} ${error.message ?? response.statusText}`,\n );\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\n// Uses Confluence v2 — v1 /wiki/rest/api/space is deprecated alongside /content.\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/api/v2/spaces\", params),\n );\n\n return response.results ?? [];\n}\n\n// Direct key lookup via v2 — avoids the v1 enumeration trap that capped at 250 spaces\n// and silently failed on enterprise tenancies with hundreds of spaces.\nasync function getSpaceIdByKey(spaceKey: string): Promise<string> {\n const params = new URLSearchParams();\n params.set(\"keys\", spaceKey);\n params.set(\"limit\", \"1\");\n\n const response = await confluenceFetch<ConfluenceResponse<ConfluenceSpace>>(\n buildEndpoint(\"/wiki/api/v2/spaces\", params),\n );\n\n const space = response.results?.[0];\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// v2 splits pages and blogposts into separate resources. Try /pages first;\n// fall back to /blogposts on 404 so search-content get-page works for both\n// (searchContent returns mixed results and tools/get-page.ts has no type discriminator).\nexport async function getPage(pageId: string): Promise<ConfluencePage> {\n try {\n return await confluenceFetch<ConfluencePage>(\n `/wiki/api/v2/pages/${pageId}?body-format=storage`,\n );\n } catch (error) {\n if (error instanceof ConfluenceApiError && error.status === 404) {\n return await confluenceFetch<ConfluencePage>(\n `/wiki/api/v2/blogposts/${pageId}?body-format=storage`,\n );\n }\n throw error;\n }\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?: ConfluencePageType;\n}): Promise<ConfluencePage> {\n const spaceId = await getSpaceIdByKey(options.spaceKey);\n const type: ConfluencePageType = options.type ?? \"page\";\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 (type === \"blogpost\") {\n // v2 blogposts cannot have a parent — surface the user error instead of dropping it silently.\n if (options.parentId) {\n throw new Error(\"Confluence blogposts cannot have a parentId\");\n }\n return confluenceFetch<ConfluencePage>(\"/wiki/api/v2/blogposts\", {\n method: \"POST\",\n body: JSON.stringify(body),\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\n// v2 PUT /pages/{id} is a full replace, not PATCH — title and body are both required.\n// Callers must resolve fallbacks (e.g. from a prior getPage) before invoking this.\n// Mirrors getPage's pages-then-blogposts fallback so update-page works on either resource\n// (createPage with type='blogpost' returns a blogpost id that this must accept).\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 const body = {\n id: pageId,\n status: \"current\",\n title: options.title,\n body: {\n representation: \"storage\",\n value: options.content,\n },\n version: {\n number: options.version,\n message: options.versionMessage,\n },\n };\n\n const init: RequestInit = {\n method: \"PUT\",\n body: JSON.stringify(body),\n };\n\n try {\n return await confluenceFetch<ConfluencePage>(`/wiki/api/v2/pages/${pageId}`, init);\n } catch (error) {\n if (error instanceof ConfluenceApiError && error.status === 404) {\n return await confluenceFetch<ConfluencePage>(`/wiki/api/v2/blogposts/${pageId}`, init);\n }\n throw error;\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
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
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 ?? \"page\",\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 || !content.trim()) return undefined;\n\n const trimmed = content.trim();\n if (trimmed.startsWith(\"<\")) return content;\n\n return formatAsStorage(content);\n}\n\nfunction nonEmpty(value: string | undefined): string | undefined {\n return value && value.trim() ? value : undefined;\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 // v2 PUT /pages/{id} is a full replace — both title and body must be sent on every\n // update. Resolve missing fields from the current page so partial updates work.\n // The schema describes empty values as \"keep current\", so treat empty/whitespace\n // strings as unset (??-fallback would otherwise let \"\" overwrite a real title).\n const currentPage = await getPage(pageId);\n const storageContent = toStorageContent(content);\n const currentBody = currentPage.body?.storage?.value ?? \"\";\n\n const updatedPage = await updatePage(pageId, {\n title: nonEmpty(title) ?? currentPage.title,\n content: storageContent ?? currentBody,\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.565",
3
+ "version": "0.1.566",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "workspace": [
@@ -1,3 +1,3 @@
1
1
  /** Shared version value. */
2
- export declare const VERSION = "0.1.565";
2
+ export declare const VERSION = "0.1.566";
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.565";
4
+ export const VERSION = "0.1.566";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.565",
3
+ "version": "0.1.566",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",