#!/usr/bin/env node /** * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // @ts-check import fs from 'node:fs' import path from 'node:path' import url from 'node:url' import zodToJsonSchema from 'zod-to-json-schema' import commonTools from '../lib/tools/common.js'; import consoleTools from '../lib/tools/console.js'; import dialogsTools from '../lib/tools/dialogs.js'; import filesTools from '../lib/tools/files.js'; import installTools from '../lib/tools/install.js'; import keyboardTools from '../lib/tools/keyboard.js'; import navigateTools from '../lib/tools/navigate.js'; import pdfTools from '../lib/tools/pdf.js'; import snapshotTools from '../lib/tools/snapshot.js'; import tabsTools from '../lib/tools/tabs.js'; import screenTools from '../lib/tools/screen.js'; import testTools from '../lib/tools/testing.js'; // Category definitions for tools const categories = { 'Snapshot-based Interactions': [ ...snapshotTools, ], 'Vision-based Interactions': [ ...screenTools ], 'Tab Management': [ ...tabsTools(true), ], 'Navigation': [ ...navigateTools(true), ], 'Keyboard': [ ...keyboardTools(true) ], 'Console': [ ...consoleTools ], 'Files and Media': [ ...filesTools(true), ...pdfTools ], 'Utilities': [ ...commonTools(true), ...installTools, ...dialogsTools(true), ], 'Testing': [ ...testTools, ], }; // NOTE: Can be removed when we drop Node.js 18 support and changed to import.meta.filename. const __filename = url.fileURLToPath(import.meta.url); const kStartMarker = ``; const kEndMarker = ``; /** * @param {ParsedToolSchema} tool * @returns {string} */ function formatToolForReadme(tool) { const lines = /** @type {string[]} */ ([]); lines.push(`\n\n`); lines.push(`- **${tool.name}**\n`); lines.push(` - Description: ${tool.description}\n`); if (tool.parameters && tool.parameters.length > 0) { lines.push(` - Parameters:\n`); tool.parameters.forEach(param => { const meta = /** @type {string[]} */ ([]); if (param.type) meta.push(param.type); if (param.optional) meta.push('optional'); lines.push(` - \`${param.name}\` ${meta.length ? `(${meta.join(', ')})` : ''}: ${param.description}\n`); }); } else { lines.push(` - Parameters: None\n`); } lines.push('\n'); return lines.join(''); } /** * @typedef {{ * name: any; * description: any; * parameters: { * name: string; * description: string; * optional: boolean; * type: string; * }[]; *}} ParsedToolSchema */ /** * @param {import('../src/tools/tool').ToolSchema} schema * @returns {ParsedToolSchema} */ function processToolSchema(schema) { const inputSchema = /** @type {import('zod-to-json-schema').JsonSchema7ObjectType} */ (zodToJsonSchema(schema.inputSchema || {})); if (inputSchema.type !== 'object') throw new Error(`Tool ${schema.name} input schema is not an object`); // In JSON Schema, properties are considered optional unless listed in the required array const requiredParams = inputSchema?.required || []; const parameters = Object.entries(inputSchema.properties).map(([name, prop]) => { return { name, description: prop.description || '', optional: !requiredParams.includes(name), type: /** @type {any} */ (prop).type, }; }); return { name: schema.name, description: schema.description, parameters }; } async function updateReadme() { console.log('Loading tool information from compiled modules...'); // Count the tools processed const totalTools = Object.values(categories).flat().length; console.log(`Found ${totalTools} tools`); const generatedLines = /** @type {string[]} */ ([]); for (const [category, categoryTools] of Object.entries(categories)) { generatedLines.push(`### ${category}\n\n`); for (const tool of categoryTools) { const scheme = processToolSchema(tool.schema); generatedLines.push(formatToolForReadme(scheme)); } } const readmePath = path.join(path.dirname(__filename), '..', 'README.md'); const readmeContent = await fs.promises.readFile(readmePath, 'utf-8'); const startMarker = readmeContent.indexOf(kStartMarker); const endMarker = readmeContent.indexOf(kEndMarker); if (startMarker === -1 || endMarker === -1) throw new Error('Markers for generated section not found in README'); const newReadmeContent = [ readmeContent.slice(0, startMarker), kStartMarker + '\n\n', generatedLines.join(''), kEndMarker, readmeContent.slice(endMarker + kEndMarker.length), ].join(''); // Write updated README await fs.promises.writeFile(readmePath, newReadmeContent, 'utf-8'); console.log('README updated successfully'); } // Run the update updateReadme().catch(err => { console.error('Error updating README:', err); process.exit(1); });