diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3397a0a..59fc92f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,7 +75,7 @@ jobs: URL: https://pkg.stainless.com/s?subpackage=mcp-server AUTH: ${{ steps.github-oidc.outputs.github_token }} SHA: ${{ github.sha }} - BUILD_PATH: packages/mcp-server/dist + BASE_PATH: packages/mcp-server run: ./scripts/utils/upload-artifact.sh test: timeout-minutes: 10 diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 6ab8894..b9eedaa 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -16,6 +16,8 @@ jobs: publish: name: publish runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: actions/checkout@v4 @@ -39,3 +41,10 @@ jobs: yarn tsn scripts/publish-packages.ts "{ \"paths_released\": \"$PATHS_RELEASED\" }" env: NPM_TOKEN: ${{ secrets.BEEPER_DESKTOP_NPM_TOKEN || secrets.NPM_TOKEN }} + + - name: Upload MCP Server DXT GitHub release asset + run: | + gh release upload ${{ github.event.release.tag_name }} \ + packages/mcp-server/beeper_desktop_api_api.mcpb + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index d98d51a..74cba89 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ dist dist-deno /*.tgz .idea/ - +dist-bundle +*.mcpb diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 88f7c73..18e45d5 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.4" + ".": "0.1.5" } diff --git a/.stats.yml b/.stats.yml index 6f44d46..fc8ce6a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 11 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/beeper%2Fbeeper-desktop-api-097cbf5de83619d0b40ba59377494db32816f2a5369d4e986a94568f3d79c7c8.yml -openapi_spec_hash: cbb791aa1e4b75740911af7a0d93529d -config_hash: 35aeee58f2bef9a9f5354e4c8504e26e +configured_endpoints: 14 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/beeper%2Fbeeper-desktop-api-8c712fe19f280b0b89ecc8a3ce61e9f6b165cee97ce33f66c66a7a5db339c755.yml +openapi_spec_hash: 1ea71129cc1a1ccc3dc8a99566082311 +config_hash: 061b75b88f80bb43b4121e5e7c1255e2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8008f60..9667165 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # Changelog +## 0.1.5 (2025-09-19) + +Full Changelog: [v0.1.4...v0.1.5](https://github.com/beeper/desktop-api-js/compare/v0.1.4...v0.1.5) + +### Features + +* **api:** fix typo in bearerAuth ([76ac2e1](https://github.com/beeper/desktop-api-js/commit/76ac2e16ba6422053a24de98270016f90abe77a7)) +* **api:** manual updates ([3c711d9](https://github.com/beeper/desktop-api-js/commit/3c711d9190aed783a3c80c994bdc1a44107cd1be)) +* **api:** manual updates ([6f43810](https://github.com/beeper/desktop-api-js/commit/6f43810bf528676498df6ee81380fdf181ef5363)) +* **api:** manual updates ([2bc9e1b](https://github.com/beeper/desktop-api-js/commit/2bc9e1b7b6006cf9341b0bbf7ad5fba6ae482822)) +* **api:** manual updates ([49f039b](https://github.com/beeper/desktop-api-js/commit/49f039bbfb9080e425ea0756bdab767107de711d)) +* **api:** small tweaks ([ab7267b](https://github.com/beeper/desktop-api-js/commit/ab7267b120d2f009050bf8944cb43f6a27631de5)) +* **mcp:** add docs search tool ([d90bd86](https://github.com/beeper/desktop-api-js/commit/d90bd86e672737b397e15d388f4e9ad1a8bd43e3)) +* **mcp:** allow setting logging level ([6f2169f](https://github.com/beeper/desktop-api-js/commit/6f2169fd4bb1aaf12dbc7765c304fc9b722b0c32)) +* **mcp:** expose client options in `streamableHTTPApp` ([9afd751](https://github.com/beeper/desktop-api-js/commit/9afd7510b1c96fe5624d1f37f84521c1be1a6353)) + + +### Bug Fixes + +* **ci:** set permissions for DXT publish action ([5a6b63d](https://github.com/beeper/desktop-api-js/commit/5a6b63d7d7ba2514901ff5524890294feeaa981c)) +* coerce nullable values to undefined ([f43ac68](https://github.com/beeper/desktop-api-js/commit/f43ac6875858be2d27c31cbe7acc3d1fbac76c40)) +* **mcp:** fix query options parsing ([4f00241](https://github.com/beeper/desktop-api-js/commit/4f00241e35309ff7441fd842ba787a87caf17cae)) +* **mcp:** fix uploading dxt release assets ([1189099](https://github.com/beeper/desktop-api-js/commit/118909952860f420b4bc3f63b3de7e31d486c967)) + + +### Chores + +* ci build action ([b63fda2](https://github.com/beeper/desktop-api-js/commit/b63fda25086cfcc8758df9b5fcb32b49a78df28c)) +* **internal:** codegen related update ([b244e26](https://github.com/beeper/desktop-api-js/commit/b244e2647d3fd792e3764127ebdd8dc0c0f6df7f)) +* **internal:** codegen related update ([b961076](https://github.com/beeper/desktop-api-js/commit/b961076bf7533638f00eebb1b2a81508db35ad7b)) +* **internal:** codegen related update ([dd44c80](https://github.com/beeper/desktop-api-js/commit/dd44c803559a85fe7bc1f34fe7a5c4a356ec3460)) +* **internal:** gitignore .mcpb files ([5a8ade3](https://github.com/beeper/desktop-api-js/commit/5a8ade3b8c2fff3764893114e5eb862604fc5c6b)) +* **mcp:** rename dxt to mcpb ([343d030](https://github.com/beeper/desktop-api-js/commit/343d0303c26c103a763be49e7319bee88aa71b7e)) +* **mcp:** upload dxt as release asset ([55f6005](https://github.com/beeper/desktop-api-js/commit/55f6005d92b7f26a46c49ba76f17f7ea34b8c4b9)) + ## 0.1.4 (2025-09-01) Full Changelog: [v0.1.3...v0.1.4](https://github.com/beeper/desktop-api-js/compare/v0.1.3...v0.1.4) diff --git a/api.md b/api.md index 825f9d1..54b94fd 100644 --- a/api.md +++ b/api.md @@ -24,20 +24,36 @@ Methods: Types: +- AppDownloadAssetResponse - AppOpenResponse +- AppSearchResponse Methods: +- client.app.downloadAsset({ ...params }) -> AppDownloadAssetResponse - client.app.open({ ...params }) -> AppOpenResponse +- client.app.search({ ...params }) -> AppSearchResponse + +# Contacts + +Types: + +- ContactSearchResponse + +Methods: + +- client.contacts.search({ ...params }) -> ContactSearchResponse # Chats Types: - Chat +- ChatCreateResponse Methods: +- client.chats.create({ ...params }) -> ChatCreateResponse - client.chats.retrieve({ ...params }) -> Chat - client.chats.archive({ ...params }) -> BaseResponse - client.chats.search({ ...params }) -> ChatsCursor @@ -53,22 +69,12 @@ Methods: Types: -- MessageSendResponse - -Methods: - -- client.messages.search({ ...params }) -> MessagesCursor -- client.messages.send({ ...params }) -> MessageSendResponse - -## Attachments - -Types: - -- AttachmentDownloadResponse +- MessageSendResponse Methods: -- client.messages.attachments.download({ ...params }) -> AttachmentDownloadResponse +- client.messages.search({ ...params }) -> MessagesCursor +- client.messages.send({ ...params }) -> MessageSendResponse # Token diff --git a/package.json b/package.json index c5faeaf..bf401d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@beeper/desktop-api", - "version": "0.1.4", + "version": "0.1.5", "description": "The official TypeScript library for the Beeper Desktop API", "author": "Beeper Desktop ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 7d51cbc..2ab4e18 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -218,13 +218,14 @@ The following tools are available in this MCP server. ### Resource `app`: -- `open_in_app` (`write`) tags: [app]: Open Beeper, optionally focusing a chat or message, or pre-filling a draft. +- `open_in_app` (`write`) tags: [app]: Open Beeper Desktop and optionally navigate to a specific chat, message, or pre-fill draft text and attachment. +- `search` (`read`) tags: [app]: Search for chats, participant name matches in groups, and the first page of messages in one call. Use this when the user asks for a specific chat, group, or person. ### Resource `chats`: - `get_chat` (`read`) tags: [chats]: Get chat details: metadata, participants (limited), last activity. - `archive_chat` (`write`) tags: [chats]: Archive or unarchive a chat. -- `search_chats` (`read`) tags: [chats]: Search chats by inbox, type, unread status, or text. Paginates. +- `search_chats` (`read`) tags: [chats]: Search chats by title/network or participants using Beeper Desktop's renderer algorithm. Optional 'scope'. ### Resource `chats.reminders`: @@ -239,17 +240,11 @@ The following tools are available in this MCP server. • ✅ RIGHT: query="dinner" or query="sick" or query="error" (single words users type) • ❌ WRONG: query="dinner plans tonight" or query="health issues" (phrases/concepts) • The query matches ALL words provided (in any order). Example: query="flight booking" finds messages with both "flight" AND "booking". - - Media filters: Use onlyWithMedia for any media, or specific filters like onlyWithVideo, onlyWithImage, onlyWithLink, onlyWithFile for specific types. - - Pagination: use 'oldestCursor' + direction='before' for older; 'newestCursor' + direction='after' for newer. - Performance: provide chatIDs/accountIDs when known. Omitted 'query' returns results based on filters only. Partial matches enabled; 'excludeLowPriority' defaults to true. - Workflow tip: To search messages in specific conversations: 1) Use find-chats to get chatIDs, 2) Use search-messages with those chatIDs. - IMPORTANT: Chat names vary widely. ASK the user for clarification: • "Which chat do you mean by family?" (could be "The Smiths", "Mom Dad Kids", etc.) • "What's the name of your work chat?" (could be "Team", company name, project name) - • "Who are the participants?" (use participantQuery in find-chats) + • "Who are the participants?" (use scope="participants" in search-chats) Returns: matching messages and referenced chats. - `send_message` (`write`) tags: [messages]: Send a text message to a specific chat. Supports replying to existing messages. Returns the sent message ID and a deeplink to the chat - -### Resource `messages.attachments`: - -- `download_attachment` (`write`) tags: [messages]: Download a message attachment and return the local file path. diff --git a/packages/mcp-server/build b/packages/mcp-server/build index ce3047d..b94538a 100644 --- a/packages/mcp-server/build +++ b/packages/mcp-server/build @@ -30,3 +30,27 @@ cp tsconfig.dist-src.json dist/src/tsconfig.json chmod +x dist/index.js DIST_PATH=./dist PKG_IMPORT_PATH=@beeper/desktop-mcp/ node ../../scripts/utils/postprocess-files.cjs + +# mcp bundle +rm -rf dist-bundle beeper_desktop_api_api.mcpb; mkdir dist-bundle + +# copy package.json +PKG_JSON_PATH=../../packages/mcp-server/package.json node ../../scripts/utils/make-dist-package-json.cjs > dist-bundle/package.json + +# copy files +node scripts/copy-bundle-files.cjs + +# install runtime deps +cd dist-bundle +npm install +cd .. + +# pack bundle +cp manifest.json dist-bundle + +npx mcpb pack dist-bundle beeper_desktop_api_api.mcpb + +npx mcpb sign beeper_desktop_api_api.mcpb --self-signed + +# clean up +rm -rf dist-bundle diff --git a/packages/mcp-server/manifest.json b/packages/mcp-server/manifest.json new file mode 100644 index 0000000..09047c8 --- /dev/null +++ b/packages/mcp-server/manifest.json @@ -0,0 +1,43 @@ +{ + "dxt_version": "0.2", + "name": "@beeper/desktop-mcp", + "version": "0.1.4", + "description": "The official MCP Server for the Beeper Desktop API", + "author": { + "name": "Beeper Desktop", + "email": "help@beeper.com" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/beeper/desktop-api-js.git" + }, + "homepage": "https://github.com/beeper/desktop-api-js/tree/main/packages/mcp-server#readme", + "documentation": "https://developers.beeper.com/desktop-api/", + "server": { + "type": "node", + "entry_point": "index.js", + "mcp_config": { + "command": "node", + "args": ["${__dirname}/index.js"], + "env": { + "BEEPER_ACCESS_TOKEN": "${user_config.BEEPER_ACCESS_TOKEN}" + } + } + }, + "user_config": { + "BEEPER_ACCESS_TOKEN": { + "title": "access_token", + "description": "Bearer access token obtained via OAuth2 PKCE flow or created in-app. Required for all API operations.", + "required": true, + "type": "string" + } + }, + "tools": [], + "tools_generated": true, + "compatibility": { + "runtimes": { + "node": ">=18.0.0" + } + }, + "keywords": ["api"] +} diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index ac00c70..b9510db 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "@beeper/desktop-mcp", - "version": "0.1.4", + "version": "0.1.5", "description": "The official MCP Server for the Beeper Desktop API", "author": "Beeper Desktop ", "types": "dist/index.d.ts", @@ -47,6 +47,7 @@ "mcp-server": "dist/index.js" }, "devDependencies": { + "@anthropic-ai/mcpb": "^1.1.0", "@types/cors": "^2.8.19", "@types/express": "^5.0.3", "@types/jest": "^29.4.0", diff --git a/packages/mcp-server/scripts/copy-bundle-files.cjs b/packages/mcp-server/scripts/copy-bundle-files.cjs new file mode 100644 index 0000000..8a1188b --- /dev/null +++ b/packages/mcp-server/scripts/copy-bundle-files.cjs @@ -0,0 +1,36 @@ +const fs = require('fs'); +const path = require('path'); +const pkgJson = require('../dist-bundle/package.json'); + +const distDir = path.resolve(__dirname, '..', 'dist'); +const distBundleDir = path.resolve(__dirname, '..', 'dist-bundle'); +const distBundlePkgJson = path.join(distBundleDir, 'package.json'); + +async function* walk(dir) { + for await (const d of await fs.promises.opendir(dir)) { + const entry = path.join(dir, d.name); + if (d.isDirectory()) yield* walk(entry); + else if (d.isFile()) yield entry; + } +} + +async function copyFiles() { + // copy runtime files + for await (const file of walk(distDir)) { + if (!/[cm]?js$/.test(file)) continue; + const dest = path.join(distBundleDir, path.relative(distDir, file)); + await fs.promises.mkdir(path.dirname(dest), { recursive: true }); + await fs.promises.copyFile(file, dest); + } + + // replace package.json reference with local reference + for (const dep in pkgJson.dependencies) { + if (dep === '@beeper/desktop-api') { + pkgJson.dependencies[dep] = 'file:../../../dist/'; + } + } + + await fs.promises.writeFile(distBundlePkgJson, JSON.stringify(pkgJson, null, 2)); +} + +copyFiles(); diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index baa106d..8339908 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -7,9 +7,7 @@ import { Endpoint, ContentBlock, Metadata } from './tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import { newDenoHTTPWorker } from '@valtown/deno-http-worker'; import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types'; -import { workerPath } from './code-tool-paths.cjs'; /** * A tool that runs code against a copy of the SDK. @@ -20,7 +18,7 @@ import { workerPath } from './code-tool-paths.cjs'; * * @param endpoints - The endpoints to include in the list. */ -export function codeTool(): Endpoint { +export async function codeTool(): Promise { const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] }; const tool: Tool = { name: 'execute', @@ -29,6 +27,10 @@ export function codeTool(): Endpoint { inputSchema: { type: 'object', properties: { code: { type: 'string' } } }, }; + // Import dynamically to avoid failing at import time in cases where the environment is not well-supported. + const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker'); + const { workerPath } = await import('./code-tool-paths.cjs'); + const handler = async (client: BeeperDesktop, args: unknown) => { const baseURLHostname = new URL(client.baseURL).hostname; const { code } = args as { code: string }; diff --git a/packages/mcp-server/src/http.ts b/packages/mcp-server/src/http.ts index 3165f91..91f0c84 100644 --- a/packages/mcp-server/src/http.ts +++ b/packages/mcp-server/src/http.ts @@ -7,7 +7,7 @@ import cors from 'cors'; import express from 'express'; import { fromError } from 'zod-validation-error/v3'; import { McpOptions, parseQueryOptions } from './options'; -import { initMcpServer, newMcpServer } from './server'; +import { ClientOptions, initMcpServer, newMcpServer } from './server'; import { parseAuthHeaders } from './headers'; const oauthResourceIdentifier = (req: express.Request): string => { @@ -15,11 +15,17 @@ const oauthResourceIdentifier = (req: express.Request): string => { return `${protocol}://${req.get('host')}/`; }; -const newServer = ( - defaultMcpOptions: McpOptions, - req: express.Request, - res: express.Response, -): McpServer | null => { +const newServer = ({ + clientOptions, + mcpOptions: defaultMcpOptions, + req, + res, +}: { + clientOptions: ClientOptions; + mcpOptions: McpOptions; + req: express.Request; + res: express.Response; +}): McpServer | null => { const server = newMcpServer(); let mcpOptions: McpOptions; @@ -41,10 +47,8 @@ const newServer = ( initMcpServer({ server: server, clientOptions: { + ...clientOptions, ...authOptions, - defaultHeaders: { - 'X-Stainless-MCP': 'true', - }, }, mcpOptions, }); @@ -67,17 +71,19 @@ const newServer = ( return server; }; -const post = (defaultOptions: McpOptions) => async (req: express.Request, res: express.Response) => { - const server = newServer(defaultOptions, req, res); - // If we return null, we already set the authorization error. - if (server === null) return; - const transport = new StreamableHTTPServerTransport({ - // Stateless server - sessionIdGenerator: undefined, - }); - await server.connect(transport); - await transport.handleRequest(req, res, req.body); -}; +const post = + (options: { clientOptions: ClientOptions; mcpOptions: McpOptions }) => + async (req: express.Request, res: express.Response) => { + const server = newServer({ ...options, req, res }); + // If we return null, we already set the authorization error. + if (server === null) return; + const transport = new StreamableHTTPServerTransport({ + // Stateless server + sessionIdGenerator: undefined, + }); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + }; const get = async (req: express.Request, res: express.Response) => { res.status(405).json({ @@ -109,21 +115,27 @@ const oauthMetadata = (req: express.Request, res: express.Response) => { }); }; -export const streamableHTTPApp = (options: McpOptions): express.Express => { +export const streamableHTTPApp = ({ + clientOptions = {}, + mcpOptions = {}, +}: { + clientOptions?: ClientOptions; + mcpOptions?: McpOptions; +}): express.Express => { const app = express(); app.set('query parser', 'extended'); app.use(express.json()); app.get('/.well-known/oauth-protected-resource', cors(), oauthMetadata); app.get('/', get); - app.post('/', post(options)); + app.post('/', post({ clientOptions, mcpOptions })); app.delete('/', del); return app; }; export const launchStreamableHTTPServer = async (options: McpOptions, port: number | string | undefined) => { - const app = streamableHTTPApp(options); + const app = streamableHTTPApp({ mcpOptions: options }); const server = app.listen(port); const address = server.address(); diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index c450e4b..4850a0e 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -14,7 +14,7 @@ async function main() { return; } - const selectedTools = selectToolsOrError(endpoints, options); + const selectedTools = await selectToolsOrError(endpoints, options); console.error( `MCP Server starting with ${selectedTools.length} tools:`, @@ -47,9 +47,9 @@ function parseOptionsOrError() { } } -function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): Endpoint[] { +async function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): Promise { try { - const includedTools = selectTools(endpoints, options); + const includedTools = await selectTools(endpoints, options); if (includedTools.length === 0) { console.error('No tools match the provided filters.'); process.exit(1); diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts index 2100cf5..ecc9f10 100644 --- a/packages/mcp-server/src/options.ts +++ b/packages/mcp-server/src/options.ts @@ -367,12 +367,12 @@ export function parseQueryOptions(defaultOptions: McpOptions, query: unknown): M } let dynamicTools: boolean | undefined = - queryOptions.no_tools && !queryOptions.no_tools?.includes('dynamic') ? false + queryOptions.no_tools && queryOptions.no_tools?.includes('dynamic') ? false : queryOptions.tools?.includes('dynamic') ? true : defaultOptions.includeDynamicTools; let allTools: boolean | undefined = - queryOptions.no_tools && !queryOptions.no_tools?.includes('all') ? false + queryOptions.no_tools && queryOptions.no_tools?.includes('all') ? false : queryOptions.tools?.includes('all') ? true : defaultOptions.includeAllTools; diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 54ffb6c..3f193eb 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -5,8 +5,9 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { Endpoint, endpoints, HandlerFunction, query } from './tools'; import { CallToolRequestSchema, - Implementation, ListToolsRequestSchema, + SetLevelRequestSchema, + Implementation, Tool, } from '@modelcontextprotocol/sdk/types.js'; import { ClientOptions } from '@beeper/desktop-api'; @@ -32,12 +33,12 @@ export const newMcpServer = () => new McpServer( { name: 'beeper_desktop_api_api', - version: '0.1.4', + version: '0.1.5', }, { capabilities: { tools: {}, logging: {} }, instructions: - 'This MCP server provides access to your Beeper Desktop messages and chats. Use the search and find tools to locate messages and conversations, then use send_message to respond.', + 'Access to all chats and messages across networks using Beeper Desktop. Can be used to find, get, send, and manage messages and chats.', }, ); @@ -59,7 +60,7 @@ export function initMcpServer(params: { let providedEndpoints: Endpoint[] | null = null; let endpointMap: Record | null = null; - const initTools = (implementation?: Implementation) => { + const initTools = async (implementation?: Implementation) => { if (implementation && (!mcpOptions.client || mcpOptions.client === 'infer')) { mcpOptions.client = implementation.name.toLowerCase().includes('claude') ? 'claude' @@ -70,8 +71,8 @@ export function initMcpServer(params: { ...mcpOptions.capabilities, }; } - providedEndpoints = selectTools(endpoints, mcpOptions); - endpointMap = Object.fromEntries(providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint])); + providedEndpoints ??= await selectTools(endpoints, mcpOptions); + endpointMap ??= Object.fromEntries(providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint])); }; const logAtLevel = @@ -89,7 +90,7 @@ export function initMcpServer(params: { error: logAtLevel('error'), }; - const client = new BeeperDesktop({ + let client = new BeeperDesktop({ logger, ...params.clientOptions, defaultHeaders: { @@ -100,7 +101,7 @@ export function initMcpServer(params: { server.setRequestHandler(ListToolsRequestSchema, async () => { if (providedEndpoints === null) { - initTools(server.getClientVersion()); + await initTools(server.getClientVersion()); } return { tools: providedEndpoints!.map((endpoint) => endpoint.tool), @@ -109,7 +110,7 @@ export function initMcpServer(params: { server.setRequestHandler(CallToolRequestSchema, async (request) => { if (endpointMap === null) { - initTools(server.getClientVersion()); + await initTools(server.getClientVersion()); } const { name, arguments: args } = request.params; const endpoint = endpointMap![name]; @@ -119,12 +120,35 @@ export function initMcpServer(params: { return executeHandler(endpoint.tool, endpoint.handler, client, args, mcpOptions.capabilities); }); + + server.setRequestHandler(SetLevelRequestSchema, async (request) => { + const { level } = request.params; + switch (level) { + case 'debug': + client = client.withOptions({ logLevel: 'debug' }); + break; + case 'info': + client = client.withOptions({ logLevel: 'info' }); + break; + case 'notice': + case 'warning': + client = client.withOptions({ logLevel: 'warn' }); + break; + case 'error': + client = client.withOptions({ logLevel: 'error' }); + break; + default: + client = client.withOptions({ logLevel: 'off' }); + break; + } + return {}; + }); } /** * Selects the tools to include in the MCP Server based on the provided options. */ -export function selectTools(endpoints: Endpoint[], options?: McpOptions): Endpoint[] { +export async function selectTools(endpoints: Endpoint[], options?: McpOptions): Promise { const filteredEndpoints = query(options?.filters ?? [], endpoints); let includedTools = filteredEndpoints; @@ -139,7 +163,7 @@ export function selectTools(endpoints: Endpoint[], options?: McpOptions): Endpoi } else if (options?.includeDynamicTools) { includedTools = dynamicTools(endpoints); } else if (options?.includeCodeTools) { - includedTools = [codeTool()]; + includedTools = [await codeTool()]; } else { includedTools = endpoints; } diff --git a/packages/mcp-server/src/tools/app/open-in-app.ts b/packages/mcp-server/src/tools/app/open-in-app.ts index 6f4cefb..bf8fbb1 100644 --- a/packages/mcp-server/src/tools/app/open-in-app.ts +++ b/packages/mcp-server/src/tools/app/open-in-app.ts @@ -16,7 +16,8 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'open_in_app', - description: 'Open Beeper, optionally focusing a chat or message, or pre-filling a draft.', + description: + 'Open Beeper Desktop and optionally navigate to a specific chat, message, or pre-fill draft text and attachment.', inputSchema: { type: 'object', properties: { @@ -25,13 +26,17 @@ export const tool: Tool = { description: 'Optional Beeper chat ID (or local chat ID) to focus after opening the app. If omitted, only opens/focuses the app.', }, + draftAttachmentPath: { + type: 'string', + description: 'Optional draft attachment path to populate in the message input field.', + }, draftText: { type: 'string', description: 'Optional draft text to populate in the message input field.', }, - messageSortKey: { + messageID: { type: 'string', - description: 'Optional message sort key. Jumps to that message in the chat when opening.', + description: 'Optional message ID. Jumps to that message in the chat when opening.', }, }, required: [], diff --git a/packages/mcp-server/src/tools/app/search.ts b/packages/mcp-server/src/tools/app/search.ts new file mode 100644 index 0000000..a911592 --- /dev/null +++ b/packages/mcp-server/src/tools/app/search.ts @@ -0,0 +1,41 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { Metadata, asTextContentResult } from '@beeper/desktop-mcp/tools/types'; + +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import BeeperDesktop from '@beeper/desktop-api'; + +export const metadata: Metadata = { + resource: 'app', + operation: 'read', + tags: ['app'], + httpMethod: 'get', + httpPath: '/v0/search', + operationId: 'search', +}; + +export const tool: Tool = { + name: 'search', + description: + 'Search for chats, participant name matches in groups, and the first page of messages in one call. Use this when the user asks for a specific chat, group, or person.', + inputSchema: { + type: 'object', + properties: { + query: { + type: 'string', + description: 'User-typed search text. Literal word matching (NOT semantic).', + }, + }, + required: ['query'], + }, + annotations: { + readOnlyHint: true, + }, +}; + +export const handler = async (client: BeeperDesktop, args: Record | undefined) => { + const body = args as any; + return asTextContentResult(await client.app.search(body)); +}; + +export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/chats/search-chats.ts b/packages/mcp-server/src/tools/chats/search-chats.ts index f621c81..bc746d9 100644 --- a/packages/mcp-server/src/tools/chats/search-chats.ts +++ b/packages/mcp-server/src/tools/chats/search-chats.ts @@ -16,7 +16,8 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'search_chats', - description: 'Search chats by inbox, type, unread status, or text. Paginates.', + description: + "Search chats by title/network or participants using Beeper Desktop's renderer algorithm. Optional 'scope'.", inputSchema: { type: 'object', properties: { @@ -65,21 +66,22 @@ export const tool: Tool = { type: 'integer', description: 'Set the maximum number of chats to retrieve. Valid range: 1-200, default is 50', }, - participantQuery: { + query: { type: 'string', description: - 'Search string to filter chats by participant names. When multiple words provided, ALL words must match. Searches in username, displayName, and fullName fields.', + 'Literal token search (non-semantic). Use single words users type (e.g., "dinner"). When multiple words provided, ALL must match. Case-insensitive.', }, - query: { + scope: { type: 'string', description: - 'Search string to filter chats by title. When multiple words provided, ALL words must match. Matches are case-insensitive substrings.', + "Search scope: 'titles' matches title + network; 'participants' matches participant names.", + enum: ['titles', 'participants'], }, type: { type: 'string', description: - 'Specify the type of chats to retrieve: use "single" for direct messages, "group" for group chats, "channel" for channels, or "any" to get all types', - enum: ['single', 'group', 'channel', 'any'], + 'Specify the type of chats to retrieve: use "single" for direct messages, "group" for group chats, or "any" to get all types', + enum: ['single', 'group', 'any'], }, unreadOnly: { type: 'boolean', diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts index ad0dc8c..e3ad0a9 100644 --- a/packages/mcp-server/src/tools/index.ts +++ b/packages/mcp-server/src/tools/index.ts @@ -6,6 +6,7 @@ export { Metadata, Endpoint, HandlerFunction }; import get_accounts from './accounts/get-accounts'; import open_in_app from './app/open-in-app'; +import search from './app/search'; import get_chat from './chats/get-chat'; import archive_chat from './chats/archive-chat'; import search_chats from './chats/search-chats'; @@ -13,7 +14,6 @@ import set_chat_reminder from './chats/reminders/set-chat-reminder'; import clear_chat_reminder from './chats/reminders/clear-chat-reminder'; import search_messages from './messages/search-messages'; import send_message from './messages/send-message'; -import download_attachment from './messages/attachments/download-attachment'; export const endpoints: Endpoint[] = []; @@ -23,6 +23,7 @@ function addEndpoint(endpoint: Endpoint) { addEndpoint(get_accounts); addEndpoint(open_in_app); +addEndpoint(search); addEndpoint(get_chat); addEndpoint(archive_chat); addEndpoint(search_chats); @@ -30,7 +31,6 @@ addEndpoint(set_chat_reminder); addEndpoint(clear_chat_reminder); addEndpoint(search_messages); addEndpoint(send_message); -addEndpoint(download_attachment); export type Filter = { type: 'resource' | 'operation' | 'tag' | 'tool'; diff --git a/packages/mcp-server/src/tools/messages/attachments/download-attachment.ts b/packages/mcp-server/src/tools/messages/attachments/download-attachment.ts deleted file mode 100644 index 3d31dcc..0000000 --- a/packages/mcp-server/src/tools/messages/attachments/download-attachment.ts +++ /dev/null @@ -1,42 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asTextContentResult } from '@beeper/desktop-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import BeeperDesktop from '@beeper/desktop-api'; - -export const metadata: Metadata = { - resource: 'messages.attachments', - operation: 'write', - tags: ['messages'], - httpMethod: 'post', - httpPath: '/v0/download-attachment', - operationId: 'download_attachment', -}; - -export const tool: Tool = { - name: 'download_attachment', - description: 'Download a message attachment and return the local file path.', - inputSchema: { - type: 'object', - properties: { - chatID: { - type: 'string', - description: 'Unique identifier of the chat (supports both chatID and localChatID).', - }, - messageID: { - type: 'string', - description: 'The message ID (eventID) containing the attachment.', - }, - }, - required: ['chatID', 'messageID'], - }, - annotations: {}, -}; - -export const handler = async (client: BeeperDesktop, args: Record | undefined) => { - const body = args as any; - return asTextContentResult(await client.messages.attachments.download(body)); -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/messages/search-messages.ts b/packages/mcp-server/src/tools/messages/search-messages.ts index a3fc774..f9ca9c7 100644 --- a/packages/mcp-server/src/tools/messages/search-messages.ts +++ b/packages/mcp-server/src/tools/messages/search-messages.ts @@ -17,7 +17,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'search_messages', description: - 'Search messages across chats using Beeper\'s message index.\n- When to use: find messages by text and/or filters (chatIDs, accountIDs, chatType, media type filters, sender, date ranges).\n- CRITICAL: Query is LITERAL WORD MATCHING, NOT semantic search! Only finds messages containing these EXACT words.\n • ✅ RIGHT: query="dinner" or query="sick" or query="error" (single words users type)\n • ❌ WRONG: query="dinner plans tonight" or query="health issues" (phrases/concepts)\n • The query matches ALL words provided (in any order). Example: query="flight booking" finds messages with both "flight" AND "booking".\n- Media filters: Use onlyWithMedia for any media, or specific filters like onlyWithVideo, onlyWithImage, onlyWithLink, onlyWithFile for specific types.\n- Pagination: use \'oldestCursor\' + direction=\'before\' for older; \'newestCursor\' + direction=\'after\' for newer.\n- Performance: provide chatIDs/accountIDs when known. Omitted \'query\' returns results based on filters only. Partial matches enabled; \'excludeLowPriority\' defaults to true.\n- Workflow tip: To search messages in specific conversations: 1) Use find-chats to get chatIDs, 2) Use search-messages with those chatIDs.\n- IMPORTANT: Chat names vary widely. ASK the user for clarification:\n • "Which chat do you mean by family?" (could be "The Smiths", "Mom Dad Kids", etc.)\n • "What\'s the name of your work chat?" (could be "Team", company name, project name)\n • "Who are the participants?" (use participantQuery in find-chats)\nReturns: matching messages and referenced chats.', + 'Search messages across chats using Beeper\'s message index.\n- When to use: find messages by text and/or filters (chatIDs, accountIDs, chatType, media type filters, sender, date ranges).\n- CRITICAL: Query is LITERAL WORD MATCHING, NOT semantic search! Only finds messages containing these EXACT words.\n • ✅ RIGHT: query="dinner" or query="sick" or query="error" (single words users type)\n • ❌ WRONG: query="dinner plans tonight" or query="health issues" (phrases/concepts)\n • The query matches ALL words provided (in any order). Example: query="flight booking" finds messages with both "flight" AND "booking".\n- Performance: provide chatIDs/accountIDs when known. Omitted \'query\' returns results based on filters only. Partial matches enabled; \'excludeLowPriority\' defaults to true.\n- Workflow tip: To search messages in specific conversations: 1) Use find-chats to get chatIDs, 2) Use search-messages with those chatIDs.\n- IMPORTANT: Chat names vary widely. ASK the user for clarification:\n • "Which chat do you mean by family?" (could be "The Smiths", "Mom Dad Kids", etc.)\n • "What\'s the name of your work chat?" (could be "Team", company name, project name)\n • "Who are the participants?" (use scope="participants" in search-chats)\nReturns: matching messages and referenced chats.', inputSchema: { type: 'object', properties: { @@ -75,27 +75,17 @@ export const tool: Tool = { }, limit: { type: 'integer', - description: 'Maximum number of messages to return (1–500). Defaults to 50.', - }, - onlyWithFile: { - type: 'boolean', - description: 'Only return messages that contain file attachments.', - }, - onlyWithImage: { - type: 'boolean', - description: 'Only return messages that contain image attachments.', - }, - onlyWithLink: { - type: 'boolean', - description: 'Only return messages that contain link attachments.', - }, - onlyWithMedia: { - type: 'boolean', - description: 'Only return messages that contain any type of media attachment.', + description: + 'Maximum number of messages to return (1–500). Defaults to 20. The current implementation caps each page at 20 items even if a higher limit is requested.', }, - onlyWithVideo: { - type: 'boolean', - description: 'Only return messages that contain video attachments.', + mediaTypes: { + type: 'array', + description: + "Filter messages by media types. Use ['any'] for any media type, or specify exact types like ['video', 'image']. Omit for no media filtering.", + items: { + type: 'string', + enum: ['any', 'video', 'image', 'link', 'file'], + }, }, query: { type: 'string', diff --git a/packages/mcp-server/src/tools/messages/send-message.ts b/packages/mcp-server/src/tools/messages/send-message.ts index 2a25670..93a7e8a 100644 --- a/packages/mcp-server/src/tools/messages/send-message.ts +++ b/packages/mcp-server/src/tools/messages/send-message.ts @@ -23,8 +23,7 @@ export const tool: Tool = { properties: { chatID: { type: 'string', - description: - 'The identifier of the chat where the message will send (accepts both chatID and local chat ID)', + description: 'Unique identifier of the chat (a.k.a. room or thread).', }, replyToMessageID: { type: 'string', diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock index 707a2de..ad81983 100644 --- a/packages/mcp-server/yarn.lock +++ b/packages/mcp-server/yarn.lock @@ -10,6 +10,20 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" +"@anthropic-ai/dxt@^0.2.6": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@anthropic-ai/dxt/-/dxt-0.2.6.tgz#636197c3d083c9136ac3b5a11d2ba82477fdc2c6" + integrity sha512-5VSqKRpkytTYh5UJz9jOaI8zLXNCe4Gc+ArKGFV6IeWnEPP0Qnd0k+V3pO8cYzp92Puf/+Cgo0xc4haE0azTXg== + dependencies: + "@inquirer/prompts" "^6.0.1" + commander "^13.1.0" + fflate "^0.8.2" + galactus "^1.0.0" + ignore "^7.0.5" + node-forge "^1.3.1" + pretty-bytes "^5.6.0" + zod "^3.25.67" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" @@ -336,6 +350,144 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@inquirer/checkbox@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/checkbox/-/checkbox-3.0.1.tgz#0a57f704265f78c36e17f07e421b98efb4b9867b" + integrity sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/confirm@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-4.0.1.tgz#9106d6bffa0b2fdd0e4f60319b6f04f2e06e6e25" + integrity sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/core@^9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-9.2.1.tgz#677c49dee399c9063f31e0c93f0f37bddc67add1" + integrity sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg== + dependencies: + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + "@types/mute-stream" "^0.0.4" + "@types/node" "^22.5.5" + "@types/wrap-ansi" "^3.0.0" + ansi-escapes "^4.3.2" + cli-width "^4.1.0" + mute-stream "^1.0.0" + signal-exit "^4.1.0" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/editor@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/editor/-/editor-3.0.1.tgz#d109f21e050af6b960725388cb1c04214ed7c7bc" + integrity sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + external-editor "^3.1.0" + +"@inquirer/expand@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/expand/-/expand-3.0.1.tgz#aed9183cac4d12811be47a4a895ea8e82a17e22c" + integrity sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/figures@^1.0.6": + version "1.0.13" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.13.tgz#ad0afd62baab1c23175115a9b62f511b6a751e45" + integrity sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw== + +"@inquirer/input@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/input/-/input-3.0.1.tgz#de63d49e516487388508d42049deb70f2cb5f28e" + integrity sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/number@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/number/-/number-2.0.1.tgz#b9863080d02ab7dc2e56e16433d83abea0f2a980" + integrity sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + +"@inquirer/password@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/password/-/password-3.0.1.tgz#2a9a9143591088336bbd573bcb05d5bf080dbf87" + integrity sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + +"@inquirer/prompts@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/prompts/-/prompts-6.0.1.tgz#43f5c0ed35c5ebfe52f1d43d46da2d363d950071" + integrity sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A== + dependencies: + "@inquirer/checkbox" "^3.0.1" + "@inquirer/confirm" "^4.0.1" + "@inquirer/editor" "^3.0.1" + "@inquirer/expand" "^3.0.1" + "@inquirer/input" "^3.0.1" + "@inquirer/number" "^2.0.1" + "@inquirer/password" "^3.0.1" + "@inquirer/rawlist" "^3.0.1" + "@inquirer/search" "^2.0.1" + "@inquirer/select" "^3.0.1" + +"@inquirer/rawlist@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/rawlist/-/rawlist-3.0.1.tgz#729def358419cc929045f264131878ed379e0af3" + integrity sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/search@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/search/-/search-2.0.1.tgz#69b774a0a826de2e27b48981d01bc5ad81e73721" + integrity sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/select@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@inquirer/select/-/select-3.0.1.tgz#1df9ed27fb85a5f526d559ac5ce7cc4e9dc4e7ec" + integrity sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q== + dependencies: + "@inquirer/core" "^9.2.1" + "@inquirer/figures" "^1.0.6" + "@inquirer/type" "^2.0.0" + ansi-escapes "^4.3.2" + yoctocolors-cjs "^2.1.2" + +"@inquirer/type@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-2.0.0.tgz#08fa513dca2cb6264fe1b0a2fabade051444e3f6" + integrity sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag== + dependencies: + mute-stream "^1.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -725,6 +877,13 @@ dependencies: "@types/node" "*" +"@types/cors@^2.8.19": + version "2.8.19" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342" + integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg== + dependencies: + "@types/node" "*" + "@types/express-serve-static-core@^5.0.0": version "5.0.7" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz#2fa94879c9d46b11a5df4c74ac75befd6b283de6" @@ -788,6 +947,13 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== +"@types/mute-stream@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@types/mute-stream/-/mute-stream-0.0.4.tgz#77208e56a08767af6c5e1237be8888e2f255c478" + integrity sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow== + dependencies: + "@types/node" "*" + "@types/node@*": version "22.15.17" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.17.tgz#355ccec95f705b664e4332bb64a7f07db30b7055" @@ -795,7 +961,14 @@ dependencies: undici-types "~6.21.0" -"@types/qs@*": +"@types/node@^22.5.5": + version "22.18.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.18.0.tgz#9e4709be4f104e3568f7dd1c71e2949bf147a47b" + integrity sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ== + dependencies: + undici-types "~6.21.0" + +"@types/qs@*", "@types/qs@^6.14.0": version "6.14.0" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== @@ -827,6 +1000,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/wrap-ansi@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" + integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -925,6 +1103,11 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== +"@valtown/deno-http-worker@^0.0.21": + version "0.0.21" + resolved "https://registry.yarnpkg.com/@valtown/deno-http-worker/-/deno-http-worker-0.0.21.tgz#9ce3b5c1d0db211fe7ea8297881fe551838474ad" + integrity sha512-16kFuUykann75lNytnXXIQlmpzreZjzdyT27ebT3yNGCS3kKaS1iZYWHc3Si9An54Cphwr4qEcviChQkEeJBlA== + accepts@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" @@ -968,7 +1151,7 @@ ajv@^6.12.4, ajv@^6.12.6: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-escapes@^4.2.1: +ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -1210,6 +1393,11 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + ci-info@^3.2.0: version "3.9.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" @@ -1225,6 +1413,11 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -1261,6 +1454,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +commander@^13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46" + integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1673,6 +1871,15 @@ express@^5.0.1, express@^5.1.0: type-is "^2.0.1" vary "^1.1.2" +external-editor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -1718,6 +1925,11 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fflate@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -1781,6 +1993,14 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== +flora-colossus@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/flora-colossus/-/flora-colossus-2.0.0.tgz#af1e85db0a8256ef05f3fb531c1235236c97220a" + integrity sha512-dz4HxH6pOvbUzZpZ/yXhafjbR2I8cenK5xL0KtBFb7U2ADsR+OwXifnxZjij/pZWF775uSCMzWVd+jDik2H2IA== + dependencies: + debug "^4.3.4" + fs-extra "^10.1.0" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -1791,6 +2011,15 @@ fresh@^2.0.0: resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== +fs-extra@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1806,6 +2035,15 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +galactus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/galactus/-/galactus-1.0.0.tgz#c2615182afa0c6d0859b92e56ae36d052827db7e" + integrity sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ== + dependencies: + debug "^4.3.4" + flora-colossus "^2.0.0" + fs-extra "^10.1.0" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -1898,7 +2136,7 @@ gopd@^1.2.0: resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.2.9: +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -1953,11 +2191,23 @@ iconv-lite@0.6.3, iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + ignore@^5.2.0, ignore@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== +ignore@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== + import-fresh@^3.2.1: version "3.3.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" @@ -2536,6 +2786,15 @@ json5@^2.2.2, json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonfile@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" + integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -2709,6 +2968,11 @@ ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +mute-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" + integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -2719,6 +2983,11 @@ negotiator@^1.0.0: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== +node-forge@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -2784,6 +3053,11 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + p-all@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-all/-/p-all-3.0.0.tgz#077c023c37e75e760193badab2bad3ccd5782bfb" @@ -2927,6 +3201,11 @@ prettier@^3.0.0: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5" integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw== +pretty-bytes@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" @@ -3074,7 +3353,7 @@ safe-buffer@5.2.1, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -"safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -3178,6 +3457,11 @@ signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -3322,6 +3606,13 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -3387,9 +3678,9 @@ ts-node@^10.5.0: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz": - version "1.1.8" - resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz#f544b359b8f05e607771ffacc280e58201476b04" +"tsc-multi@https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz": + version "1.1.9" + resolved "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz#777f6f5d9e26bf0e94e5170990dd3a841d6707cd" dependencies: debug "^4.3.7" fast-glob "^3.3.2" @@ -3462,6 +3753,11 @@ undici-types@~6.21.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + unpipe@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -3525,6 +3821,15 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -3585,6 +3890,11 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yoctocolors-cjs@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz#7e4964ea8ec422b7a40ac917d3a344cfd2304baa" + integrity sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw== + zod-to-json-schema@^3.24.1, zod-to-json-schema@^3.24.5: version "3.24.5" resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3" @@ -3600,7 +3910,7 @@ zod@^3.23.8: resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.4.tgz#e2e2cca5faaa012d76e527d0d36622e0a90c315f" integrity sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg== -zod@^3.25.20: +zod@^3.25.20, zod@^3.25.67: version "3.25.76" resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index 54c2860..35e2b8f 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -12,7 +12,7 @@ if [[ "$SIGNED_URL" == "null" ]]; then exit 1 fi -UPLOAD_RESPONSE=$(tar -cz "${BUILD_PATH:-dist}" | curl -v -X PUT \ +UPLOAD_RESPONSE=$(tar "${BASE_PATH:+-C$BASE_PATH}" -cz "${ARTIFACT_PATH:-dist}" | curl -v -X PUT \ -H "Content-Type: application/gzip" \ --data-binary @- "$SIGNED_URL" 2>&1) diff --git a/src/client.ts b/src/client.ts index f5ad48f..d9bc8cf 100644 --- a/src/client.ts +++ b/src/client.ts @@ -20,22 +20,28 @@ import * as Uploads from './core/uploads'; import * as API from './resources/index'; import { APIPromise } from './core/api-promise'; import { Account, AccountListResponse, Accounts } from './resources/accounts'; -import { App, AppOpenParams, AppOpenResponse } from './resources/app'; +import { + App, + AppDownloadAssetParams, + AppDownloadAssetResponse, + AppOpenParams, + AppOpenResponse, + AppSearchParams, + AppSearchResponse, +} from './resources/app'; +import { ContactSearchParams, ContactSearchResponse, Contacts } from './resources/contacts'; +import { MessageSearchParams, MessageSendParams, MessageSendResponse, Messages } from './resources/messages'; import { RevokeRequest, Token, UserInfo } from './resources/token'; import { Chat, ChatArchiveParams, + ChatCreateParams, + ChatCreateResponse, ChatRetrieveParams, ChatSearchParams, Chats, ChatsCursor, } from './resources/chats/chats'; -import { - MessageSearchParams, - MessageSendParams, - MessageSendResponse, - Messages, -} from './resources/messages/messages'; import { type Fetch } from './internal/builtin-types'; import { isRunningInBrowser } from './internal/detect-platform'; import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers'; @@ -766,6 +772,10 @@ export class BeeperDesktop { * App operations */ app: API.App = new API.App(this); + /** + * Contacts operations + */ + contacts: API.Contacts = new API.Contacts(this); /** * Chats operations */ @@ -782,6 +792,7 @@ export class BeeperDesktop { BeeperDesktop.Accounts = Accounts; BeeperDesktop.App = App; +BeeperDesktop.Contacts = Contacts; BeeperDesktop.Chats = Chats; BeeperDesktop.Messages = Messages; BeeperDesktop.Token = Token; @@ -794,12 +805,28 @@ export declare namespace BeeperDesktop { export { Accounts as Accounts, type Account as Account, type AccountListResponse as AccountListResponse }; - export { App as App, type AppOpenResponse as AppOpenResponse, type AppOpenParams as AppOpenParams }; + export { + App as App, + type AppDownloadAssetResponse as AppDownloadAssetResponse, + type AppOpenResponse as AppOpenResponse, + type AppSearchResponse as AppSearchResponse, + type AppDownloadAssetParams as AppDownloadAssetParams, + type AppOpenParams as AppOpenParams, + type AppSearchParams as AppSearchParams, + }; + + export { + Contacts as Contacts, + type ContactSearchResponse as ContactSearchResponse, + type ContactSearchParams as ContactSearchParams, + }; export { Chats as Chats, type Chat as Chat, + type ChatCreateResponse as ChatCreateResponse, type ChatsCursor as ChatsCursor, + type ChatCreateParams as ChatCreateParams, type ChatRetrieveParams as ChatRetrieveParams, type ChatArchiveParams as ChatArchiveParams, type ChatSearchParams as ChatSearchParams, diff --git a/src/internal/utils/values.ts b/src/internal/utils/values.ts index 2889032..67775aa 100644 --- a/src/internal/utils/values.ts +++ b/src/internal/utils/values.ts @@ -76,21 +76,21 @@ export const coerceBoolean = (value: unknown): boolean => { }; export const maybeCoerceInteger = (value: unknown): number | undefined => { - if (value === undefined) { + if (value == null) { return undefined; } return coerceInteger(value); }; export const maybeCoerceFloat = (value: unknown): number | undefined => { - if (value === undefined) { + if (value == null) { return undefined; } return coerceFloat(value); }; export const maybeCoerceBoolean = (value: unknown): boolean | undefined => { - if (value === undefined) { + if (value == null) { return undefined; } return coerceBoolean(value); diff --git a/src/resources/app.ts b/src/resources/app.ts index 42c4dfe..a7991cf 100644 --- a/src/resources/app.ts +++ b/src/resources/app.ts @@ -1,6 +1,8 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../core/resource'; +import * as Shared from './shared'; +import * as ChatsAPI from './chats/chats'; import { APIPromise } from '../core/api-promise'; import { RequestOptions } from '../internal/request-options'; @@ -8,9 +10,27 @@ import { RequestOptions } from '../internal/request-options'; * App operations */ export class App extends APIResource { + /** + * Download a Matrix asset using its mxc:// or localmxc:// URL and return the local + * file URL. + * + * @example + * ```ts + * const response = await client.app.downloadAsset({ + * url: 'x', + * }); + * ``` + */ + downloadAsset( + body: AppDownloadAssetParams, + options?: RequestOptions, + ): APIPromise { + return this._client.post('/v0/download-asset', { body, ...options }); + } + /** * Open Beeper Desktop and optionally navigate to a specific chat, message, or - * pre-fill draft text + * pre-fill draft text and attachment. * * @example * ```ts @@ -20,6 +40,32 @@ export class App extends APIResource { open(body: AppOpenParams | null | undefined = {}, options?: RequestOptions): APIPromise { return this._client.post('/v0/open-app', { body, ...options }); } + + /** + * Returns matching chats, participant name matches in groups, and the first page + * of messages in one call. Paginate messages via search-messages. Paginate chats + * via search-chats. Uses the same sorting as the chat search in the app. + * + * @example + * ```ts + * const response = await client.app.search({ query: 'x' }); + * ``` + */ + search(query: AppSearchParams, options?: RequestOptions): APIPromise { + return this._client.get('/v0/search', { query, ...options }); + } +} + +export interface AppDownloadAssetResponse { + /** + * Error message if the download failed. + */ + error?: string; + + /** + * Local file URL to the downloaded asset. + */ + srcURL?: string; } /** @@ -32,6 +78,64 @@ export interface AppOpenResponse { success: boolean; } +export interface AppSearchResponse { + results: AppSearchResponse.Results; +} + +export namespace AppSearchResponse { + export interface Results { + /** + * Top chat results. + */ + chats: Array; + + /** + * Top group results by participant matches. + */ + in_groups: Array; + + messages: Results.Messages; + } + + export namespace Results { + export interface Messages { + /** + * Map of chatID -> chat details for chats referenced in items. + */ + chats: { [key: string]: ChatsAPI.Chat }; + + /** + * True if additional results can be fetched using the provided cursors. + */ + hasMore: boolean; + + /** + * Messages matching the query and filters. + */ + items: Array; + + /** + * Cursor for fetching newer results (use with direction='after'). Opaque string; + * do not inspect. + */ + newestCursor: string | null; + + /** + * Cursor for fetching older results (use with direction='before'). Opaque string; + * do not inspect. + */ + oldestCursor: string | null; + } + } +} + +export interface AppDownloadAssetParams { + /** + * Matrix content URL (mxc:// or localmxc://) for the asset to download. + */ + url: string; +} + export interface AppOpenParams { /** * Optional Beeper chat ID (or local chat ID) to focus after opening the app. If @@ -39,17 +143,36 @@ export interface AppOpenParams { */ chatID?: string; + /** + * Optional draft attachment path to populate in the message input field. + */ + draftAttachmentPath?: string; + /** * Optional draft text to populate in the message input field. */ draftText?: string; /** - * Optional message sort key. Jumps to that message in the chat when opening. + * Optional message ID. Jumps to that message in the chat when opening. + */ + messageID?: string; +} + +export interface AppSearchParams { + /** + * User-typed search text. Literal word matching (NOT semantic). */ - messageSortKey?: string; + query: string; } export declare namespace App { - export { type AppOpenResponse as AppOpenResponse, type AppOpenParams as AppOpenParams }; + export { + type AppDownloadAssetResponse as AppDownloadAssetResponse, + type AppOpenResponse as AppOpenResponse, + type AppSearchResponse as AppSearchResponse, + type AppDownloadAssetParams as AppDownloadAssetParams, + type AppOpenParams as AppOpenParams, + type AppSearchParams as AppSearchParams, + }; } diff --git a/src/resources/chats/chats.ts b/src/resources/chats/chats.ts index 062665d..0154f56 100644 --- a/src/resources/chats/chats.ts +++ b/src/resources/chats/chats.ts @@ -14,6 +14,24 @@ import { RequestOptions } from '../../internal/request-options'; export class Chats extends APIResource { reminders: RemindersAPI.Reminders = new RemindersAPI.Reminders(this._client); + /** + * Create a single or group chat on a specific account using participant IDs and + * optional title. + * + * @example + * ```ts + * const chat = await client.chats.create({ + * accountID: + * 'local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc', + * participantIDs: ['string'], + * type: 'single', + * }); + * ``` + */ + create(body: ChatCreateParams, options?: RequestOptions): APIPromise { + return this._client.post('/v0/create-chat', { body, ...options }); + } + /** * Retrieve chat details including metadata, participants, and latest message * @@ -44,7 +62,8 @@ export class Chats extends APIResource { } /** - * Search and filter conversations across all messaging accounts + * Search chats by title/network or participants using Beeper Desktop's renderer + * algorithm. * * @example * ```ts @@ -155,6 +174,41 @@ export namespace Chat { } } +export interface ChatCreateResponse extends Shared.BaseResponse { + /** + * Newly created chat if available. + */ + chatID?: string; +} + +export interface ChatCreateParams { + /** + * Account to create the chat on. + */ + accountID: string; + + /** + * User IDs to include in the new chat. + */ + participantIDs: Array; + + /** + * Chat type to create: 'single' requires exactly one participantID; 'group' + * supports multiple participants and optional title. + */ + type: 'single' | 'group'; + + /** + * Optional first message content if the platform requires it to create the chat. + */ + messageText?: string; + + /** + * Optional title for group chats; ignored for single chats on most platforms. + */ + title?: string; +} + export interface ChatRetrieveParams { /** * Unique identifier of the chat to retrieve. Not available for iMessage chats. @@ -199,7 +253,7 @@ export interface ChatSearchParams extends CursorParams { * Include chats marked as Muted by the user, which are usually less important. * Default: true. Set to false if the user wants a more refined search. */ - includeMuted?: boolean; + includeMuted?: boolean | null; /** * Provide an ISO datetime string to only retrieve chats with last activity after @@ -214,28 +268,27 @@ export interface ChatSearchParams extends CursorParams { lastActivityBefore?: string; /** - * Search string to filter chats by participant names. When multiple words - * provided, ALL words must match. Searches in username, displayName, and fullName - * fields. + * Literal token search (non-semantic). Use single words users type (e.g., + * "dinner"). When multiple words provided, ALL must match. Case-insensitive. */ - participantQuery?: string; + query?: string; /** - * Search string to filter chats by title. When multiple words provided, ALL words - * must match. Matches are case-insensitive substrings. + * Search scope: 'titles' matches title + network; 'participants' matches + * participant names. */ - query?: string; + scope?: 'titles' | 'participants'; /** * Specify the type of chats to retrieve: use "single" for direct messages, "group" - * for group chats, "channel" for channels, or "any" to get all types + * for group chats, or "any" to get all types */ - type?: 'single' | 'group' | 'channel' | 'any'; + type?: 'single' | 'group' | 'any'; /** * Set to true to only retrieve chats that have unread messages */ - unreadOnly?: boolean; + unreadOnly?: boolean | null; } Chats.Reminders = Reminders; @@ -243,7 +296,9 @@ Chats.Reminders = Reminders; export declare namespace Chats { export { type Chat as Chat, + type ChatCreateResponse as ChatCreateResponse, type ChatsCursor as ChatsCursor, + type ChatCreateParams as ChatCreateParams, type ChatRetrieveParams as ChatRetrieveParams, type ChatArchiveParams as ChatArchiveParams, type ChatSearchParams as ChatSearchParams, diff --git a/src/resources/chats/index.ts b/src/resources/chats/index.ts index 11ac515..ab2b155 100644 --- a/src/resources/chats/index.ts +++ b/src/resources/chats/index.ts @@ -3,6 +3,8 @@ export { Chats, type Chat, + type ChatCreateResponse, + type ChatCreateParams, type ChatRetrieveParams, type ChatArchiveParams, type ChatSearchParams, diff --git a/src/resources/contacts.ts b/src/resources/contacts.ts new file mode 100644 index 0000000..b7b0f4b --- /dev/null +++ b/src/resources/contacts.ts @@ -0,0 +1,42 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import * as Shared from './shared'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; + +/** + * Contacts operations + */ +export class Contacts extends APIResource { + /** + * Search users across on a specific account using the network's search API. Only + * use for creating new chats. + */ + search(query: ContactSearchParams, options?: RequestOptions): APIPromise { + return this._client.get('/v0/search-users', { query, ...options }); + } +} + +export interface ContactSearchResponse { + items: Array; +} + +export interface ContactSearchParams { + /** + * Beeper account ID this resource belongs to. + */ + accountID: string; + + /** + * Text to search users by. Network-specific behavior. + */ + query: string; +} + +export declare namespace Contacts { + export { + type ContactSearchResponse as ContactSearchResponse, + type ContactSearchParams as ContactSearchParams, + }; +} diff --git a/src/resources/index.ts b/src/resources/index.ts index f5869db..4d7984c 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -2,19 +2,30 @@ export * from './shared'; export { Accounts, type Account, type AccountListResponse } from './accounts'; -export { App, type AppOpenResponse, type AppOpenParams } from './app'; +export { + App, + type AppDownloadAssetResponse, + type AppOpenResponse, + type AppSearchResponse, + type AppDownloadAssetParams, + type AppOpenParams, + type AppSearchParams, +} from './app'; export { Chats, type Chat, + type ChatCreateResponse, + type ChatCreateParams, type ChatRetrieveParams, type ChatArchiveParams, type ChatSearchParams, type ChatsCursor, } from './chats/chats'; +export { Contacts, type ContactSearchResponse, type ContactSearchParams } from './contacts'; export { Messages, type MessageSendResponse, type MessageSearchParams, type MessageSendParams, -} from './messages/messages'; +} from './messages'; export { Token, type RevokeRequest, type UserInfo } from './token'; diff --git a/src/resources/messages.ts b/src/resources/messages.ts index eb17523..32dff27 100644 --- a/src/resources/messages.ts +++ b/src/resources/messages.ts @@ -1,3 +1,146 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -export * from './messages/index'; +import { APIResource } from '../core/resource'; +import * as Shared from './shared'; +import { MessagesCursor } from './shared'; +import { APIPromise } from '../core/api-promise'; +import { Cursor, type CursorParams, PagePromise } from '../core/pagination'; +import { RequestOptions } from '../internal/request-options'; + +/** + * Messages operations + */ +export class Messages extends APIResource { + /** + * Search messages across chats using Beeper's message index + * + * @example + * ```ts + * // Automatically fetches more pages as needed. + * for await (const message of client.messages.search()) { + * // ... + * } + * ``` + */ + search( + query: MessageSearchParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList('/v0/search-messages', Cursor, { query, ...options }); + } + + /** + * Send a text message to a specific chat. Supports replying to existing messages. + * Returns the sent message ID. + * + * @example + * ```ts + * const response = await client.messages.send({ + * chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com', + * }); + * ``` + */ + send(body: MessageSendParams, options?: RequestOptions): APIPromise { + return this._client.post('/v0/send-message', { body, ...options }); + } +} + +export interface MessageSendResponse extends Shared.BaseResponse { + /** + * Unique identifier of the chat (a.k.a. room or thread). + */ + chatID: string; + + /** + * Pending message ID + */ + pendingMessageID: string; +} + +export interface MessageSearchParams extends CursorParams { + /** + * Limit search to specific Beeper account IDs (bridge instances). + */ + accountIDs?: Array; + + /** + * Limit search to specific Beeper chat IDs. + */ + chatIDs?: Array; + + /** + * Filter by chat type: 'group' for group chats, 'single' for 1:1 chats. + */ + chatType?: 'group' | 'single'; + + /** + * Only include messages with timestamp strictly after this ISO 8601 datetime + * (e.g., '2024-07-01T00:00:00Z' or '2024-07-01T00:00:00+02:00'). + */ + dateAfter?: string; + + /** + * Only include messages with timestamp strictly before this ISO 8601 datetime + * (e.g., '2024-07-31T23:59:59Z' or '2024-07-31T23:59:59+02:00'). + */ + dateBefore?: string; + + /** + * Exclude messages marked Low Priority by the user. Default: true. Set to false to + * include all. + */ + excludeLowPriority?: boolean | null; + + /** + * Include messages in chats marked as Muted by the user, which are usually less + * important. Default: true. Set to false if the user wants a more refined search. + */ + includeMuted?: boolean | null; + + /** + * Filter messages by media types. Use ['any'] for any media type, or specify exact + * types like ['video', 'image']. Omit for no media filtering. + */ + mediaTypes?: Array<'any' | 'video' | 'image' | 'link' | 'file'>; + + /** + * Literal word search (NOT semantic). Finds messages containing these EXACT words + * in any order. Use single words users actually type, not concepts or phrases. + * Example: use "dinner" not "dinner plans", use "sick" not "health issues". If + * omitted, returns results filtered only by other parameters. + */ + query?: string; + + /** + * Filter by sender: 'me' (messages sent by the authenticated user), 'others' + * (messages sent by others), or a specific user ID string (user.id). + */ + sender?: 'me' | 'others' | (string & {}); +} + +export interface MessageSendParams { + /** + * Unique identifier of the chat (a.k.a. room or thread). + */ + chatID: string; + + /** + * Provide a message ID to send this as a reply to an existing message + */ + replyToMessageID?: string; + + /** + * Text content of the message you want to send. You may use markdown. + */ + text?: string; +} + +export declare namespace Messages { + export { + type MessageSendResponse as MessageSendResponse, + type MessageSearchParams as MessageSearchParams, + type MessageSendParams as MessageSendParams, + }; +} + +export { type MessagesCursor }; diff --git a/src/resources/messages/attachments.ts b/src/resources/messages/attachments.ts deleted file mode 100644 index 1b425b6..0000000 --- a/src/resources/messages/attachments.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../../core/resource'; -import { APIPromise } from '../../core/api-promise'; -import { RequestOptions } from '../../internal/request-options'; - -/** - * Attachments operations - */ -export class Attachments extends APIResource { - /** - * Download an attachment from a message and return the local file path - * - * @example - * ```ts - * const response = await client.messages.attachments.download( - * { - * chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com', - * messageID: 'messageID', - * }, - * ); - * ``` - */ - download(body: AttachmentDownloadParams, options?: RequestOptions): APIPromise { - return this._client.post('/v0/download-attachment', { body, ...options }); - } -} - -export interface AttachmentDownloadResponse { - /** - * Whether the attachment was successfully downloaded. - */ - success: boolean; - - /** - * Error message if the download failed. - */ - error?: string; - - /** - * Local file system path to the downloaded attachment. - */ - filePath?: string; -} - -export interface AttachmentDownloadParams { - /** - * Unique identifier of the chat (supports both chatID and localChatID). - */ - chatID: string; - - /** - * The message ID (eventID) containing the attachment. - */ - messageID: string; -} - -export declare namespace Attachments { - export { - type AttachmentDownloadResponse as AttachmentDownloadResponse, - type AttachmentDownloadParams as AttachmentDownloadParams, - }; -} diff --git a/src/resources/messages/index.ts b/src/resources/messages/index.ts deleted file mode 100644 index 6ff60fc..0000000 --- a/src/resources/messages/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -export { Attachments, type AttachmentDownloadResponse, type AttachmentDownloadParams } from './attachments'; -export { - Messages, - type MessageSendResponse, - type MessageSearchParams, - type MessageSendParams, -} from './messages'; diff --git a/src/resources/messages/messages.ts b/src/resources/messages/messages.ts deleted file mode 100644 index f99677e..0000000 --- a/src/resources/messages/messages.ts +++ /dev/null @@ -1,173 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../../core/resource'; -import * as Shared from '../shared'; -import { MessagesCursor } from '../shared'; -import * as AttachmentsAPI from './attachments'; -import { AttachmentDownloadParams, AttachmentDownloadResponse, Attachments } from './attachments'; -import { APIPromise } from '../../core/api-promise'; -import { Cursor, type CursorParams, PagePromise } from '../../core/pagination'; -import { RequestOptions } from '../../internal/request-options'; - -/** - * Messages operations - */ -export class Messages extends APIResource { - attachments: AttachmentsAPI.Attachments = new AttachmentsAPI.Attachments(this._client); - - /** - * Search messages across chats using Beeper's message index - * - * @example - * ```ts - * // Automatically fetches more pages as needed. - * for await (const message of client.messages.search()) { - * // ... - * } - * ``` - */ - search( - query: MessageSearchParams | null | undefined = {}, - options?: RequestOptions, - ): PagePromise { - return this._client.getAPIList('/v0/search-messages', Cursor, { query, ...options }); - } - - /** - * Send a text message to a specific chat. Supports replying to existing messages. - * Returns the sent message ID. - * - * @example - * ```ts - * const response = await client.messages.send({ - * chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com', - * }); - * ``` - */ - send(body: MessageSendParams, options?: RequestOptions): APIPromise { - return this._client.post('/v0/send-message', { body, ...options }); - } -} - -export interface MessageSendResponse extends Shared.BaseResponse { - /** - * Stable message ID. - */ - messageID: string; -} - -export interface MessageSearchParams extends CursorParams { - /** - * Limit search to specific Beeper account IDs (bridge instances). - */ - accountIDs?: Array; - - /** - * Limit search to specific Beeper chat IDs. - */ - chatIDs?: Array; - - /** - * Filter by chat type: 'group' for group chats, 'single' for 1:1 chats. - */ - chatType?: 'group' | 'single'; - - /** - * Only include messages with timestamp strictly after this ISO 8601 datetime - * (e.g., '2024-07-01T00:00:00Z' or '2024-07-01T00:00:00+02:00'). - */ - dateAfter?: string; - - /** - * Only include messages with timestamp strictly before this ISO 8601 datetime - * (e.g., '2024-07-31T23:59:59Z' or '2024-07-31T23:59:59+02:00'). - */ - dateBefore?: string; - - /** - * Exclude messages marked Low Priority by the user. Default: true. Set to false to - * include all. - */ - excludeLowPriority?: boolean; - - /** - * Include messages in chats marked as Muted by the user, which are usually less - * important. Default: true. Set to false if the user wants a more refined search. - */ - includeMuted?: boolean; - - /** - * Only return messages that contain file attachments. - */ - onlyWithFile?: boolean; - - /** - * Only return messages that contain image attachments. - */ - onlyWithImage?: boolean; - - /** - * Only return messages that contain link attachments. - */ - onlyWithLink?: boolean; - - /** - * Only return messages that contain any type of media attachment. - */ - onlyWithMedia?: boolean; - - /** - * Only return messages that contain video attachments. - */ - onlyWithVideo?: boolean; - - /** - * Literal word search (NOT semantic). Finds messages containing these EXACT words - * in any order. Use single words users actually type, not concepts or phrases. - * Example: use "dinner" not "dinner plans", use "sick" not "health issues". If - * omitted, returns results filtered only by other parameters. - */ - query?: string; - - /** - * Filter by sender: 'me' (messages sent by the authenticated user), 'others' - * (messages sent by others), or a specific user ID string (user.id). - */ - sender?: 'me' | 'others' | (string & {}); -} - -export interface MessageSendParams { - /** - * The identifier of the chat where the message will send (accepts both chatID and - * local chat ID) - */ - chatID: string; - - /** - * Provide a message ID to send this as a reply to an existing message - */ - replyToMessageID?: string; - - /** - * Text content of the message you want to send. You may use markdown. - */ - text?: string; -} - -Messages.Attachments = Attachments; - -export declare namespace Messages { - export { - type MessageSendResponse as MessageSendResponse, - type MessageSearchParams as MessageSearchParams, - type MessageSendParams as MessageSendParams, - }; - - export { - Attachments as Attachments, - type AttachmentDownloadResponse as AttachmentDownloadResponse, - type AttachmentDownloadParams as AttachmentDownloadParams, - }; -} - -export { type MessagesCursor }; diff --git a/src/version.ts b/src/version.ts index 28b999e..44ea521 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.1.4'; // x-release-please-version +export const VERSION = '0.1.5'; // x-release-please-version diff --git a/tests/api-resources/app.test.ts b/tests/api-resources/app.test.ts index 30a1eb4..0e0a118 100644 --- a/tests/api-resources/app.test.ts +++ b/tests/api-resources/app.test.ts @@ -8,6 +8,21 @@ const client = new BeeperDesktop({ }); describe('resource app', () => { + test('downloadAsset: only required params', async () => { + const responsePromise = client.app.downloadAsset({ url: 'x' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('downloadAsset: required and optional params', async () => { + const response = await client.app.downloadAsset({ url: 'x' }); + }); + test('open', async () => { const responsePromise = client.app.open(); const rawResponse = await responsePromise.asResponse(); @@ -25,11 +40,27 @@ describe('resource app', () => { client.app.open( { chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com', + draftAttachmentPath: 'draftAttachmentPath', draftText: 'draftText', - messageSortKey: 'messageSortKey', + messageID: 'messageID', }, { path: '/_stainless_unknown_path' }, ), ).rejects.toThrow(BeeperDesktop.NotFoundError); }); + + test('search: only required params', async () => { + const responsePromise = client.app.search({ query: 'x' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('search: required and optional params', async () => { + const response = await client.app.search({ query: 'x' }); + }); }); diff --git a/tests/api-resources/chats/chats.test.ts b/tests/api-resources/chats/chats.test.ts index cbfa64e..162c241 100644 --- a/tests/api-resources/chats/chats.test.ts +++ b/tests/api-resources/chats/chats.test.ts @@ -8,6 +8,31 @@ const client = new BeeperDesktop({ }); describe('resource chats', () => { + test('create: only required params', async () => { + const responsePromise = client.chats.create({ + accountID: 'local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc', + participantIDs: ['string'], + type: 'single', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('create: required and optional params', async () => { + const response = await client.chats.create({ + accountID: 'local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc', + participantIDs: ['string'], + type: 'single', + messageText: 'messageText', + title: 'title', + }); + }); + test('retrieve: only required params', async () => { const responsePromise = client.chats.retrieve({ chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com' }); const rawResponse = await responsePromise.asResponse(); @@ -68,8 +93,8 @@ describe('resource chats', () => { lastActivityAfter: '2019-12-27T18:11:19.117Z', lastActivityBefore: '2019-12-27T18:11:19.117Z', limit: 1, - participantQuery: 'participantQuery', - query: 'query', + query: 'x', + scope: 'titles', type: 'single', unreadOnly: true, }, diff --git a/tests/api-resources/messages/attachments.test.ts b/tests/api-resources/contacts.test.ts similarity index 60% rename from tests/api-resources/messages/attachments.test.ts rename to tests/api-resources/contacts.test.ts index 42125f4..b21cda7 100644 --- a/tests/api-resources/messages/attachments.test.ts +++ b/tests/api-resources/contacts.test.ts @@ -7,11 +7,11 @@ const client = new BeeperDesktop({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); -describe('resource attachments', () => { - test('download: only required params', async () => { - const responsePromise = client.messages.attachments.download({ - chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com', - messageID: 'messageID', +describe('resource contacts', () => { + test('search: only required params', async () => { + const responsePromise = client.contacts.search({ + accountID: 'local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc', + query: 'x', }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); @@ -22,10 +22,10 @@ describe('resource attachments', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('download: required and optional params', async () => { - const response = await client.messages.attachments.download({ - chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com', - messageID: 'messageID', + test('search: required and optional params', async () => { + const response = await client.contacts.search({ + accountID: 'local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc', + query: 'x', }); }); }); diff --git a/tests/api-resources/messages/messages.test.ts b/tests/api-resources/messages.test.ts similarity index 91% rename from tests/api-resources/messages/messages.test.ts rename to tests/api-resources/messages.test.ts index f62cbd1..21fafff 100644 --- a/tests/api-resources/messages/messages.test.ts +++ b/tests/api-resources/messages.test.ts @@ -30,18 +30,14 @@ describe('resource messages', () => { ], chatIDs: ['!NCdzlIaMjZUmvmvyHU:beeper.com', '1231073'], chatType: 'group', - cursor: 'eyJvZmZzZXQiOjE3MTk5OTk5OTl9', + cursor: '1725489123456|c29tZUltc2dQYWdl', dateAfter: '2025-08-01T00:00:00Z', dateBefore: '2025-08-31T23:59:59Z', direction: 'before', excludeLowPriority: true, includeMuted: true, - limit: 50, - onlyWithFile: true, - onlyWithImage: true, - onlyWithLink: true, - onlyWithMedia: true, - onlyWithVideo: true, + limit: 20, + mediaTypes: ['any'], query: 'dinner', sender: 'me', },