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",
"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": [

View file

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

View file

@ -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
)

View file

@ -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');

View file

@ -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<typeof listToolSchema>;

View file

@ -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",

View file

@ -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,
},
]);
]);

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", () => {
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);
});
});
});