From 7bf63a225cc6f3997090cd733b72dd434de8d8b9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 26 May 2025 23:46:03 +0200 Subject: [PATCH] Implement missing best practices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add npm run inspector script for MCP inspector tool - Synchronize Swift CLI version with package.json (1.0.0-beta.9) - Update macOS version requirement to v14 (Sonoma) for n-1 support - Add Swift compiler warnings check in prepare-release script - Convert tests/setup.ts from Jest to Vitest syntax - Update server status tests to match new format 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- package.json | 1 + peekaboo-cli/Package.swift | 2 +- peekaboo-cli/Sources/peekaboo/main.swift | 2 +- scripts/prepare-release.js | 31 +++++++++++++++ src/tools/list.ts | 22 +++++++++-- .../peekaboo-cli-integration.test.ts | 2 +- tests/setup.ts | 27 +++++++------ tests/unit/utils/server-status.test.ts | 39 +++++-------------- 8 files changed, 77 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index d3e508f..04de735 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "lint:swift": "cd peekaboo-cli && swiftlint", "format:swift": "cd peekaboo-cli && swiftformat .", "prepare-release": "node ./scripts/prepare-release.js", + "inspector": "npx @modelcontextprotocol/inspector node dist/index.js", "postinstall": "chmod +x dist/index.js 2>/dev/null || true" }, "keywords": [ diff --git a/peekaboo-cli/Package.swift b/peekaboo-cli/Package.swift index 468299e..7426aac 100644 --- a/peekaboo-cli/Package.swift +++ b/peekaboo-cli/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "peekaboo", platforms: [ - .macOS(.v12) + .macOS(.v14) ], products: [ .executable( diff --git a/peekaboo-cli/Sources/peekaboo/main.swift b/peekaboo-cli/Sources/peekaboo/main.swift index c5ae0d7..086d2ca 100644 --- a/peekaboo-cli/Sources/peekaboo/main.swift +++ b/peekaboo-cli/Sources/peekaboo/main.swift @@ -5,7 +5,7 @@ struct PeekabooCommand: ParsableCommand { static let configuration = CommandConfiguration( commandName: "peekaboo", abstract: "A macOS utility for screen capture, application listing, and window management", - version: "1.1.1", + version: "1.0.0-beta.9", subcommands: [ImageCommand.self, ListCommand.self], defaultSubcommand: ImageCommand.self ) diff --git a/scripts/prepare-release.js b/scripts/prepare-release.js index ec226e8..a7808b5 100755 --- a/scripts/prepare-release.js +++ b/scripts/prepare-release.js @@ -203,6 +203,37 @@ function checkSwift() { } logSuccess('SwiftLint passed'); + // Check for Swift compiler warnings/errors + log('Checking for Swift compiler warnings...', colors.cyan); + let swiftBuildOutput = ''; + try { + // Capture build output to check for warnings + swiftBuildOutput = execSync('cd peekaboo-cli && swift build --arch arm64 -c release 2>&1', { + cwd: projectRoot, + encoding: 'utf8' + }); + } catch (error) { + logError('Swift build failed during analyzer check'); + if (error.stdout) console.log(error.stdout); + if (error.stderr) console.log(error.stderr); + return false; + } + + // Check for warnings in the output + const warningMatches = swiftBuildOutput.match(/warning:|note:/gi); + if (warningMatches && warningMatches.length > 0) { + logWarning(`Found ${warningMatches.length} warnings/notes in Swift build`); + // Extract and show warning lines + const lines = swiftBuildOutput.split('\n'); + lines.forEach(line => { + if (line.includes('warning:') || line.includes('note:')) { + console.log(` ${line.trim()}`); + } + }); + } else { + logSuccess('No Swift compiler warnings found'); + } + // Run Swift tests if (!execWithOutput('npm run test:swift', 'Swift tests')) { logError('Swift tests failed'); diff --git a/src/tools/list.ts b/src/tools/list.ts index 1a9231f..836f4e4 100644 --- a/src/tools/list.ts +++ b/src/tools/list.ts @@ -14,18 +14,29 @@ export const listToolSchema = z item_type: z .enum(["running_applications", "application_windows", "server_status"]) .default("running_applications") - .describe("What to list. 'server_status' returns Peekaboo server info."), + .describe( + "Specifies the type of items to list. Valid options are:\n" + + "- `running_applications`: Lists all currently running applications with details like name, bundle ID, PID, active status, and window count.\n" + + "- `application_windows`: Lists open windows for a specific application. Requires the `app` parameter. Details can be customized with `include_window_details`.\n" + + "- `server_status`: Returns information about the Peekaboo MCP server itself, including its version and configured AI providers." + ), app: z .string() .optional() .describe( - "Required if 'item_type' is 'application_windows'. Target application. Uses fuzzy matching.", + "Required when `item_type` is `application_windows`. " + + "Specifies the target application by its name (e.g., \"Safari\", \"TextEdit\") or bundle ID. " + + "Fuzzy matching is used, so partial names may work." ), include_window_details: z .array(z.enum(["off_screen", "bounds", "ids"])) .optional() .describe( - "Optional, for 'application_windows'. Additional window details. Example: ['bounds', 'ids']", + "Optional, only applicable when `item_type` is `application_windows`. " + + "Specifies additional details to include for each window. Provide an array of strings. Example: `[\"bounds\", \"ids\"]`.\n" + + "- `ids`: Include window ID.\n" + + "- `bounds`: Include window position and size (x, y, width, height).\n" + + "- `off_screen`: Indicate if the window is currently off-screen." ), }) .refine( @@ -54,6 +65,11 @@ export const listToolSchema = z "'app' and 'include_window_details' not applicable for 'server_status'.", path: ["item_type"], }, + ) + .describe( + "Lists various system items, providing situational awareness. " + + "Can retrieve running applications, windows of a specific app, or server status. " + + "App identifier uses fuzzy matching for convenience." ); export type ListToolInput = z.infer; diff --git a/tests/integration/peekaboo-cli-integration.test.ts b/tests/integration/peekaboo-cli-integration.test.ts index 4ad3631..6e8371b 100644 --- a/tests/integration/peekaboo-cli-integration.test.ts +++ b/tests/integration/peekaboo-cli-integration.test.ts @@ -88,7 +88,7 @@ describe("Swift CLI Integration Tests", () => { ) { const firstContentItem = response.content[0] as PeekabooContentItem; expect(firstContentItem.type).toBe("text"); - expect(firstContentItem.text).toContain("Peekaboo MCP Server Status"); + expect(firstContentItem.text).toContain("Peekaboo MCP"); } else { fail( "Response content was not in the expected format for server_status", diff --git a/tests/setup.ts b/tests/setup.ts index bd560c0..2fe9009 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -1,30 +1,29 @@ -// Jest setup file +// Vitest setup file // Configure global test environment +import { beforeEach, afterEach, vi } from 'vitest'; + // Mock console methods to reduce noise during testing -const originalConsole = global.console; +const originalConsole = globalThis.console; beforeEach(() => { // Reset console mocks before each test - global.console = { + globalThis.console = { ...originalConsole, - log: jest.fn(), - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - debug: jest.fn(), + log: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + debug: vi.fn(), }; }); afterEach(() => { // Restore original console after each test - global.console = originalConsole; - jest.clearAllMocks(); + globalThis.console = originalConsole; + vi.clearAllMocks(); }); -// Global test timeout -jest.setTimeout(10000); - // Mock environment variables for testing process.env.NODE_ENV = "test"; process.env.PEEKABOO_AI_PROVIDERS = JSON.stringify([ @@ -34,4 +33,4 @@ process.env.PEEKABOO_AI_PROVIDERS = JSON.stringify([ model: "llava", enabled: true, }, -]); +]); \ No newline at end of file diff --git a/tests/unit/utils/server-status.test.ts b/tests/unit/utils/server-status.test.ts index e67c9ea..cf2519e 100644 --- a/tests/unit/utils/server-status.test.ts +++ b/tests/unit/utils/server-status.test.ts @@ -10,69 +10,50 @@ describe("Server Status Utility - generateServerStatusString", () => { it("should return status with default providers text when PEEKABOO_AI_PROVIDERS is not set", () => { const status = generateServerStatusString(testVersion); - expect(status).toContain(`Version: ${testVersion}`); - expect(status).toContain( - "Configured AI Providers (from PEEKABOO_AI_PROVIDERS ENV): None Configured. Set PEEKABOO_AI_PROVIDERS ENV.", - ); + expect(status).toBe(`Peekaboo MCP ${testVersion} using None Configured. Set PEEKABOO_AI_PROVIDERS ENV.`); }); it("should return status with default providers text when PEEKABOO_AI_PROVIDERS is an empty string", () => { process.env.PEEKABOO_AI_PROVIDERS = ""; const status = generateServerStatusString(testVersion); - expect(status).toContain(`Version: ${testVersion}`); - expect(status).toContain( - "Configured AI Providers (from PEEKABOO_AI_PROVIDERS ENV): None Configured. Set PEEKABOO_AI_PROVIDERS ENV.", - ); + expect(status).toBe(`Peekaboo MCP ${testVersion} using None Configured. Set PEEKABOO_AI_PROVIDERS ENV.`); }); it("should return status with default providers text when PEEKABOO_AI_PROVIDERS is whitespace", () => { process.env.PEEKABOO_AI_PROVIDERS = " "; const status = generateServerStatusString(testVersion); - expect(status).toContain(`Version: ${testVersion}`); - expect(status).toContain( - "Configured AI Providers (from PEEKABOO_AI_PROVIDERS ENV): None Configured. Set PEEKABOO_AI_PROVIDERS ENV.", - ); + expect(status).toBe(`Peekaboo MCP ${testVersion} using None Configured. Set PEEKABOO_AI_PROVIDERS ENV.`); }); it("should list a single provider from PEEKABOO_AI_PROVIDERS", () => { process.env.PEEKABOO_AI_PROVIDERS = "ollama/llava"; const status = generateServerStatusString(testVersion); - expect(status).toContain(`Version: ${testVersion}`); - expect(status).toContain( - "Configured AI Providers (from PEEKABOO_AI_PROVIDERS ENV): ollama/llava", - ); + expect(status).toBe(`Peekaboo MCP ${testVersion} using ollama/llava`); }); it("should list multiple providers from PEEKABOO_AI_PROVIDERS, trimmed and joined", () => { process.env.PEEKABOO_AI_PROVIDERS = "ollama/llava, openai/gpt-4o"; const status = generateServerStatusString(testVersion); - expect(status).toContain(`Version: ${testVersion}`); - expect(status).toContain( - "Configured AI Providers (from PEEKABOO_AI_PROVIDERS ENV): ollama/llava, openai/gpt-4o", - ); + expect(status).toBe(`Peekaboo MCP ${testVersion} using ollama/llava, openai/gpt-4o`); }); it("should handle extra whitespace and empty segments in PEEKABOO_AI_PROVIDERS", () => { process.env.PEEKABOO_AI_PROVIDERS = " ollama/llava , ,, openai/gpt-4o ,anthropic/claude "; const status = generateServerStatusString(testVersion); - expect(status).toContain(`Version: ${testVersion}`); - expect(status).toContain( - "Configured AI Providers (from PEEKABOO_AI_PROVIDERS ENV): ollama/llava, openai/gpt-4o, anthropic/claude", - ); + expect(status).toBe(`Peekaboo MCP ${testVersion} using ollama/llava, openai/gpt-4o, anthropic/claude`); }); it("should correctly include the provided version string", () => { const customVersion = "z.y.x"; const status = generateServerStatusString(customVersion); - expect(status).toContain(`Version: ${customVersion}`); + expect(status).toBe(`Peekaboo MCP ${customVersion} using None Configured. Set PEEKABOO_AI_PROVIDERS ENV.`); }); - it("should produce a trimmed multi-line string", () => { + it("should produce a trimmed string", () => { const status = generateServerStatusString("0.0.1"); - expect(status.startsWith("---")).toBe(true); - expect(status.endsWith("---")).toBe(true); expect(status).not.toMatch(/^\s/); // No leading whitespace expect(status).not.toMatch(/\s$/); // No trailing whitespace + expect(status.startsWith("Peekaboo MCP")).toBe(true); }); -}); +}); \ No newline at end of file