ucu-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Error taxonomy for UCU-MCP.
3
+ *
4
+ * All errors inherit from UcuError and are categorized by:
5
+ * - code: machine-readable error code
6
+ * - retryable: whether the operation can be retried
7
+ */
8
+ // ---------------------------------------------------------------------------
9
+ // Base Error Class
10
+ // ---------------------------------------------------------------------------
11
+ export class UcuError extends Error {
12
+ code;
13
+ retryable;
14
+ constructor(message, code = "UCU_ERROR", retryable = false) {
15
+ super(message);
16
+ this.name = this.constructor.name;
17
+ this.code = code;
18
+ this.retryable = retryable;
19
+ }
20
+ }
21
+ // ---------------------------------------------------------------------------
22
+ // Platform Errors
23
+ // ---------------------------------------------------------------------------
24
+ /**
25
+ * Native API call failed (permissions, OS error, timeout).
26
+ */
27
+ export class PlatformError extends UcuError {
28
+ constructor(message, retryable = true) {
29
+ super(message, "PLATFORM_ERROR", retryable);
30
+ }
31
+ }
32
+ // ---------------------------------------------------------------------------
33
+ // Safety Errors
34
+ // ---------------------------------------------------------------------------
35
+ /**
36
+ * Action blocked by safety guard.
37
+ */
38
+ export class SafetyError extends UcuError {
39
+ constructor(message) {
40
+ super(message, "SAFETY_BLOCKED", false);
41
+ }
42
+ }
43
+ // ---------------------------------------------------------------------------
44
+ // Permission Errors
45
+ // ---------------------------------------------------------------------------
46
+ /**
47
+ * Missing OS accessibility/screen-recording permissions.
48
+ */
49
+ export class PermissionError extends UcuError {
50
+ constructor(permission, platform) {
51
+ super(getPermissionMessage(permission, platform), "PERMISSION_DENIED", false);
52
+ }
53
+ }
54
+ function getPermissionMessage(permission, platform) {
55
+ if (platform === "darwin") {
56
+ return `Missing ${permission} permission. Grant it in System Settings > Privacy & Security > ${permission}.`;
57
+ }
58
+ return `Missing ${permission} permission for this operation.`;
59
+ }
60
+ // ---------------------------------------------------------------------------
61
+ // Window Errors
62
+ // ---------------------------------------------------------------------------
63
+ /**
64
+ * Requested window ID no longer exists.
65
+ */
66
+ export class WindowNotFoundError extends UcuError {
67
+ constructor(windowId) {
68
+ super(`Window ${windowId} not found. It may have been closed. Run list_windows to get fresh IDs.`, "WINDOW_NOT_FOUND", false);
69
+ }
70
+ }
71
+ // ---------------------------------------------------------------------------
72
+ // Input Errors
73
+ // ---------------------------------------------------------------------------
74
+ /**
75
+ * Click/scroll target is outside screen bounds.
76
+ */
77
+ export class CoordinateError extends UcuError {
78
+ constructor(x, y, bounds) {
79
+ super(`Coordinate (${x}, ${y}) is outside screen bounds (0-${bounds.width}, 0-${bounds.height}).`, "COORDINATE_OUT_OF_BOUNDS", false);
80
+ }
81
+ }
82
+ /**
83
+ * Keystroke or mouse event injection failed.
84
+ */
85
+ export class InputSynthesisError extends UcuError {
86
+ constructor(message) {
87
+ super(message, "INPUT_FAILED", true);
88
+ }
89
+ }
90
+ /**
91
+ * The request is well-formed JSON, but asks for a parameter combination this
92
+ * implementation does not support.
93
+ */
94
+ export class UnsupportedParameterError extends UcuError {
95
+ constructor(message) {
96
+ super(message, "UNSUPPORTED_PARAMETER", false);
97
+ }
98
+ }
99
+ // ---------------------------------------------------------------------------
100
+ // Capture Errors
101
+ // ---------------------------------------------------------------------------
102
+ /**
103
+ * Screenshot or window-state capture failed.
104
+ */
105
+ export class CaptureError extends UcuError {
106
+ constructor(message) {
107
+ super(message, "CAPTURE_FAILED", true);
108
+ }
109
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Structured logging for UCU-MCP.
3
+ *
4
+ * Pino-compatible interface built on console — no external dependency.
5
+ * Each log entry is a JSON line with level, timestamp, name, and
6
+ * optional correlationId plus arbitrary structured fields.
7
+ *
8
+ * Usage:
9
+ * import { createLogger } from "../util/logger.js";
10
+ * const log = createLogger("tools");
11
+ * log.info("Tool executed", { tool: "click", duration: 42 });
12
+ * log.error("Tool failed", { error: "boom" });
13
+ *
14
+ * Correlation IDs are scoped per-logger via `withCorrelationId`:
15
+ * const log = createLogger("safety").withCorrelationId("req-123");
16
+ * log.info("check passed"); // → { ..., correlationId: "req-123" }
17
+ */
18
+ interface LogFields {
19
+ [key: string]: unknown;
20
+ }
21
+ interface Logger {
22
+ info(message: string, fields?: LogFields): void;
23
+ warn(message: string, fields?: LogFields): void;
24
+ error(message: string, fields?: LogFields): void;
25
+ debug(message: string, fields?: LogFields): void;
26
+ withCorrelationId(id: string): Logger;
27
+ }
28
+ /**
29
+ * Create a named logger instance.
30
+ *
31
+ * @param name - Module / subsystem name (included in every log entry)
32
+ * @returns Logger with info, warn, error, debug, and withCorrelationId
33
+ */
34
+ declare function createLogger(name: string): Logger;
35
+ /**
36
+ * Default root logger for backward compatibility.
37
+ * Prefer `createLogger("module")` for new code.
38
+ */
39
+ declare const logger: Logger;
40
+ export { createLogger, logger };
41
+ export type { Logger, LogFields };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Structured logging for UCU-MCP.
3
+ *
4
+ * Pino-compatible interface built on console — no external dependency.
5
+ * Each log entry is a JSON line with level, timestamp, name, and
6
+ * optional correlationId plus arbitrary structured fields.
7
+ *
8
+ * Usage:
9
+ * import { createLogger } from "../util/logger.js";
10
+ * const log = createLogger("tools");
11
+ * log.info("Tool executed", { tool: "click", duration: 42 });
12
+ * log.error("Tool failed", { error: "boom" });
13
+ *
14
+ * Correlation IDs are scoped per-logger via `withCorrelationId`:
15
+ * const log = createLogger("safety").withCorrelationId("req-123");
16
+ * log.info("check passed"); // → { ..., correlationId: "req-123" }
17
+ */
18
+ // ── Level precedence ──────────────────────────────────────────────────────
19
+ const LEVEL_ORDER = {
20
+ debug: 0,
21
+ info: 1,
22
+ warn: 2,
23
+ error: 3,
24
+ };
25
+ function resolveMinLevel() {
26
+ const env = process.env.UCU_LOG_LEVEL?.toLowerCase();
27
+ if (env && env in LEVEL_ORDER)
28
+ return env;
29
+ return "info";
30
+ }
31
+ const MIN_LEVEL = LEVEL_ORDER[resolveMinLevel()];
32
+ // ── JSON formatting ───────────────────────────────────────────────────────
33
+ function formatEntry(level, name, message, correlationId, fields) {
34
+ const entry = {
35
+ level,
36
+ time: Date.now(),
37
+ timestamp: new Date().toISOString(),
38
+ name,
39
+ msg: message,
40
+ };
41
+ if (correlationId)
42
+ entry.correlationId = correlationId;
43
+ if (fields)
44
+ Object.assign(entry, fields);
45
+ return JSON.stringify(entry);
46
+ }
47
+ // ── Logger implementation ─────────────────────────────────────────────────
48
+ class ConsoleLogger {
49
+ _name;
50
+ _correlationId;
51
+ constructor(name, correlationId) {
52
+ this._name = name;
53
+ this._correlationId = correlationId;
54
+ }
55
+ emit(level, message, fields) {
56
+ if (LEVEL_ORDER[level] < MIN_LEVEL)
57
+ return;
58
+ // Write to stderr so stdout stays clean for MCP transport
59
+ console.error(formatEntry(level, this._name, message, this._correlationId, fields));
60
+ }
61
+ info(message, fields) {
62
+ this.emit("info", message, fields);
63
+ }
64
+ warn(message, fields) {
65
+ this.emit("warn", message, fields);
66
+ }
67
+ error(message, fields) {
68
+ this.emit("error", message, fields);
69
+ }
70
+ debug(message, fields) {
71
+ this.emit("debug", message, fields);
72
+ }
73
+ withCorrelationId(id) {
74
+ return new ConsoleLogger(this._name, id);
75
+ }
76
+ }
77
+ // ── Public API ────────────────────────────────────────────────────────────
78
+ /**
79
+ * Create a named logger instance.
80
+ *
81
+ * @param name - Module / subsystem name (included in every log entry)
82
+ * @returns Logger with info, warn, error, debug, and withCorrelationId
83
+ */
84
+ function createLogger(name) {
85
+ return new ConsoleLogger(name);
86
+ }
87
+ /**
88
+ * Default root logger for backward compatibility.
89
+ * Prefer `createLogger("module")` for new code.
90
+ */
91
+ const logger = createLogger("ucu-mcp");
92
+ export { createLogger, logger };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Retry with exponential backoff for flaky platform calls.
3
+ *
4
+ * Only retries when the thrown error is a `UcuError` with `retryable === true`,
5
+ * unless a custom `shouldRetry` predicate overrides the decision.
6
+ */
7
+ export interface RetryOptions {
8
+ /** Maximum number of retries (not including the initial attempt). Default: 3 */
9
+ maxRetries?: number;
10
+ /** Base delay in milliseconds for the first retry. Default: 100 */
11
+ baseDelay?: number;
12
+ /** Ceiling for the backoff delay in milliseconds. Default: 5000 */
13
+ maxDelay?: number;
14
+ /** Custom predicate to decide whether an error is retryable.
15
+ * Defaults to `(err) => err instanceof UcuError && err.retryable === true`. */
16
+ shouldRetry?: (error: unknown) => boolean;
17
+ }
18
+ /**
19
+ * Execute `fn` and retry with exponential backoff on retryable errors.
20
+ *
21
+ * The delay for retry attempt *n* is `baseDelay * 2^(n-1)`, capped at
22
+ * `maxDelay`. Only errors that are `UcuError` with `retryable === true`
23
+ * (or that pass the custom `shouldRetry` predicate) trigger a retry.
24
+ * All other errors propagate immediately.
25
+ *
26
+ * @param fn - The async function to execute.
27
+ * @param options - Retry configuration.
28
+ * @returns The resolved value of `fn`.
29
+ */
30
+ export declare function retry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Retry with exponential backoff for flaky platform calls.
3
+ *
4
+ * Only retries when the thrown error is a `UcuError` with `retryable === true`,
5
+ * unless a custom `shouldRetry` predicate overrides the decision.
6
+ */
7
+ import { UcuError } from "./errors.js";
8
+ // ---------------------------------------------------------------------------
9
+ // Defaults
10
+ // ---------------------------------------------------------------------------
11
+ const DEFAULT_MAX_RETRIES = 3;
12
+ const DEFAULT_BASE_DELAY = 100;
13
+ const DEFAULT_MAX_DELAY = 5000;
14
+ // ---------------------------------------------------------------------------
15
+ // Implementation
16
+ // ---------------------------------------------------------------------------
17
+ /**
18
+ * Execute `fn` and retry with exponential backoff on retryable errors.
19
+ *
20
+ * The delay for retry attempt *n* is `baseDelay * 2^(n-1)`, capped at
21
+ * `maxDelay`. Only errors that are `UcuError` with `retryable === true`
22
+ * (or that pass the custom `shouldRetry` predicate) trigger a retry.
23
+ * All other errors propagate immediately.
24
+ *
25
+ * @param fn - The async function to execute.
26
+ * @param options - Retry configuration.
27
+ * @returns The resolved value of `fn`.
28
+ */
29
+ export async function retry(fn, options) {
30
+ const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;
31
+ const baseDelay = options?.baseDelay ?? DEFAULT_BASE_DELAY;
32
+ const maxDelay = options?.maxDelay ?? DEFAULT_MAX_DELAY;
33
+ const shouldRetry = options?.shouldRetry ??
34
+ ((err) => err instanceof UcuError && err.retryable === true);
35
+ let lastError;
36
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
37
+ try {
38
+ return await fn();
39
+ }
40
+ catch (error) {
41
+ lastError = error;
42
+ // If we've exhausted all retries or the error is not retryable, throw.
43
+ if (attempt === maxRetries || !shouldRetry(error)) {
44
+ throw error;
45
+ }
46
+ // Exponential backoff: baseDelay * 2^attempt, capped at maxDelay.
47
+ const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
48
+ await new Promise((resolve) => setTimeout(resolve, delay));
49
+ }
50
+ }
51
+ // Unreachable — the loop either returns or throws.
52
+ throw lastError;
53
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Cross-platform input synthesis for UCU-MCP.
3
+ *
4
+ * macOS: Uses CGEvent API exclusively for BACKGROUND input injection.
5
+ * This does NOT activate windows or steal focus — the AI agent can
6
+ * control the desktop while the user continues working in another
7
+ * terminal/window without interruption.
8
+ *
9
+ * Windows: Uses SendInput (stub).
10
+ * Linux: Uses xdotool (stub).
11
+ */
12
+ export declare function click(x: number, y: number, button?: "left" | "right" | "middle", _platform?: string): Promise<void>;
13
+ export declare function doubleClick(x: number, y: number, button?: "left" | "right" | "middle", _platform?: string): Promise<void>;
14
+ export declare function move(x: number, y: number, _platform?: string): Promise<void>;
15
+ export declare function drag(fromX: number, fromY: number, toX: number, toY: number, button?: "left" | "right" | "middle", duration?: number, _platform?: string): Promise<void>;
16
+ export declare function scroll(x: number, y: number, deltaX: number, deltaY: number, _platform?: string): Promise<void>;
17
+ export declare function typeText(text: string, delay?: number, _platform?: string): Promise<void>;
18
+ export declare function pressKey(key: string, modifiers?: string[], _platform?: string): Promise<void>;
19
+ export declare function pressShortcut(keys: string[], _platform?: string): Promise<void>;
20
+ export declare function getCursorPosition(_platform?: string): Promise<{
21
+ x: number;
22
+ y: number;
23
+ }>;