Implement missing best practices

- 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 <noreply@anthropic.com>
This commit is contained in:
Peter Steinberger 2025-05-26 23:46:03 +02:00
parent b9f7e2da7a
commit 7bf63a225c
8 changed files with 77 additions and 49 deletions

View file

@ -29,6 +29,7 @@
"lint:swift": "cd peekaboo-cli && swiftlint", "lint:swift": "cd peekaboo-cli && swiftlint",
"format:swift": "cd peekaboo-cli && swiftformat .", "format:swift": "cd peekaboo-cli && swiftformat .",
"prepare-release": "node ./scripts/prepare-release.js", "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" "postinstall": "chmod +x dist/index.js 2>/dev/null || true"
}, },
"keywords": [ "keywords": [

View file

@ -4,7 +4,7 @@ import PackageDescription
let package = Package( let package = Package(
name: "peekaboo", name: "peekaboo",
platforms: [ platforms: [
.macOS(.v12) .macOS(.v14)
], ],
products: [ products: [
.executable( .executable(

View file

@ -5,7 +5,7 @@ struct PeekabooCommand: ParsableCommand {
static let configuration = CommandConfiguration( static let configuration = CommandConfiguration(
commandName: "peekaboo", commandName: "peekaboo",
abstract: "A macOS utility for screen capture, application listing, and window management", 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], subcommands: [ImageCommand.self, ListCommand.self],
defaultSubcommand: ImageCommand.self defaultSubcommand: ImageCommand.self
) )

View file

@ -203,6 +203,37 @@ function checkSwift() {
} }
logSuccess('SwiftLint passed'); 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 // Run Swift tests
if (!execWithOutput('npm run test:swift', 'Swift tests')) { if (!execWithOutput('npm run test:swift', 'Swift tests')) {
logError('Swift tests failed'); logError('Swift tests failed');

View file

@ -14,18 +14,29 @@ export const listToolSchema = z
item_type: z item_type: z
.enum(["running_applications", "application_windows", "server_status"]) .enum(["running_applications", "application_windows", "server_status"])
.default("running_applications") .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 app: z
.string() .string()
.optional() .optional()
.describe( .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 include_window_details: z
.array(z.enum(["off_screen", "bounds", "ids"])) .array(z.enum(["off_screen", "bounds", "ids"]))
.optional() .optional()
.describe( .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( .refine(
@ -54,6 +65,11 @@ export const listToolSchema = z
"'app' and 'include_window_details' not applicable for 'server_status'.", "'app' and 'include_window_details' not applicable for 'server_status'.",
path: ["item_type"], 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<typeof listToolSchema>; export type ListToolInput = z.infer<typeof listToolSchema>;

View file

@ -88,7 +88,7 @@ describe("Swift CLI Integration Tests", () => {
) { ) {
const firstContentItem = response.content[0] as PeekabooContentItem; const firstContentItem = response.content[0] as PeekabooContentItem;
expect(firstContentItem.type).toBe("text"); expect(firstContentItem.type).toBe("text");
expect(firstContentItem.text).toContain("Peekaboo MCP Server Status"); expect(firstContentItem.text).toContain("Peekaboo MCP");
} else { } else {
fail( fail(
"Response content was not in the expected format for server_status", "Response content was not in the expected format for server_status",

View file

@ -1,30 +1,29 @@
// Jest setup file // Vitest setup file
// Configure global test environment // Configure global test environment
import { beforeEach, afterEach, vi } from 'vitest';
// Mock console methods to reduce noise during testing // Mock console methods to reduce noise during testing
const originalConsole = global.console; const originalConsole = globalThis.console;
beforeEach(() => { beforeEach(() => {
// Reset console mocks before each test // Reset console mocks before each test
global.console = { globalThis.console = {
...originalConsole, ...originalConsole,
log: jest.fn(), log: vi.fn(),
error: jest.fn(), error: vi.fn(),
warn: jest.fn(), warn: vi.fn(),
info: jest.fn(), info: vi.fn(),
debug: jest.fn(), debug: vi.fn(),
}; };
}); });
afterEach(() => { afterEach(() => {
// Restore original console after each test // Restore original console after each test
global.console = originalConsole; globalThis.console = originalConsole;
jest.clearAllMocks(); vi.clearAllMocks();
}); });
// Global test timeout
jest.setTimeout(10000);
// Mock environment variables for testing // Mock environment variables for testing
process.env.NODE_ENV = "test"; process.env.NODE_ENV = "test";
process.env.PEEKABOO_AI_PROVIDERS = JSON.stringify([ process.env.PEEKABOO_AI_PROVIDERS = JSON.stringify([
@ -34,4 +33,4 @@ process.env.PEEKABOO_AI_PROVIDERS = JSON.stringify([
model: "llava", model: "llava",
enabled: true, enabled: true,
}, },
]); ]);

View file

@ -10,69 +10,50 @@ describe("Server Status Utility - generateServerStatusString", () => {
it("should return status with default providers text when PEEKABOO_AI_PROVIDERS is not set", () => { it("should return status with default providers text when PEEKABOO_AI_PROVIDERS is not set", () => {
const status = generateServerStatusString(testVersion); const status = generateServerStatusString(testVersion);
expect(status).toContain(`Version: ${testVersion}`); expect(status).toBe(`Peekaboo MCP ${testVersion} using None Configured. Set PEEKABOO_AI_PROVIDERS ENV.`);
expect(status).toContain(
"Configured AI Providers (from PEEKABOO_AI_PROVIDERS ENV): None Configured. Set PEEKABOO_AI_PROVIDERS ENV.",
);
}); });
it("should return status with default providers text when PEEKABOO_AI_PROVIDERS is an empty string", () => { it("should return status with default providers text when PEEKABOO_AI_PROVIDERS is an empty string", () => {
process.env.PEEKABOO_AI_PROVIDERS = ""; process.env.PEEKABOO_AI_PROVIDERS = "";
const status = generateServerStatusString(testVersion); const status = generateServerStatusString(testVersion);
expect(status).toContain(`Version: ${testVersion}`); expect(status).toBe(`Peekaboo MCP ${testVersion} using None Configured. Set PEEKABOO_AI_PROVIDERS ENV.`);
expect(status).toContain(
"Configured AI Providers (from PEEKABOO_AI_PROVIDERS ENV): None Configured. Set PEEKABOO_AI_PROVIDERS ENV.",
);
}); });
it("should return status with default providers text when PEEKABOO_AI_PROVIDERS is whitespace", () => { it("should return status with default providers text when PEEKABOO_AI_PROVIDERS is whitespace", () => {
process.env.PEEKABOO_AI_PROVIDERS = " "; process.env.PEEKABOO_AI_PROVIDERS = " ";
const status = generateServerStatusString(testVersion); const status = generateServerStatusString(testVersion);
expect(status).toContain(`Version: ${testVersion}`); expect(status).toBe(`Peekaboo MCP ${testVersion} using None Configured. Set PEEKABOO_AI_PROVIDERS ENV.`);
expect(status).toContain(
"Configured AI Providers (from PEEKABOO_AI_PROVIDERS ENV): None Configured. Set PEEKABOO_AI_PROVIDERS ENV.",
);
}); });
it("should list a single provider from PEEKABOO_AI_PROVIDERS", () => { it("should list a single provider from PEEKABOO_AI_PROVIDERS", () => {
process.env.PEEKABOO_AI_PROVIDERS = "ollama/llava"; process.env.PEEKABOO_AI_PROVIDERS = "ollama/llava";
const status = generateServerStatusString(testVersion); const status = generateServerStatusString(testVersion);
expect(status).toContain(`Version: ${testVersion}`); expect(status).toBe(`Peekaboo MCP ${testVersion} using ollama/llava`);
expect(status).toContain(
"Configured AI Providers (from PEEKABOO_AI_PROVIDERS ENV): ollama/llava",
);
}); });
it("should list multiple providers from PEEKABOO_AI_PROVIDERS, trimmed and joined", () => { it("should list multiple providers from PEEKABOO_AI_PROVIDERS, trimmed and joined", () => {
process.env.PEEKABOO_AI_PROVIDERS = "ollama/llava, openai/gpt-4o"; process.env.PEEKABOO_AI_PROVIDERS = "ollama/llava, openai/gpt-4o";
const status = generateServerStatusString(testVersion); const status = generateServerStatusString(testVersion);
expect(status).toContain(`Version: ${testVersion}`); expect(status).toBe(`Peekaboo MCP ${testVersion} using ollama/llava, openai/gpt-4o`);
expect(status).toContain(
"Configured AI Providers (from PEEKABOO_AI_PROVIDERS ENV): ollama/llava, openai/gpt-4o",
);
}); });
it("should handle extra whitespace and empty segments in PEEKABOO_AI_PROVIDERS", () => { it("should handle extra whitespace and empty segments in PEEKABOO_AI_PROVIDERS", () => {
process.env.PEEKABOO_AI_PROVIDERS = process.env.PEEKABOO_AI_PROVIDERS =
" ollama/llava , ,, openai/gpt-4o ,anthropic/claude "; " ollama/llava , ,, openai/gpt-4o ,anthropic/claude ";
const status = generateServerStatusString(testVersion); const status = generateServerStatusString(testVersion);
expect(status).toContain(`Version: ${testVersion}`); expect(status).toBe(`Peekaboo MCP ${testVersion} using ollama/llava, openai/gpt-4o, anthropic/claude`);
expect(status).toContain(
"Configured AI Providers (from PEEKABOO_AI_PROVIDERS ENV): ollama/llava, openai/gpt-4o, anthropic/claude",
);
}); });
it("should correctly include the provided version string", () => { it("should correctly include the provided version string", () => {
const customVersion = "z.y.x"; const customVersion = "z.y.x";
const status = generateServerStatusString(customVersion); 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"); 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 leading whitespace
expect(status).not.toMatch(/\s$/); // No trailing whitespace expect(status).not.toMatch(/\s$/); // No trailing whitespace
expect(status.startsWith("Peekaboo MCP")).toBe(true);
}); });
}); });