diff --git a/.gitignore b/.gitignore index e90b3df5..d8463c69 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,8 @@ test_files/ test_fix.js test_search.js .ripgrep-downloads/ +.build/ +.claude +.build/ +bin/macos/macos-ax-helper-darwin-arm64 +bin/macos/macos-ax-helper-darwin-x64 diff --git a/FAQ.md b/FAQ.md index c4ead1b1..609bde4c 100644 --- a/FAQ.md +++ b/FAQ.md @@ -52,6 +52,8 @@ This document provides answers to the most commonly asked questions about Claude - [Troubleshooting](#troubleshooting) - [Claude says it doesn't have permission to access my files/directories](#claude-says-it-doesnt-have-permission-to-access-my-filesdirectories) + - [macOS Accessibility tools return permission errors](#macos-accessibility-tools-return-permission-errors) + - [Electron debug tools cannot attach](#electron-debug-tools-cannot-attach) - [Claude keeps hitting token/output limits](#claude-keeps-hitting-tokenoutput-limits) - [Installation fails on my system](#installation-fails-on-my-system) @@ -485,6 +487,27 @@ Recent updates have removed directory restrictions. If you're still experiencing 3. When Claude asks for permission to use tools, approve for the entire chat 4. Check if there are any specific permission issues with the directory in question (file permissions, etc.) +### macOS Accessibility tools return permission errors + +If `macos_ax_*` tools fail with permission messages: +1. Open **System Settings → Privacy & Security → Accessibility** +2. Ensure the process running Desktop Commander MCP is enabled +3. Restart Claude/Desktop Commander after changing permissions +4. Run `macos_ax_status` to confirm `hasPermission: true` + +If helper binary issues are reported, build native binaries with: +```bash +./build-macos-helper.sh +``` + +### Electron debug tools cannot attach + +If `electron_debug_attach` fails: +1. Launch the target app with a debug port enabled (for example `--remote-debugging-port=9222`) +2. Confirm endpoint is reachable at `http://127.0.0.1:9222/json/list` +3. Use the correct host/port in `electron_debug_attach` +4. Disconnect stale sessions with `electron_debug_disconnect` and retry + ### Claude keeps hitting token/output limits Claude Desktop has certain limits on message size. When working with large codebases or extensive outputs, you might encounter these limits. Some strategies to work around them: @@ -584,4 +607,4 @@ Jupyter notebooks and Claude Desktop Commander serve different purposes: - Visual output for data visualization - More structured for educational purposes -For data science or analysis projects, you might use both: Claude Desktop Commander for system tasks and code management, and Jupyter for interactive exploration and visualization. \ No newline at end of file +For data science or analysis projects, you might use both: Claude Desktop Commander for system tasks and code management, and Jupyter for interactive exploration and visualization. diff --git a/README.md b/README.md index 96012eb9..9d09b214 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Work with code and text, run processes, and automate tasks, going far beyond oth - [Remote MCP (ChatGPT, Claude Web)](#remote-mcp-chatgpt-claude-web) - [Getting Started](#getting-started) - [Usage](#usage) +- [macOS Accessibility + Electron Debug Control](#macos-accessibility--electron-debug-control) - [Handling Long-Running Commands](#handling-long-running-commands) - [Work in Progress and TODOs](#roadmap) - [Sponsors and Supporters](#support-desktop-commander) @@ -452,6 +453,18 @@ The server provides a comprehensive set of tools organized into several categori | | `list_sessions` | List all active terminal sessions | | | `list_processes` | List all running processes with detailed information | | | `kill_process` | Terminate a running process by PID | +| **macOS Control** | `macos_ax_status` | Check macOS Accessibility helper status, permissions, and diagnostics | +| | `macos_ax_list_apps` | List macOS apps available for Accessibility automation | +| | `macos_ax_find` | Find interactive Accessibility elements in an app using text/role filters | +| | `macos_ax_click` | Click Accessibility elements by stable id or app+text lookup | +| | `macos_ax_type` | Type text with keyboard event injection | +| | `macos_ax_key` | Press key combinations (e.g. cmd+c, cmd+v, return) | +| | `macos_ax_activate` | Bring an app to front by name or PID | +| | `macos_ax_wait_for` | Wait for a matching UI element to appear | +| | `macos_ax_batch` | Execute multi-step AX automations in one call | +| **Electron Debugger** | `electron_debug_attach` | Attach to a CDP endpoint (Electron/Chromium debug port) | +| | `electron_debug_eval` | Evaluate JavaScript through an attached CDP session | +| | `electron_debug_disconnect` | Disconnect an active CDP session | | **Filesystem** | `read_file` | Read contents from local filesystem, URLs, Excel files (.xlsx, .xls, .xlsm), and PDFs with line/page-based pagination | | | `read_multiple_files` | Read multiple files simultaneously | | | `write_file` | Write file contents with options for rewrite or append mode. Supports Excel files (JSON 2D array format). For PDFs, use `write_pdf` | @@ -782,6 +795,46 @@ Troubleshooting: - When properly connected, the process will continue execution after hitting the first breakpoint - You can add additional breakpoints in your IDE once connected +## macOS Accessibility + Electron Debug Control + +Desktop Commander now includes a macOS native Accessibility helper and a minimal Electron CDP adapter. + +### Setup + +1. Build helper binaries on macOS: +```bash +./build-macos-helper.sh +``` + +2. Grant Accessibility permission to the MCP host process: +- System Settings → Privacy & Security → Accessibility + +3. Verify status: +- Call `macos_ax_status` and confirm `hasPermission: true` + +### Core tool flow + +1. `macos_ax_list_apps` +2. `macos_ax_find` +3. `macos_ax_click` +4. `macos_ax_wait_for` for async UI transitions +5. `macos_ax_batch` for multi-step actions + +For Electron apps launched with `--remote-debugging-port`: +1. `electron_debug_attach` +2. `electron_debug_eval` +3. `electron_debug_disconnect` + +### Tests + +```bash +node test/test-macos-control.js +node test/test-electron-debug.js +``` + +Detailed guide: `macos-control.md` +Known caveats: `known-limitations.md` + ## Model Context Protocol Integration This project extends the MCP Filesystem Server to enable: diff --git a/build-macos-helper.sh b/build-macos-helper.sh new file mode 100755 index 00000000..3153de07 --- /dev/null +++ b/build-macos-helper.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")" && pwd)" +HELPER_DIR="$ROOT_DIR/native/macos-ax-helper" +OUT_DIR="$ROOT_DIR/bin/macos" + +if [[ "$(uname -s)" != "Darwin" ]]; then + echo "build-macos-helper.sh is macOS-only" + exit 1 +fi + +mkdir -p "$OUT_DIR" + +build_arch() { + local arch="$1" + local out_name="$2" + + echo "Building macos-ax-helper for $arch..." + if swift build --package-path "$HELPER_DIR" -c release --arch "$arch"; then + local built="$HELPER_DIR/.build/apple/Products/Release/macos-ax-helper" + + if [[ ! -f "$built" ]]; then + built="$HELPER_DIR/.build/release/macos-ax-helper" + fi + + if [[ ! -f "$built" ]]; then + echo "Could not find built helper for $arch" + return 1 + fi + + cp "$built" "$OUT_DIR/$out_name" + chmod +x "$OUT_DIR/$out_name" + echo "Wrote $OUT_DIR/$out_name" + else + echo "Warning: build failed for $arch" + return 1 + fi +} + +status=0 +build_arch arm64 macos-ax-helper-darwin-arm64 || status=1 +build_arch x86_64 macos-ax-helper-darwin-x64 || status=1 + +if [[ $status -ne 0 ]]; then + echo "One or more architectures failed. If running on Apple Silicon, x64 build may require extra toolchains." +fi + +exit $status diff --git a/known-limitations.md b/known-limitations.md new file mode 100644 index 00000000..a20b2713 --- /dev/null +++ b/known-limitations.md @@ -0,0 +1,20 @@ +# Known Limitations + +## Accessibility path + +- macOS-only: AX tools return unsupported platform on non-macOS hosts. +- Requires explicit Accessibility permission for the MCP host process. +- AX trees differ by app and can be incomplete or unstable in custom UI frameworks. +- Stable IDs are best-effort hashes of visible properties; some dynamic UIs can invalidate them quickly. +- Stale-ID fallback depends on prior element signatures and may fail in heavily changing layouts. + +## Electron/CDP path + +- Requires app/browser launched with remote debugging endpoint enabled. +- Current CDP tool set is intentionally minimal: attach/eval/disconnect. +- DOM interaction helpers are not yet first-class MCP tools (can be done via JavaScript in `electron_debug_eval`). + +## Packaging/build + +- Native helper binaries are architecture-specific (`darwin-arm64`, `darwin-x64`). +- Building both architectures from one machine may require additional toolchain support. diff --git a/macos-control.md b/macos-control.md new file mode 100644 index 00000000..4ae1ca60 --- /dev/null +++ b/macos-control.md @@ -0,0 +1,141 @@ +# macOS Control in Desktop Commander MCP + +This guide covers the macOS Accessibility (AX) and Electron debugger control features added to Desktop Commander MCP. + +## Overview + +The implementation has two control paths: + +1. Accessibility path (native apps and Electron UI chrome) +- Uses a native Swift helper (`macos-ax-helper`) to query and act on AX elements. +- Supports finding/clicking elements, typing, keypress, waiting for UI state, and batching actions. + +2. Debugger path (Electron/Chromium web content) +- Uses Chrome DevTools Protocol (CDP) over WebSocket. +- Supports attach, JavaScript evaluation, and disconnect. + +## New MCP tools + +Accessibility tools: +- `macos_ax_status` +- `macos_ax_list_apps` +- `macos_ax_find` +- `macos_ax_click` +- `macos_ax_type` +- `macos_ax_key` +- `macos_ax_activate` +- `macos_ax_wait_for` +- `macos_ax_batch` + +Electron debug tools: +- `electron_debug_attach` +- `electron_debug_eval` +- `electron_debug_disconnect` + +## Native helper contract + +`macos-ax-helper` reads one JSON request from `stdin` and returns one JSON response to `stdout`. + +Request shape: +```json +{ + "requestId": "optional-id", + "command": "list_elements", + "args": {} +} +``` + +Success response: +```json +{ + "ok": true, + "data": {}, + "meta": {"requestId": "optional-id", "durationMs": 12} +} +``` + +Error response: +```json +{ + "ok": false, + "error": { + "code": "PERMISSION_DENIED", + "message": "Accessibility permissions not granted", + "details": {} + }, + "meta": {"requestId": "optional-id", "durationMs": 9} +} +``` + +Current helper commands: +- `status` +- `list_apps` +- `list_elements` +- `click` +- `type_text` +- `press_key` +- `activate` +- `wait_for` + +## Build helper binaries + +From repo root: + +```bash +./build-macos-helper.sh +``` + +Expected outputs: +- `bin/macos/macos-ax-helper-darwin-arm64` +- `bin/macos/macos-ax-helper-darwin-x64` + +If only one architecture can be built on your machine, the script exits non-zero and prints guidance. + +## Permissions setup + +Grant Accessibility permission to the process running Desktop Commander MCP. + +macOS path: +- System Settings -> Privacy & Security -> Accessibility + +Use `macos_ax_status` to verify permission state and process identification details. + +## Suggested usage flow + +1. `macos_ax_status` +2. `macos_ax_list_apps` +3. `macos_ax_find` (app + text/role) +4. `macos_ax_click` (prefer by `id`) +5. `macos_ax_wait_for` when UI transitions are asynchronous +6. `macos_ax_batch` for multi-step flows + +For Electron debug workflows: +1. Launch target with remote debugging enabled +2. `electron_debug_attach` +3. `electron_debug_eval` +4. `electron_debug_disconnect` + +## Test steps + +1. Build TypeScript: +```bash +npm run build +``` + +2. Run focused tests: +```bash +node test/test-macos-control.js +node test/test-electron-debug.js +``` + +3. Optional: run the full test suite: +```bash +npm test +``` + +## Optimization opportunities + +- Keep a persistent helper process to avoid process spawn overhead per AX call. +- Add element path locators (`AXParent` chain) for stronger stale-ID recovery. +- Add incremental snapshot mode to avoid full tree scans on repeated `find`. +- Add richer CDP APIs (`DOM.querySelector`, click/type helpers) on top of `electron_debug_eval`. diff --git a/manifest.template.json b/manifest.template.json index 65528b4c..5c14dbd9 100644 --- a/manifest.template.json +++ b/manifest.template.json @@ -135,6 +135,54 @@ "name": "give_feedback_to_desktop_commander", "description": "Open feedback form in browser to provide feedback about Desktop Commander." }, + { + "name": "macos_ax_status", + "description": "Check macOS Accessibility automation status, helper readiness, and permissions." + }, + { + "name": "macos_ax_list_apps", + "description": "List regular macOS apps available for Accessibility control." + }, + { + "name": "macos_ax_find", + "description": "Find interactive macOS Accessibility elements in an app with text/role filtering." + }, + { + "name": "macos_ax_click", + "description": "Click a macOS Accessibility element by stable id or by app+text lookup." + }, + { + "name": "macos_ax_type", + "description": "Type text with macOS keyboard event injection." + }, + { + "name": "macos_ax_key", + "description": "Press a key with optional modifiers using macOS keyboard events." + }, + { + "name": "macos_ax_activate", + "description": "Activate a macOS app window by name or PID." + }, + { + "name": "macos_ax_wait_for", + "description": "Wait for a matching macOS Accessibility element to appear." + }, + { + "name": "macos_ax_batch", + "description": "Run a sequence of macOS Accessibility actions in one request." + }, + { + "name": "electron_debug_attach", + "description": "Attach to an Electron/Chromium CDP endpoint and create a debug session." + }, + { + "name": "electron_debug_eval", + "description": "Evaluate JavaScript through an attached Electron debug session." + }, + { + "name": "electron_debug_disconnect", + "description": "Disconnect an existing Electron debug session." + }, { "name": "get_prompts", "description": "Browse and retrieve curated Desktop Commander prompts for various tasks and workflows." @@ -164,4 +212,4 @@ "node": ">=18.0.0" } } -} \ No newline at end of file +} diff --git a/native/macos-ax-helper/Package.swift b/native/macos-ax-helper/Package.swift new file mode 100644 index 00000000..1f0f27de --- /dev/null +++ b/native/macos-ax-helper/Package.swift @@ -0,0 +1,18 @@ +// swift-tools-version: 5.9 +import PackageDescription + +let package = Package( + name: "macos-ax-helper", + platforms: [ + .macOS(.v12) + ], + products: [ + .executable(name: "macos-ax-helper", targets: ["macos-ax-helper"]) + ], + targets: [ + .executableTarget( + name: "macos-ax-helper", + path: "Sources/macos-ax-helper" + ) + ] +) diff --git a/native/macos-ax-helper/Sources/macos-ax-helper/main.swift b/native/macos-ax-helper/Sources/macos-ax-helper/main.swift new file mode 100644 index 00000000..6a01e37c --- /dev/null +++ b/native/macos-ax-helper/Sources/macos-ax-helper/main.swift @@ -0,0 +1,1564 @@ +import Foundation +import AppKit +import ApplicationServices +import CryptoKit + +let helperVersion = "0.1.0" + +let interactiveRoles: Set = [ + "AXButton", "AXCheckBox", "AXRadioButton", "AXTextField", "AXTextArea", "AXSecureTextField", + "AXComboBox", "AXPopUpButton", "AXSlider", "AXLink", "AXMenuItem", "AXMenuButton", "AXMenu", + "AXMenuBar", "AXMenuBarItem", "AXTab", "AXTabGroup", "AXToolbar", "AXToolbarButton", "AXIncrementor", + "AXColorWell", "AXDateField", "AXDisclosureTriangle", "AXList", "AXOutline", "AXTable", "AXCell", + "AXRow", "AXColumn", "AXScrollBar", "AXSplitter", "AXSearchField", "AXSegmentedControl", "AXStepper", + "AXSwitch", "AXToggle" +] + +let keyCodes: [String: CGKeyCode] = [ + "a": 0, "b": 11, "c": 8, "d": 2, "e": 14, "f": 3, "g": 5, "h": 4, + "i": 34, "j": 38, "k": 40, "l": 37, "m": 46, "n": 45, "o": 31, "p": 35, + "q": 12, "r": 15, "s": 1, "t": 17, "u": 32, "v": 9, "w": 13, "x": 7, + "y": 16, "z": 6, + "1": 18, "2": 19, "3": 20, "4": 21, "5": 23, "6": 22, "7": 26, "8": 28, + "9": 25, "0": 29, + " ": 49, "\n": 36, "\t": 48, + "-": 27, "=": 24, "[": 33, "]": 30, "\\": 42, ";": 41, "'": 39, + ",": 43, ".": 47, "/": 44, "`": 50, + "return": 36, "tab": 48, "space": 49, "delete": 51, "escape": 53, + "left": 123, "right": 124, "down": 125, "up": 126, +] + +let shiftChars: [Character: Character] = [ + "!": "1", "@": "2", "#": "3", "$": "4", "%": "5", "^": "6", "&": "7", + "*": "8", "(": "9", ")": "0", "_": "-", "+": "=", "{": "[", "}": "]", + "|": "\\", ":": ";", "\"": "'", "<": ",", ">": ".", "?": "/", "~": "`" +] + +let modifierFlags: [String: CGEventFlags] = [ + "cmd": .maskCommand, + "shift": .maskShift, + "alt": .maskAlternate, + "ctrl": .maskControl, +] + +enum HelperCode: String { + case unsupportedPlatform = "UNSUPPORTED_PLATFORM" + case permissionDenied = "PERMISSION_DENIED" + case invalidArgument = "INVALID_ARGUMENT" + case notFound = "NOT_FOUND" + case timeout = "TIMEOUT" + case actionFailed = "ACTION_FAILED" + case internalError = "INTERNAL_ERROR" +} + +func makeMeta(requestId: String?, startedAt: Date) -> [String: Any] { + let durationMs = Int(Date().timeIntervalSince(startedAt) * 1000) + var meta: [String: Any] = ["durationMs": durationMs] + if let requestId { + meta["requestId"] = requestId + } + return meta +} + +func printResponse(_ object: [String: Any]) { + do { + let data = try JSONSerialization.data(withJSONObject: object, options: []) + if let text = String(data: data, encoding: .utf8) { + print(text) + } else { + print("{\"ok\":false,\"error\":{\"code\":\"INTERNAL_ERROR\",\"message\":\"Failed to encode response\"}}") + } + } catch { + print("{\"ok\":false,\"error\":{\"code\":\"INTERNAL_ERROR\",\"message\":\"Response serialization failed\"}}") + } +} + +func successResponse(data: Any, requestId: String?, startedAt: Date) { + printResponse([ + "ok": true, + "data": data, + "meta": makeMeta(requestId: requestId, startedAt: startedAt), + ]) +} + +func errorResponse(code: HelperCode, message: String, details: [String: Any]? = nil, requestId: String?, startedAt: Date) { + var errorObject: [String: Any] = [ + "code": code.rawValue, + "message": message, + ] + + if let details { + errorObject["details"] = details + } + + printResponse([ + "ok": false, + "error": errorObject, + "meta": makeMeta(requestId: requestId, startedAt: startedAt), + ]) +} + +func isMacOS() -> Bool { + #if os(macOS) + return true + #else + return false + #endif +} + +func accessibilityTrusted(prompt: Bool = false) -> Bool { + let promptKey = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String + let options = [promptKey: prompt] as CFDictionary + return AXIsProcessTrustedWithOptions(options) +} + +func processInfoString() -> String { + let executable = CommandLine.arguments.first ?? "unknown" + return "PID: \(ProcessInfo.processInfo.processIdentifier), Executable: \(executable)" +} + +func axGetAttribute(_ element: AXUIElement, _ attribute: String) -> AnyObject? { + var value: CFTypeRef? + let error = AXUIElementCopyAttributeValue(element, attribute as CFString, &value) + if error == .success, let value { + return value as AnyObject + } + return nil +} + +func axGetAttributeNames(_ element: AXUIElement) -> [String] { + var names: CFArray? + let error = AXUIElementCopyAttributeNames(element, &names) + if error == .success, let names { + return names as? [String] ?? [] + } + return [] +} + +func axGetActions(_ element: AXUIElement) -> [String] { + var actions: CFArray? + let error = AXUIElementCopyActionNames(element, &actions) + if error == .success, let actions { + return actions as? [String] ?? [] + } + return [] +} + +func axElement(from value: AnyObject?) -> AXUIElement? { + guard let value else { return nil } + guard CFGetTypeID(value) == AXUIElementGetTypeID() else { return nil } + return unsafeBitCast(value, to: AXUIElement.self) +} + +func axPerformAction(_ element: AXUIElement, _ action: String) -> Bool { + AXUIElementPerformAction(element, action as CFString) == .success +} + +func axPoint(from value: AnyObject?) -> CGPoint? { + guard let value else { return nil } + guard CFGetTypeID(value) == AXValueGetTypeID() else { return nil } + let axValue = value as! AXValue + if AXValueGetType(axValue) != .cgPoint { return nil } + var point = CGPoint.zero + if AXValueGetValue(axValue, .cgPoint, &point) { + return point + } + return nil +} + +func axSize(from value: AnyObject?) -> CGSize? { + guard let value else { return nil } + guard CFGetTypeID(value) == AXValueGetTypeID() else { return nil } + let axValue = value as! AXValue + if AXValueGetType(axValue) != .cgSize { return nil } + var size = CGSize.zero + if AXValueGetValue(axValue, .cgSize, &size) { + return size + } + return nil +} + +func isElementVisibleOnScreen(bounds: CGRect) -> Bool { + if bounds.width <= 0 || bounds.height <= 0 { + return false + } + + for screen in NSScreen.screens { + if screen.frame.intersects(bounds) { + return true + } + } + + return false +} + +func elementTitle(role: String, title: String, value: AnyObject?) -> String { + if !title.isEmpty { + return title + } + + if let stringValue = value as? String, !stringValue.isEmpty { + return String(stringValue.prefix(120)) + } + + if role == "AXRow" || role == "AXCell" || role == "AXGroup" { + return "" + } + + return "" +} + +func firstStaticTextValue(in element: AXUIElement, maxDepth: Int = 2, currentDepth: Int = 0) -> String? { + if currentDepth > maxDepth { + return nil + } + + let children = axGetAttribute(element, kAXChildrenAttribute as String) as? [AnyObject] ?? [] + for child in children { + guard CFGetTypeID(child) == AXUIElementGetTypeID() else { + continue + } + + let childElement = child as! AXUIElement + let childRole = (axGetAttribute(childElement, kAXRoleAttribute as String) as? String) ?? "" + if childRole == "AXStaticText", + let value = axGetAttribute(childElement, kAXValueAttribute as String) as? String, + !value.isEmpty { + return String(value.prefix(120)) + } + + if let nested = firstStaticTextValue(in: childElement, maxDepth: maxDepth, currentDepth: currentDepth + 1) { + return nested + } + } + + return nil +} + +func boolLikeValue(_ value: AnyObject?) -> Bool? { + if let boolValue = value as? Bool { + return boolValue + } + + if let numberValue = value as? NSNumber { + return numberValue.intValue != 0 + } + + return nil +} + +func stableElementId(pid: pid_t, role: String, title: String, bounds: CGRect) -> String { + let payload = "\(pid):\(role):\(title):\(Int(bounds.origin.x)):\(Int(bounds.origin.y)):\(Int(bounds.width)):\(Int(bounds.height))" + let digest = SHA256.hash(data: Data(payload.utf8)) + let hex = digest.map { String(format: "%02x", $0) }.joined() + return "\(pid)-\(hex.prefix(12))" +} + +func visibleWindowOwnerPIDs() -> Set { + var pids = Set() + + guard let windowList = CGWindowListCopyWindowInfo( + [.optionOnScreenOnly, .excludeDesktopElements], + kCGNullWindowID + ) as? [[String: Any]] else { + return pids + } + + for window in windowList { + if let ownerPID = window[kCGWindowOwnerPID as String] as? NSNumber { + pids.insert(pid_t(ownerPID.int32Value)) + } + } + + return pids +} + +func runningApps(visibleOnly: Bool = false) -> [NSRunningApplication] { + let apps = NSWorkspace.shared.runningApplications.filter { $0.activationPolicy == .regular } + guard visibleOnly else { + return apps + } + + let visiblePIDs = visibleWindowOwnerPIDs() + return apps.filter { visiblePIDs.contains($0.processIdentifier) } +} + +func appName(_ app: NSRunningApplication) -> String { + app.localizedName ?? "Unknown" +} + +func parseAppIdentifier(_ value: Any?) -> String? { + if let string = value as? String, !string.isEmpty { + return string + } + if let number = value as? NSNumber { + return String(number.intValue) + } + return nil +} + +func findApp(_ identifier: String) -> NSRunningApplication? { + let apps = runningApps() + + if let pid = Int32(identifier) { + return apps.first(where: { $0.processIdentifier == pid }) + } + + let lower = identifier.lowercased() + if let exact = apps.first(where: { (appName($0)).lowercased() == lower }) { + return exact + } + + return apps.first(where: { appName($0).lowercased().contains(lower) }) +} + +func appWindows(_ axApp: AXUIElement) -> [AXUIElement] { + let values = axGetAttribute(axApp, kAXWindowsAttribute as String) as? [AnyObject] ?? [] + return values.compactMap { value in + if CFGetTypeID(value) == AXUIElementGetTypeID() { + return (value as! AXUIElement) + } + return nil + } +} + +func mainOrFrontWindow(_ axApp: AXUIElement) -> AXUIElement? { + if let focused = axElement(from: axGetAttribute(axApp, kAXFocusedWindowAttribute as String)) { + return focused + } + + let windows = appWindows(axApp) + if windows.isEmpty { + return nil + } + + for window in windows { + if let isMain = axGetAttribute(window, kAXMainAttribute as String) as? Bool, isMain { + return window + } + } + + for window in windows { + if let minimized = axGetAttribute(window, kAXMinimizedAttribute as String) as? Bool, minimized { + continue + } + return window + } + + return windows.first +} + +struct ElementQuery { + let text: String? + let roles: Set? + let depth: Int + let limit: Int +} + +func matchesQuery(_ element: [String: Any], query: ElementQuery) -> Bool { + if let roles = query.roles, let role = element["role"] as? String, !roles.contains(role) { + return false + } + + if let text = query.text?.lowercased(), !text.isEmpty { + let haystack = [ + element["title"] as? String ?? "", + element["desc"] as? String ?? "", + element["label"] as? String ?? "", + element["text"] as? String ?? "", + ].joined(separator: " ").lowercased() + + return haystack.contains(text) + } + + return true +} + +func elementInfo( + element: AXUIElement, + appName: String, + pid: pid_t, + focusedElement: AXUIElement? +) -> [String: Any]? { + let role = (axGetAttribute(element, kAXRoleAttribute as String) as? String) ?? "" + if role.isEmpty || !interactiveRoles.contains(role) { + return nil + } + + if let enabled = axGetAttribute(element, kAXEnabledAttribute as String) as? Bool, !enabled { + return nil + } + + if let hidden = axGetAttribute(element, kAXHiddenAttribute as String) as? Bool, hidden { + return nil + } + + let point = axPoint(from: axGetAttribute(element, kAXPositionAttribute as String)) ?? .zero + let size = axSize(from: axGetAttribute(element, kAXSizeAttribute as String)) ?? .zero + let bounds = CGRect(origin: point, size: size) + + if !isElementVisibleOnScreen(bounds: bounds) { + return nil + } + + let rawTitle = (axGetAttribute(element, kAXTitleAttribute as String) as? String) ?? "" + let description = (axGetAttribute(element, kAXDescriptionAttribute as String) as? String) ?? "" + let roleDescription = (axGetAttribute(element, kAXRoleDescriptionAttribute as String) as? String) ?? "" + let label = (axGetAttribute(element, "AXLabel") as? String) ?? "" + let value = axGetAttribute(element, kAXValueAttribute as String) + let textValue: String? = { + if let stringValue = value as? String { + return stringValue + } + if let numberValue = value as? NSNumber { + return numberValue.stringValue + } + return nil + }() + + var title = elementTitle(role: role, title: rawTitle, value: value) + if title.isEmpty && (role == "AXRow" || role == "AXCell" || role == "AXGroup") { + title = firstStaticTextValue(in: element) ?? "" + } + let id = stableElementId(pid: pid, role: role, title: title, bounds: bounds) + let actions = axGetActions(element) + + var result: [String: Any] = [ + "id": id, + "app": appName, + "pid": Int(pid), + "role": role, + "bounds": [ + Int(bounds.origin.x.rounded()), + Int(bounds.origin.y.rounded()), + Int(bounds.width.rounded()), + Int(bounds.height.rounded()), + ] + ] + + if !title.isEmpty { + result["title"] = title + } + let effectiveDescription = !description.isEmpty ? description : roleDescription + if !effectiveDescription.isEmpty { + result["desc"] = effectiveDescription + } + if !label.isEmpty { + result["label"] = label + } + if let textValue, !textValue.isEmpty { + result["text"] = textValue + } + if !actions.isEmpty { + result["actions"] = actions + } + + if let focusedElement, CFEqual(element, focusedElement) { + result["focused"] = true + } + + if let selected = axGetAttribute(element, kAXSelectedAttribute as String) as? Bool, selected { + result["selected"] = true + } + + if ["AXSwitch", "AXCheckBox", "AXToggle", "AXRadioButton"].contains(role), let boolValue = boolLikeValue(value) { + result["checked"] = boolValue + } + + return result +} + +func walkTree( + element: AXUIElement, + appName: String, + pid: pid_t, + focusedElement: AXUIElement?, + query: ElementQuery, + depth: Int, + seen: inout Set, + results: inout [[String: Any]] +) { + if depth > query.depth || results.count >= query.limit { + return + } + + if let info = elementInfo(element: element, appName: appName, pid: pid, focusedElement: focusedElement) { + let id = info["id"] as? String ?? "" + if !id.isEmpty && !seen.contains(id) && matchesQuery(info, query: query) { + seen.insert(id) + results.append(info) + if results.count >= query.limit { + return + } + } + } + + let children = axGetAttribute(element, kAXChildrenAttribute as String) as? [AnyObject] ?? [] + for child in children { + if results.count >= query.limit { + return + } + if CFGetTypeID(child) == AXUIElementGetTypeID() { + walkTree( + element: (child as! AXUIElement), + appName: appName, + pid: pid, + focusedElement: focusedElement, + query: query, + depth: depth + 1, + seen: &seen, + results: &results + ) + } + } + + let visibleChildren = axGetAttribute(element, kAXVisibleChildrenAttribute as String) as? [AnyObject] ?? [] + for child in visibleChildren { + if results.count >= query.limit { + return + } + if CFGetTypeID(child) == AXUIElementGetTypeID() { + walkTree( + element: (child as! AXUIElement), + appName: appName, + pid: pid, + focusedElement: focusedElement, + query: query, + depth: depth + 1, + seen: &seen, + results: &results + ) + } + } +} + +func collectElements( + scope: String, + appIdentifier: String?, + text: String?, + roles: [String]?, + depth: Int, + limit: Int +) -> [[String: Any]] { + var targets: [NSRunningApplication] = [] + + switch scope { + case "top_window": + if let front = NSWorkspace.shared.frontmostApplication { + targets = [front] + } + case "app": + if let appIdentifier, let app = findApp(appIdentifier) { + targets = [app] + } + default: + targets = runningApps(visibleOnly: true) + } + + if targets.isEmpty { + return [] + } + + let query = ElementQuery( + text: text, + roles: roles != nil ? Set(roles!) : nil, + depth: max(1, depth), + limit: max(1, limit) + ) + + var seen = Set() + var results: [[String: Any]] = [] + + for app in targets { + if results.count >= query.limit { + break + } + + let pid = app.processIdentifier + let appElement = AXUIElementCreateApplication(pid) + let focusedElement = axElement(from: axGetAttribute(appElement, kAXFocusedUIElementAttribute as String)) + + let windows: [AXUIElement] + if scope == "top_window", let oneWindow = mainOrFrontWindow(appElement) { + windows = [oneWindow] + } else { + windows = appWindows(appElement) + } + + for window in windows { + if results.count >= query.limit { + break + } + + if let minimized = axGetAttribute(window, kAXMinimizedAttribute as String) as? Bool, minimized { + continue + } + + walkTree( + element: window, + appName: appName(app), + pid: pid, + focusedElement: focusedElement, + query: query, + depth: 0, + seen: &seen, + results: &results + ) + } + + if results.count >= query.limit { + break + } + + if let menuBar = axElement(from: axGetAttribute(appElement, kAXMenuBarAttribute as String)) { + walkTree( + element: menuBar, + appName: appName(app), + pid: pid, + focusedElement: focusedElement, + query: query, + depth: 0, + seen: &seen, + results: &results + ) + } + } + + return results +} + +func parsePIDFromElementID(_ id: String) -> String? { + guard let first = id.split(separator: "-").first, !first.isEmpty else { + return nil + } + let value = String(first) + return Int32(value) != nil ? value : nil +} + +func boundsFromElementInfo(_ info: [String: Any]) -> CGRect? { + guard let bounds = info["bounds"] as? [Any], bounds.count == 4 else { + return nil + } + + let x = (bounds[0] as? NSNumber)?.doubleValue ?? 0 + let y = (bounds[1] as? NSNumber)?.doubleValue ?? 0 + let w = (bounds[2] as? NSNumber)?.doubleValue ?? 0 + let h = (bounds[3] as? NSNumber)?.doubleValue ?? 0 + + guard w > 0, h > 0 else { + return nil + } + + return CGRect(x: x, y: y, width: w, height: h) +} + +func coordinateClick(point: CGPoint, clickCount: Int = 1) -> Bool { + let count = max(1, clickCount) + for index in 0.. Bool { + let point = CGPoint(x: bounds.midX, y: bounds.midY) + return coordinateClick(point: point) +} + +func scrollAt(x: Double, y: Double, direction: String, amount: Int) -> Bool { + let steps = max(1, amount) + let delta = Int32(direction.lowercased() == "up" ? steps : -steps) + + guard let event = CGEvent( + scrollWheelEvent2Source: nil, + units: .line, + wheelCount: 1, + wheel1: delta, + wheel2: 0, + wheel3: 0 + ) else { + return false + } + + event.location = CGPoint(x: x, y: y) + event.post(tap: .cghidEventTap) + return true +} + +func findElementHandleById( + in element: AXUIElement, + targetId: String, + appName: String, + pid: pid_t, + focusedElement: AXUIElement?, + maxDepth: Int, + depth: Int = 0 +) -> (element: AXUIElement, info: [String: Any])? { + if depth > maxDepth { + return nil + } + + if let info = elementInfo(element: element, appName: appName, pid: pid, focusedElement: focusedElement), + let candidateId = info["id"] as? String, + candidateId == targetId { + return (element, info) + } + + let childCollections = [ + axGetAttribute(element, kAXChildrenAttribute as String) as? [AnyObject] ?? [], + axGetAttribute(element, kAXVisibleChildrenAttribute as String) as? [AnyObject] ?? [], + ] + + for collection in childCollections { + for child in collection { + guard CFGetTypeID(child) == AXUIElementGetTypeID() else { + continue + } + if let found = findElementHandleById( + in: child as! AXUIElement, + targetId: targetId, + appName: appName, + pid: pid, + focusedElement: focusedElement, + maxDepth: maxDepth, + depth: depth + 1 + ) { + return found + } + } + } + + return nil +} + +func clickElementById(_ id: String, appIdentifier: String?) -> (Bool, [String: Any]) { + let appCandidates: [NSRunningApplication] + if let appIdentifier, let app = findApp(appIdentifier) { + appCandidates = [app] + } else if let pidString = parsePIDFromElementID(id), let app = findApp(pidString) { + appCandidates = [app] + } else { + appCandidates = runningApps(visibleOnly: true) + } + + for app in appCandidates { + let pid = app.processIdentifier + let appElement = AXUIElementCreateApplication(pid) + let focusedElement = axElement(from: axGetAttribute(appElement, kAXFocusedUIElementAttribute as String)) + let appLabel = appName(app) + + let windows = appWindows(appElement).filter { window in + !((axGetAttribute(window, kAXMinimizedAttribute as String) as? Bool) ?? false) + } + + for window in windows { + if let found = findElementHandleById( + in: window, + targetId: id, + appName: appLabel, + pid: pid, + focusedElement: focusedElement, + maxDepth: 12 + ) { + let actions = axGetActions(found.element) + let preferred = ["AXPress", "AXConfirm", "AXOpen", "AXPick"] + let selectedAction = preferred.first(where: { actions.contains($0) }) ?? actions.first + + if let selectedAction, axPerformAction(found.element, selectedAction) { + return (true, [ + "id": id, + "action": selectedAction, + "method": "ax_action", + "element": found.info, + ]) + } + + if let bounds = boundsFromElementInfo(found.info), coordinateClickCenter(bounds: bounds) { + return (true, [ + "id": id, + "action": selectedAction ?? "AXPress", + "method": "coordinate_click_fallback", + "element": found.info, + ]) + } + + return (false, [ + "code": HelperCode.actionFailed.rawValue, + "message": "Failed to perform click action on element: \(id)", + "element": found.info, + ]) + } + } + + if let menuBar = axElement(from: axGetAttribute(appElement, kAXMenuBarAttribute as String)), + let found = findElementHandleById( + in: menuBar, + targetId: id, + appName: appLabel, + pid: pid, + focusedElement: focusedElement, + maxDepth: 12 + ) { + let actions = axGetActions(found.element) + let preferred = ["AXPress", "AXConfirm", "AXOpen", "AXPick"] + let selectedAction = preferred.first(where: { actions.contains($0) }) ?? actions.first + + if let selectedAction, axPerformAction(found.element, selectedAction) { + return (true, [ + "id": id, + "action": selectedAction, + "method": "ax_action", + "element": found.info, + ]) + } + + if let bounds = boundsFromElementInfo(found.info), coordinateClickCenter(bounds: bounds) { + return (true, [ + "id": id, + "action": selectedAction ?? "AXPress", + "method": "coordinate_click_fallback", + "element": found.info, + ]) + } + + return (false, [ + "code": HelperCode.actionFailed.rawValue, + "message": "Failed to perform click action on element: \(id)", + "element": found.info, + ]) + } + } + + return (false, ["code": HelperCode.notFound.rawValue, "message": "Element not found: \(id)"]) +} + +func typeText(_ text: String) -> Bool { + for char in text { + let needsShift: Bool + let baseChar: Character + + if let mapped = shiftChars[char] { + needsShift = true + baseChar = mapped + } else if char.isUppercase { + needsShift = true + baseChar = Character(char.lowercased()) + } else { + needsShift = false + baseChar = char + } + + guard let keyCode = keyCodes[String(baseChar)] else { + continue + } + + guard let down = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: true), + let up = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: false) else { + continue + } + + if needsShift { + down.flags = [.maskShift] + up.flags = [.maskShift] + } + + down.post(tap: .cghidEventTap) + up.post(tap: .cghidEventTap) + + usleep(20_000) + } + + return true +} + +func pressKey(_ key: String, modifiers: [String]) -> Bool { + let normalized = key.lowercased() + guard let keyCode = keyCodes[normalized] else { + return false + } + + var flags: CGEventFlags = [] + for modifier in modifiers { + if let mapped = modifierFlags[modifier.lowercased()] { + flags.insert(mapped) + } + } + + guard let down = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: true), + let up = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: false) else { + return false + } + + down.flags = flags + up.flags = flags + + down.post(tap: .cghidEventTap) + up.post(tap: .cghidEventTap) + + return true +} + +func activateApp(_ identifier: String) -> [String: Any]? { + guard let app = findApp(identifier) else { + return nil + } + + let ok = app.activate(options: [.activateIgnoringOtherApps]) + if ok { + return [ + "success": true, + "app": appName(app), + "pid": Int(app.processIdentifier), + ] + } + + return [ + "success": false, + "app": appName(app), + "pid": Int(app.processIdentifier), + ] +} + +func waitForElement(appIdentifier: String, text: String, roles: [String]?, timeoutMs: Int, depth: Int) -> [String: Any]? { + let start = Date() + let timeoutInterval = TimeInterval(timeoutMs) / 1000.0 + + while Date().timeIntervalSince(start) < timeoutInterval { + let elements = collectElements( + scope: "app", + appIdentifier: appIdentifier, + text: text, + roles: roles, + depth: depth, + limit: 20 + ) + + if let first = elements.first { + return first + } + + usleep(300_000) + } + + return nil +} + +func findElements(appIdentifier: String, text: String?, roles: [String]?, depth: Int, limit: Int, index: Int = 0) -> [[String: Any]] { + let safeIndex = max(0, index) + let requestedLimit = max(safeIndex + 1, max(1, limit)) + let elements = collectElements( + scope: "app", + appIdentifier: appIdentifier, + text: text, + roles: roles, + depth: depth, + limit: requestedLimit + ) + + if safeIndex > 0 { + if safeIndex < elements.count { + return [elements[safeIndex]] + } + return [] + } + + return elements +} + +func parseObjectArray(_ any: Any?) -> [[String: Any]]? { + if let objects = any as? [[String: Any]] { + return objects + } + + if let list = any as? [Any] { + return list.compactMap { $0 as? [String: Any] } + } + + return nil +} + +func parseInt(_ any: Any?, default defaultValue: Int) -> Int { + if let value = any as? NSNumber { + return value.intValue + } + if let value = any as? Int { + return value + } + if let value = any as? String, let intValue = Int(value) { + return intValue + } + return defaultValue +} + +func parseDouble(_ any: Any?) -> Double? { + if let value = any as? NSNumber { + return value.doubleValue + } + if let value = any as? Double { + return value + } + if let value = any as? String, let doubleValue = Double(value) { + return doubleValue + } + return nil +} + +func parseBool(_ any: Any?, default defaultValue: Bool = false) -> Bool { + if let value = any as? Bool { + return value + } + if let value = any as? NSNumber { + return value.intValue != 0 + } + if let value = any as? String { + let lower = value.lowercased() + if ["1", "true", "yes"].contains(lower) { return true } + if ["0", "false", "no"].contains(lower) { return false } + } + return defaultValue +} + +func executeBatchCommand(_ cmd: [String: Any]) -> [String: Any] { + let action = ((cmd["action"] as? String) ?? "").lowercased() + + switch action { + case "wait": + let ms = max(0, parseInt(cmd["ms"], default: 500)) + usleep(useconds_t(ms * 1000)) + return ["action": "wait", "success": true, "ms": ms] + + case "activate": + guard let app = parseAppIdentifier(cmd["app"]) else { + return ["action": "activate", "success": false, "error": "Missing app"] + } + guard let result = activateApp(app) else { + return ["action": "activate", "success": false, "error": "No application found matching \(app)"] + } + if (result["success"] as? Bool) == true { + var response = result + response["action"] = "activate" + return response + } + return ["action": "activate", "success": false, "error": "Failed to activate app"] + + case "find", "get_state": + guard let app = parseAppIdentifier(cmd["app"]) else { + return ["action": action, "success": false, "error": "Missing app"] + } + + let text = (cmd["text"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) + let normalizedText = (text?.isEmpty == false) ? text : nil + let roles = parseStringArray(cmd["roles"] ?? cmd["role"]) + if normalizedText == nil && (roles == nil || roles?.isEmpty == true) { + return ["action": action, "success": false, "error": "Provide text or role"] + } + + let depth = max(1, parseInt(cmd["depth"], default: 10)) + let index = max(0, parseInt(cmd["index"], default: 0)) + let limit = max(1, parseInt(cmd["limit"], default: index + 1)) + let elements = findElements(appIdentifier: app, text: normalizedText, roles: roles, depth: depth, limit: limit, index: index) + + guard let element = elements.first else { + return ["action": action, "success": false, "error": "No matching element found"] + } + + if action == "get_state" { + var response: [String: Any] = [ + "action": "get_state", + "success": true, + "element": element, + ] + if let checked = element["checked"] { + response["checked"] = checked + } + if let selected = element["selected"] { + response["selected"] = selected + } + if let textValue = element["text"] { + response["text"] = textValue + } + return response + } + + return [ + "action": "find", + "success": true, + "element": element, + ] + + case "click", "find_and_click": + let ifExists = parseBool(cmd["if_exists"], default: false) + let explicitID = cmd["id"] as? String + let appHint = parseAppIdentifier(cmd["app"]) + var targetElement: [String: Any]? = nil + var targetID = explicitID + + if targetID == nil { + guard let app = appHint else { + return ["action": action, "success": false, "error": "Must provide id or app"] + } + + let text = (cmd["text"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) + let normalizedText = (text?.isEmpty == false) ? text : nil + let roles = parseStringArray(cmd["roles"] ?? cmd["role"]) + if normalizedText == nil && (roles == nil || roles?.isEmpty == true) { + return ["action": action, "success": false, "error": "Provide text or role"] + } + + let depth = max(1, parseInt(cmd["depth"], default: 10)) + let index = max(0, parseInt(cmd["index"], default: 0)) + let limit = max(1, parseInt(cmd["limit"], default: index + 1)) + let matches = findElements(appIdentifier: app, text: normalizedText, roles: roles, depth: depth, limit: limit, index: index) + targetElement = matches.first + targetID = targetElement?["id"] as? String + } + + guard let resolvedID = targetID else { + if ifExists { + return ["action": action, "success": true, "skipped": true] + } + return ["action": action, "success": false, "error": "No matching element found"] + } + + let (clickedOK, payload) = clickElementById(resolvedID, appIdentifier: appHint) + if clickedOK { + var response = payload + response["action"] = action + response["success"] = true + if let targetElement { + response["element"] = targetElement + } + return response + } + + let helperMessage = payload["message"] as? String ?? "Click failed" + let helperCode = payload["code"] as? String + if ifExists && helperCode == HelperCode.notFound.rawValue { + return ["action": action, "success": true, "skipped": true] + } + + var response: [String: Any] = ["action": action, "success": false, "error": helperMessage] + if let targetElement { + response["element"] = targetElement + } + return response + + case "type": + let text = (cmd["text"] as? String) ?? "" + let ok = typeText(text) + return ok + ? ["action": "type", "success": true, "text": text, "length": text.count] + : ["action": "type", "success": false, "error": "Type failed"] + + case "key": + guard let key = cmd["key"] as? String, !key.isEmpty else { + return ["action": "key", "success": false, "error": "Missing key"] + } + let modifiers = parseStringArray(cmd["modifiers"]) ?? [] + let ok = pressKey(key, modifiers: modifiers) + return ok + ? ["action": "key", "success": true, "key": key, "modifiers": modifiers] + : ["action": "key", "success": false, "error": "Failed to press key: \(key)"] + + case "wait_for": + guard let app = parseAppIdentifier(cmd["app"]), + let text = cmd["text"] as? String, !text.isEmpty else { + return ["action": "wait_for", "success": false, "error": "Missing app or text"] + } + let roles = parseStringArray(cmd["roles"] ?? cmd["role"]) + let timeoutMs = max(1, parseInt(cmd["timeout_ms"], default: 5000)) + let depth = max(1, parseInt(cmd["depth"], default: 10)) + if let element = waitForElement(appIdentifier: app, text: text, roles: roles, timeoutMs: timeoutMs, depth: depth) { + return ["action": "wait_for", "success": true, "element": element] + } + return ["action": "wait_for", "success": false, "error": "Timed out waiting for element"] + + case "scroll": + guard let x = parseDouble(cmd["x"]), let y = parseDouble(cmd["y"]) else { + return ["action": "scroll", "success": false, "error": "Missing x or y"] + } + let direction = ((cmd["direction"] as? String) ?? "down").lowercased() + let amount = max(1, parseInt(cmd["amount"], default: 3)) + let ok = scrollAt(x: x, y: y, direction: direction, amount: amount) + return ok + ? ["action": "scroll", "success": true, "x": x, "y": y, "direction": direction, "amount": amount] + : ["action": "scroll", "success": false, "error": "Failed to scroll"] + + default: + return ["action": action.isEmpty ? "unknown" : action, "success": false, "error": "Unknown action: \(action)"] + } +} + +func runBatchCommands(_ commands: [[String: Any]], stopOnError: Bool) -> [String: Any] { + var results: [[String: Any]] = [] + var failedAt: Int? = nil + + for (index, cmd) in commands.enumerated() { + let result = executeBatchCommand(cmd) + results.append(result) + + let success = (result["success"] as? Bool) ?? false + let skipped = (result["skipped"] as? Bool) ?? false + if !success && !skipped { + failedAt = index + if stopOnError { + break + } + } + } + + return [ + "success": failedAt == nil, + "results": results, + "failedAt": failedAt != nil ? failedAt! : NSNull(), + "completed": results.count, + "total": commands.count, + ] +} + +func parseRequest() throws -> [String: Any] { + let data = FileHandle.standardInput.readDataToEndOfFile() + guard !data.isEmpty else { + throw NSError(domain: "macos-ax-helper", code: 1, userInfo: [NSLocalizedDescriptionKey: "Empty request payload"]) + } + + let object = try JSONSerialization.jsonObject(with: data, options: []) + guard let request = object as? [String: Any] else { + throw NSError(domain: "macos-ax-helper", code: 2, userInfo: [NSLocalizedDescriptionKey: "Invalid JSON object request"]) + } + + return request +} + +func parseStringArray(_ any: Any?) -> [String]? { + if let list = any as? [String] { + return list + } + if let anyList = any as? [Any] { + return anyList.map { String(describing: $0) } + } + return nil +} + +let startedAt = Date() + +if !isMacOS() { + errorResponse(code: .unsupportedPlatform, message: "macos-ax-helper supports only macOS", requestId: nil, startedAt: startedAt) + exit(0) +} + +do { + let request = try parseRequest() + let command = (request["command"] as? String) ?? "" + let requestId = request["requestId"] as? String + let args = request["args"] as? [String: Any] ?? [:] + + switch command { + case "status": + let hasPermission = accessibilityTrusted(prompt: false) + successResponse(data: [ + "platform": "darwin", + "hasPermission": hasPermission, + "helperVersion": helperVersion, + "processInfo": processInfoString(), + ], requestId: requestId, startedAt: startedAt) + + case "list_apps": + guard accessibilityTrusted(prompt: false) else { + errorResponse( + code: .permissionDenied, + message: "Accessibility permissions not granted", + details: ["processInfo": processInfoString()], + requestId: requestId, + startedAt: startedAt + ) + break + } + + let apps = runningApps(visibleOnly: true).map { app in + [ + "name": appName(app), + "pid": Int(app.processIdentifier), + "bundleId": app.bundleIdentifier ?? "", + "active": app.isActive, + ] as [String : Any] + } + + successResponse(data: ["apps": apps], requestId: requestId, startedAt: startedAt) + + case "list_elements": + guard accessibilityTrusted(prompt: false) else { + errorResponse( + code: .permissionDenied, + message: "Accessibility permissions not granted", + details: ["processInfo": processInfoString()], + requestId: requestId, + startedAt: startedAt + ) + break + } + + let scope = (args["scope"] as? String) ?? "top_window" + let appIdentifier = parseAppIdentifier(args["app"]) + let text = args["text"] as? String + let roles = parseStringArray(args["roles"]) + let depth = (args["depth"] as? Int) ?? 10 + let limit = (args["limit"] as? Int) ?? 250 + + if scope == "app" && appIdentifier == nil { + errorResponse(code: .invalidArgument, message: "list_elements with scope=app requires args.app", requestId: requestId, startedAt: startedAt) + break + } + + let elements = collectElements( + scope: scope, + appIdentifier: appIdentifier, + text: text, + roles: roles, + depth: depth, + limit: limit + ) + + successResponse(data: ["elements": elements], requestId: requestId, startedAt: startedAt) + + case "find": + guard accessibilityTrusted(prompt: false) else { + errorResponse( + code: .permissionDenied, + message: "Accessibility permissions not granted", + details: ["processInfo": processInfoString()], + requestId: requestId, + startedAt: startedAt + ) + break + } + + guard let app = parseAppIdentifier(args["app"]) else { + errorResponse(code: .invalidArgument, message: "find requires args.app", requestId: requestId, startedAt: startedAt) + break + } + + let text = (args["text"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) + let normalizedText = (text?.isEmpty == false) ? text : nil + let roles = parseStringArray(args["roles"] ?? args["role"]) + if normalizedText == nil && (roles == nil || roles?.isEmpty == true) { + errorResponse(code: .invalidArgument, message: "find requires args.text or args.role(s)", requestId: requestId, startedAt: startedAt) + break + } + let depth = parseInt(args["depth"], default: 10) + let index = max(0, parseInt(args["index"], default: 0)) + let limit = max(1, parseInt(args["limit"], default: index + 1)) + let elements = findElements(appIdentifier: app, text: normalizedText, roles: roles, depth: depth, limit: limit, index: index) + successResponse(data: ["elements": elements], requestId: requestId, startedAt: startedAt) + + case "click": + guard accessibilityTrusted(prompt: false) else { + errorResponse( + code: .permissionDenied, + message: "Accessibility permissions not granted", + details: ["processInfo": processInfoString()], + requestId: requestId, + startedAt: startedAt + ) + break + } + + guard let id = args["id"] as? String, !id.isEmpty else { + errorResponse(code: .invalidArgument, message: "click requires args.id", requestId: requestId, startedAt: startedAt) + break + } + + let appIdentifier = parseAppIdentifier(args["app"]) + let (ok, payload) = clickElementById(id, appIdentifier: appIdentifier) + + if ok { + successResponse(data: payload, requestId: requestId, startedAt: startedAt) + } else { + let codeRaw = payload["code"] as? String ?? HelperCode.actionFailed.rawValue + let code = HelperCode(rawValue: codeRaw) ?? .actionFailed + let message = payload["message"] as? String ?? "click failed" + errorResponse(code: code, message: message, details: payload, requestId: requestId, startedAt: startedAt) + } + + case "type_text": + guard accessibilityTrusted(prompt: false) else { + errorResponse( + code: .permissionDenied, + message: "Accessibility permissions not granted", + details: ["processInfo": processInfoString()], + requestId: requestId, + startedAt: startedAt + ) + break + } + + guard let text = args["text"] as? String else { + errorResponse(code: .invalidArgument, message: "type_text requires args.text", requestId: requestId, startedAt: startedAt) + break + } + + let ok = typeText(text) + if ok { + successResponse(data: ["success": true, "text": text, "length": text.count], requestId: requestId, startedAt: startedAt) + } else { + errorResponse(code: .actionFailed, message: "Failed to type text", requestId: requestId, startedAt: startedAt) + } + + case "press_key": + guard accessibilityTrusted(prompt: false) else { + errorResponse( + code: .permissionDenied, + message: "Accessibility permissions not granted", + details: ["processInfo": processInfoString()], + requestId: requestId, + startedAt: startedAt + ) + break + } + + guard let key = args["key"] as? String, !key.isEmpty else { + errorResponse(code: .invalidArgument, message: "press_key requires args.key", requestId: requestId, startedAt: startedAt) + break + } + + let modifiers = parseStringArray(args["modifiers"]) ?? [] + let ok = pressKey(key, modifiers: modifiers) + + if ok { + successResponse(data: ["success": true, "key": key, "modifiers": modifiers], requestId: requestId, startedAt: startedAt) + } else { + errorResponse(code: .actionFailed, message: "Failed to press key: \(key)", requestId: requestId, startedAt: startedAt) + } + + case "activate": + guard accessibilityTrusted(prompt: false) else { + errorResponse( + code: .permissionDenied, + message: "Accessibility permissions not granted", + details: ["processInfo": processInfoString()], + requestId: requestId, + startedAt: startedAt + ) + break + } + + guard let app = parseAppIdentifier(args["app"]) else { + errorResponse(code: .invalidArgument, message: "activate requires args.app", requestId: requestId, startedAt: startedAt) + break + } + + guard let result = activateApp(app) else { + errorResponse(code: .notFound, message: "No application found matching \(app)", requestId: requestId, startedAt: startedAt) + break + } + + if (result["success"] as? Bool) == true { + successResponse(data: result, requestId: requestId, startedAt: startedAt) + } else { + errorResponse(code: .actionFailed, message: "Failed to activate app", details: result, requestId: requestId, startedAt: startedAt) + } + + case "wait_for": + guard accessibilityTrusted(prompt: false) else { + errorResponse( + code: .permissionDenied, + message: "Accessibility permissions not granted", + details: ["processInfo": processInfoString()], + requestId: requestId, + startedAt: startedAt + ) + break + } + + guard let app = parseAppIdentifier(args["app"]), + let text = args["text"] as? String, !text.isEmpty else { + errorResponse(code: .invalidArgument, message: "wait_for requires args.app and args.text", requestId: requestId, startedAt: startedAt) + break + } + + let roles = parseStringArray(args["roles"]) + let timeoutMs = (args["timeout_ms"] as? Int) ?? 5000 + let depth = (args["depth"] as? Int) ?? 10 + + if let element = waitForElement(appIdentifier: app, text: text, roles: roles, timeoutMs: timeoutMs, depth: depth) { + successResponse(data: ["element": element], requestId: requestId, startedAt: startedAt) + } else { + errorResponse(code: .timeout, message: "Timed out waiting for element", details: ["app": app, "text": text, "timeout_ms": timeoutMs], requestId: requestId, startedAt: startedAt) + } + + case "scroll": + guard accessibilityTrusted(prompt: false) else { + errorResponse( + code: .permissionDenied, + message: "Accessibility permissions not granted", + details: ["processInfo": processInfoString()], + requestId: requestId, + startedAt: startedAt + ) + break + } + + guard let x = parseDouble(args["x"]), + let y = parseDouble(args["y"]) else { + errorResponse(code: .invalidArgument, message: "scroll requires args.x and args.y", requestId: requestId, startedAt: startedAt) + break + } + + let direction = ((args["direction"] as? String) ?? "down").lowercased() + let amount = max(1, parseInt(args["amount"], default: 3)) + if scrollAt(x: x, y: y, direction: direction, amount: amount) { + successResponse(data: ["success": true, "x": x, "y": y, "direction": direction, "amount": amount], requestId: requestId, startedAt: startedAt) + } else { + errorResponse(code: .actionFailed, message: "Failed to scroll", requestId: requestId, startedAt: startedAt) + } + + case "batch": + guard let commands = parseObjectArray(args["commands"]) else { + errorResponse(code: .invalidArgument, message: "batch requires args.commands (array of objects)", requestId: requestId, startedAt: startedAt) + break + } + + let stopOnError = parseBool(args["stop_on_error"], default: true) + let requiresPermission = commands.contains { command in + let action = ((command["action"] as? String) ?? "").lowercased() + return action != "wait" + } + + if requiresPermission && !accessibilityTrusted(prompt: false) { + errorResponse( + code: .permissionDenied, + message: "Accessibility permissions not granted", + details: ["processInfo": processInfoString()], + requestId: requestId, + startedAt: startedAt + ) + break + } + + let result = runBatchCommands(commands, stopOnError: stopOnError) + successResponse(data: result, requestId: requestId, startedAt: startedAt) + + default: + errorResponse(code: .invalidArgument, message: "Unknown command: \(command)", requestId: requestId, startedAt: startedAt) + } +} catch { + errorResponse(code: .internalError, message: "Unhandled helper error: \(error.localizedDescription)", requestId: nil, startedAt: startedAt) +} diff --git a/package-lock.json b/package-lock.json index a441e966..1569be89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,6 @@ "md-to-pdf": "^5.2.5", "open": "^10.2.0", "pdf-lib": "^1.17.1", - "pizzip": "^3.2.0", "remark": "^15.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", @@ -81,12 +80,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -104,9 +103,9 @@ } }, "node_modules/@borewit/text-codec": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.1.tgz", - "integrity": "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", + "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", "license": "MIT", "funding": { "type": "github", @@ -133,6 +132,17 @@ "node": ">=12" } }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", @@ -144,9 +154,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", "license": "MIT", "optional": true, "dependencies": { @@ -154,9 +164,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -171,9 +181,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -188,9 +198,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -205,9 +215,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -222,9 +232,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -239,9 +249,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -256,9 +266,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -273,9 +283,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -290,9 +300,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -307,9 +317,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -324,9 +334,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -341,9 +351,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -358,9 +368,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -375,9 +385,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -392,9 +402,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -409,9 +419,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -426,9 +436,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -443,9 +453,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], @@ -460,9 +470,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -477,9 +487,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], @@ -494,9 +504,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -511,9 +521,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", "cpu": [ "arm64" ], @@ -528,9 +538,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -545,9 +555,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -562,9 +572,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -579,9 +589,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -637,9 +647,9 @@ "license": "MIT" }, "node_modules/@hono/node-server": { - "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", - "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "version": "1.19.7", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.7.tgz", + "integrity": "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -1169,9 +1179,9 @@ } }, "node_modules/@inquirer/core/node_modules/@types/node": { - "version": "22.19.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", - "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", "dev": true, "license": "MIT", "dependencies": { @@ -1428,17 +1438,6 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -1460,7 +1459,14 @@ "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", @@ -1471,31 +1477,87 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", + "optional": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", - "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.1.tgz", + "integrity": "sha512-yO28oVFFC7EBoiKdAn+VqRm+plcfv4v0xp6osG/VsCB0NlPZWi87ajbCZZ8f/RvOFLEu7//rSRmuZZ7lMoe3gQ==", "license": "MIT", "dependencies": { - "@hono/node-server": "^1.19.9", + "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -1503,15 +1565,14 @@ "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" + "zod-to-json-schema": "^3.25.0" }, "engines": { "node": ">=18" @@ -1529,6 +1590,243 @@ } } }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1568,19 +1866,28 @@ } }, "node_modules/@opendocsg/pdf2md": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@opendocsg/pdf2md/-/pdf2md-0.2.3.tgz", - "integrity": "sha512-3LwYnqKa1KEIFE+54FbdPtCgr84To0IqhkFAQP95wG0WGOUc1WS3T+z+rWRkJWNz4fIXcq+Jk+tusLOLQ1XfCg==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@opendocsg/pdf2md/-/pdf2md-0.2.2.tgz", + "integrity": "sha512-l6mcjcbL+eboX8ddUgu29R6aFOcDxkadVJvPG+1QcUhUrJt+q3+n0TxmKmYheaXeL17Eq9ZGUtyS2EnPs5a2vw==", "license": "MIT", "dependencies": { "enumify": "^1.0.4", "minimist": "^1.2.5", - "unpdf": "^1.4.0" + "unpdf": "^0.12.1" }, "bin": { "pdf2md": "lib/pdf2md-cli.js" } }, + "node_modules/@opendocsg/pdf2md/node_modules/unpdf": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/unpdf/-/unpdf-0.12.2.tgz", + "integrity": "sha512-3eyDFfayk+Sf5+inJ4OyhecR2BtRFEeZqUfGPdq2O8aBLau9MYL9lAP+GEcSAaVd2JWqde8Dnz38z0x7KRglaA==", + "license": "MIT", + "optionalDependencies": { + "canvas": "^2.11.2" + } + }, "node_modules/@pdf-lib/standard-fonts": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", @@ -1610,16 +1917,16 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.12.1.tgz", - "integrity": "sha512-fXa6uXLxfslBlus3MEpW8S6S9fe5RwmAE5Gd8u3krqOwnkZJV3/lQJiY3LaFdTctLLqJtyMgEUGkbDnRNf6vbQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.11.0.tgz", + "integrity": "sha512-n6oQX6mYkG8TRPuPXmbPidkUbsSRalhmaaVAQxvH1IkQy63cwsH+kOjB3e4cpCDHg0aSvsiX9bQ4s2VB6mGWUQ==", "license": "Apache-2.0", "dependencies": { "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", - "semver": "^7.7.4", + "semver": "^7.7.3", "tar-fs": "^3.1.1", "yargs": "^17.7.2" }, @@ -1664,9 +1971,9 @@ } }, "node_modules/@supabase/auth-js": { - "version": "2.95.3", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.95.3.tgz", - "integrity": "sha512-vD2YoS8E2iKIX0F7EwXTmqhUpaNsmbU6X2R0/NdFcs02oEfnHyNP/3M716f3wVJ2E5XHGiTFXki6lRckhJ0Thg==", + "version": "2.90.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.90.0.tgz", + "integrity": "sha512-J5Jgr1XE86NpK0LX8yLdjv3NzzsUnGky72QIGR1AcdVXHQIhwjb3JFEjNutODNKyOrknx00lyClOvqooHFHIiw==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -1676,9 +1983,9 @@ } }, "node_modules/@supabase/functions-js": { - "version": "2.95.3", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.95.3.tgz", - "integrity": "sha512-uTuOAKzs9R/IovW1krO0ZbUHSJnsnyJElTXIRhjJTqymIVGcHzkAYnBCJqd7468Fs/Foz1BQ7Dv6DCl05lr7ig==", + "version": "2.90.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.90.0.tgz", + "integrity": "sha512-YEk4AVtdaUzjxhil2GemOjq0VQVQfg8LmPyooJt80lNscQHfDyCGmHeQ6pDI48dkICTE87V7KVrlXaVGCZa1Rw==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -1688,9 +1995,9 @@ } }, "node_modules/@supabase/postgrest-js": { - "version": "2.95.3", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.95.3.tgz", - "integrity": "sha512-LTrRBqU1gOovxRm1vRXPItSMPBmEFqrfTqdPTRtzOILV4jPSueFz6pES5hpb4LRlkFwCPRmv3nQJ5N625V2Xrg==", + "version": "2.90.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.90.0.tgz", + "integrity": "sha512-XCgrXuGtBPgxQWKfS8tZ4/deK5+cgUW+XLl4zq4v7JRUfCHlc3ZewZid5hjcpAIDYwGWq9ihrfPUNDAfl5jDdg==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -1700,9 +2007,9 @@ } }, "node_modules/@supabase/realtime-js": { - "version": "2.95.3", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.95.3.tgz", - "integrity": "sha512-D7EAtfU3w6BEUxDACjowWNJo/ZRo7sDIuhuOGKHIm9FHieGeoJV5R6GKTLtga/5l/6fDr2u+WcW/m8I9SYmaIw==", + "version": "2.90.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.90.0.tgz", + "integrity": "sha512-Hncs2Obk2Wd4Mcj51XDtLqOuiDT6onSmpHkwjsaLDZylgLi4uN81W4LYhj+rdPm3A9nQIRB4/5SZ3Hab2Adlsg==", "license": "MIT", "dependencies": { "@types/phoenix": "^1.6.6", @@ -1715,9 +2022,9 @@ } }, "node_modules/@supabase/storage-js": { - "version": "2.95.3", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.95.3.tgz", - "integrity": "sha512-4GxkJiXI3HHWjxpC3sDx1BVrV87O0hfX+wvJdqGv67KeCu+g44SPnII8y0LL/Wr677jB7tpjAxKdtVWf+xhc9A==", + "version": "2.90.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.90.0.tgz", + "integrity": "sha512-50XiDACfHXhibm20J94TEdOYMLXm4fZCHTdAAs9xBDJTBA4RXVN52Bx1MVW34pu3S72rAgObQxVyAFaZ+7+7Tg==", "license": "MIT", "dependencies": { "iceberg-js": "^0.8.1", @@ -1728,16 +2035,16 @@ } }, "node_modules/@supabase/supabase-js": { - "version": "2.95.3", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.95.3.tgz", - "integrity": "sha512-Fukw1cUTQ6xdLiHDJhKKPu6svEPaCEDvThqCne3OaQyZvuq2qjhJAd91kJu3PXLG18aooCgYBaB6qQz35hhABg==", + "version": "2.90.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.90.0.tgz", + "integrity": "sha512-foWQl5x1+LuqT7qdnAoCDcKHTNBSaED+eSEOEmmpqXiQuCfJU2K0lDkfGm7PUBx/BDaw3tFObzuJyZtHDbrehA==", "license": "MIT", "dependencies": { - "@supabase/auth-js": "2.95.3", - "@supabase/functions-js": "2.95.3", - "@supabase/postgrest-js": "2.95.3", - "@supabase/realtime-js": "2.95.3", - "@supabase/storage-js": "2.95.3" + "@supabase/auth-js": "2.90.0", + "@supabase/functions-js": "2.90.0", + "@supabase/postgrest-js": "2.90.0", + "@supabase/realtime-js": "2.90.0", + "@supabase/storage-js": "2.90.0" }, "engines": { "node": ">=20.0.0" @@ -1859,9 +2166,9 @@ "license": "MIT" }, "node_modules/@types/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true, "license": "MIT" }, @@ -1898,11 +2205,10 @@ } }, "node_modules/@types/node": { - "version": "20.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", - "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "version": "20.19.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", + "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -2271,14 +2577,22 @@ "@yarnpkg/fslib": "^3.1.3" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "optional": true + }, "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", + "peer": true, "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" @@ -2290,7 +2604,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2334,11 +2647,10 @@ } }, "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2449,6 +2761,13 @@ "dev": true, "license": "MIT" }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC", + "optional": true + }, "node_modules/archive-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz", @@ -2525,7 +2844,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "deprecated": "Glob versions prior to v9 are no longer supported", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -2584,6 +2903,21 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -2591,10 +2925,20 @@ "license": "MIT" }, "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT", + "peer": true }, "node_modules/array-union": { "version": "2.1.0", @@ -2641,9 +2985,9 @@ } }, "node_modules/b4a": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.4.tgz", - "integrity": "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", "license": "Apache-2.0", "peerDependencies": { "react-native-b4a": "*" @@ -2685,9 +3029,9 @@ } }, "node_modules/bare-fs": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", - "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.2.tgz", + "integrity": "sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -2782,9 +3126,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2852,27 +3196,74 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "license": "MIT", + "peer": true, "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=18" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, "node_modules/brace-expansion": { @@ -2916,7 +3307,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3147,9 +3537,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001770", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", - "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", + "version": "1.0.30001761", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", + "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", "dev": true, "funding": [ { @@ -3167,6 +3557,22 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/caw": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", @@ -3262,6 +3668,16 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -3273,9 +3689,9 @@ } }, "node_modules/chromium-bidi": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz", - "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-12.0.1.tgz", + "integrity": "sha512-fGg+6jr0xjQhzpy5N4ErZxQ4wF7KLEvhGZXD6EgvZKDhu7iOhZXnZhcDxPJDcwTcrD48NPzOCo84RP2lv3Z+Cg==", "license": "Apache-2.0", "dependencies": { "mitt": "^3.0.1", @@ -3503,6 +3919,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -3552,17 +3978,23 @@ "proto-list": "~1.2.1" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "safe-buffer": "5.2.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">= 0.6" } }, "node_modules/content-type": { @@ -3584,13 +4016,11 @@ } }, "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT", - "engines": { - "node": ">=6.6.0" - } + "peer": true }, "node_modules/core-util-is": { "version": "1.0.3", @@ -3599,9 +4029,9 @@ "license": "MIT" }, "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -3609,10 +4039,6 @@ }, "engines": { "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/cosmiconfig": { @@ -3641,6 +4067,12 @@ } } }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/cosmiconfig/node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -3747,9 +4179,9 @@ } }, "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", "license": "MIT", "dependencies": { "character-entities": "^2.0.0" @@ -4042,9 +4474,9 @@ } }, "node_modules/default-browser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", - "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", + "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", "license": "MIT", "dependencies": { "bundle-name": "^4.1.0", @@ -4136,6 +4568,13 @@ "node": ">= 14" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4154,6 +4593,17 @@ "node": ">=6" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -4177,16 +4627,15 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1566079", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1566079.tgz", - "integrity": "sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ==", - "license": "BSD-3-Clause", - "peer": true + "version": "0.0.1534754", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1534754.tgz", + "integrity": "sha512-26T91cV5dbOYnXdJi5qQHoTtUoNEqwkHcAyu/IKtjIAxiEqPMrDiRkDOPWVsGfNZGmlQVHQbZRSjD8sxagWVsQ==", + "license": "BSD-3-Clause" }, "node_modules/diff": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -4275,19 +4724,6 @@ "node": ">=0.10.0" } }, - "node_modules/download/node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/download/node_modules/decompress-response": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", @@ -4525,9 +4961,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", "dev": true, "license": "ISC" }, @@ -4565,14 +5001,14 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" + "tapable": "^2.2.0" }, "engines": { "node": ">=10.13.0" @@ -4665,9 +5101,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4678,32 +5114,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escalade": { @@ -4889,43 +5325,46 @@ } }, "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", "peer": true, "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" }, "funding": { "type": "opencollective", @@ -4933,13 +5372,10 @@ } }, "node_modules/express-rate-limit": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", - "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", "license": "MIT", - "dependencies": { - "ip-address": "10.0.1" - }, "engines": { "node": ">= 16" }, @@ -4950,6 +5386,23 @@ "express": ">= 4.11" } }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, "node_modules/ext-list": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", @@ -5178,9 +5631,9 @@ } }, "node_modules/file-type": { - "version": "21.3.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.0.tgz", - "integrity": "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==", + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.2.0.tgz", + "integrity": "sha512-vCYBgFOrJQLoTzDyAXAL/RFfKnXXpUYt4+tipVy26nJJhT7ftgGETf2tAQF59EEL61i3MrorV/PG6tf7LJK7eg==", "license": "MIT", "dependencies": { "@tokenizer/inflate": "^0.4.1", @@ -5233,26 +5686,41 @@ } }, "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "license": "MIT", + "peer": true, "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" } }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -5343,12 +5811,13 @@ } }, "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", + "peer": true, "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/from2": { @@ -5416,6 +5885,32 @@ "node": ">=12" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5466,7 +5961,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "deprecated": "Glob versions prior to v9 are no longer supported", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -5544,6 +6039,57 @@ "node": ">= 12" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -5641,9 +6187,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5671,7 +6217,6 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -5904,6 +6449,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -5926,9 +6478,9 @@ } }, "node_modules/hono": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", - "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.3.tgz", + "integrity": "sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w==", "license": "MIT", "peer": true, "engines": { @@ -6012,9 +6564,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -6164,9 +6716,9 @@ } }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "license": "MIT", "engines": { "node": ">= 12" @@ -6403,9 +6955,9 @@ } }, "node_modules/is-wsl": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", - "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", "license": "MIT", "dependencies": { "is-inside-container": "^1.0.0" @@ -6539,15 +7091,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/js-yaml/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -6724,7 +7267,6 @@ "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", "license": "MIT", - "peer": true, "dependencies": { "@samverschueren/stream-to-observable": "^0.3.0", "is-observable": "^1.1.0", @@ -6943,9 +7485,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true, "license": "MIT" }, @@ -7235,9 +7777,9 @@ "license": "ISC" }, "node_modules/markdown-it": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", - "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "license": "MIT", "dependencies": { "argparse": "^2.0.1", @@ -7251,6 +7793,12 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", @@ -7530,22 +8078,21 @@ "license": "MIT" }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", + "peer": true, "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", - "engines": { - "node": ">=18" - }, + "peer": true, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -7577,6 +8124,16 @@ "node": ">=10.4.0" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -8154,6 +8711,19 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "peer": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -8164,19 +8734,24 @@ } }, "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { - "mime-db": "^1.54.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, "node_modules/mimic-fn": { @@ -8231,7 +8806,34 @@ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "license": "ISC", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/mitt": { @@ -8244,7 +8846,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -8294,11 +8896,19 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/nan": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.24.0.tgz", + "integrity": "sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==", + "license": "MIT", + "optional": true + }, "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -8534,6 +9144,22 @@ "node": ">=4" } }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -8544,9 +9170,9 @@ } }, "node_modules/normalize-url": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", - "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", + "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", "dev": true, "license": "MIT", "engines": { @@ -8580,6 +9206,20 @@ "node": ">=4" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -9048,14 +9688,11 @@ } }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } + "peer": true }, "node_modules/path-type": { "version": "4.0.0", @@ -9142,21 +9779,6 @@ "node": ">=0.10.0" } }, - "node_modules/pizzip": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/pizzip/-/pizzip-3.2.0.tgz", - "integrity": "sha512-X4NPNICxCfIK8VYhF6wbksn81vTiziyLbvKuORVAmolvnUzl1A1xmz9DAWKxPRq9lZg84pJOOAMq3OE61bD8IQ==", - "license": "(MIT OR GPL-3.0)", - "dependencies": { - "pako": "^2.1.0" - } - }, - "node_modules/pizzip/node_modules/pako": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", - "license": "(MIT AND Zlib)" - }, "node_modules/pkce-challenge": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", @@ -9315,17 +9937,17 @@ } }, "node_modules/puppeteer": { - "version": "24.37.3", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.37.3.tgz", - "integrity": "sha512-AUGGWq0BhPM+IOS2U9A+ZREH3HDFkV1Y5HERYGDg5cbGXjoGsTCT7/A6VZRfNU0UJJdCclyEimZICkZW6pqJyw==", + "version": "24.34.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.34.0.tgz", + "integrity": "sha512-Sdpl/zsYOsagZ4ICoZJPGZw8d9gZmK5DcxVal11dXi/1/t2eIXHjCf5NfmhDg5XnG9Nye+yo/LqMzIxie2rHTw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.12.1", - "chromium-bidi": "14.0.0", + "@puppeteer/browsers": "2.11.0", + "chromium-bidi": "12.0.1", "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1566079", - "puppeteer-core": "24.37.3", + "devtools-protocol": "0.0.1534754", + "puppeteer-core": "24.34.0", "typed-query-selector": "^2.12.0" }, "bin": { @@ -9336,27 +9958,27 @@ } }, "node_modules/puppeteer-core": { - "version": "24.37.3", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.37.3.tgz", - "integrity": "sha512-fokQ8gv+hNgsRWqVuP5rUjGp+wzV5aMTP3fcm8ekNabmLGlJdFHas1OdMscAH9Gzq4Qcf7cfI/Pe6wEcAqQhqg==", + "version": "24.34.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.34.0.tgz", + "integrity": "sha512-24evawO+mUGW4mvS2a2ivwLdX3gk8zRLZr9HP+7+VT2vBQnm0oh9jJEZmUE3ePJhRkYlZ93i7OMpdcoi2qNCLg==", "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.12.1", - "chromium-bidi": "14.0.0", + "@puppeteer/browsers": "2.11.0", + "chromium-bidi": "12.0.1", "debug": "^4.4.3", - "devtools-protocol": "0.0.1566079", + "devtools-protocol": "0.0.1534754", "typed-query-selector": "^2.12.0", - "webdriver-bidi-protocol": "0.4.1", - "ws": "^8.19.0" + "webdriver-bidi-protocol": "0.3.10", + "ws": "^8.18.3" }, "engines": { "node": ">=18" } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -9726,7 +10348,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -9742,7 +10364,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -9753,8 +10375,8 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, + "deprecated": "Glob versions prior to v9 are no longer supported", + "devOptional": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -9775,7 +10397,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -9806,6 +10428,16 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/run-applescript": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", @@ -9982,9 +10614,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -9994,31 +10626,47 @@ } }, "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "license": "MIT", + "peer": true, "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" } }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -10121,24 +10769,28 @@ } }, "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "license": "MIT", + "peer": true, "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -10280,7 +10932,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", "dependencies": { @@ -10412,6 +11064,65 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-get/node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/simple-get/node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -10820,6 +11531,24 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tar-fs": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", @@ -10861,10 +11590,20 @@ "node": ">=6" } }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/terser": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", - "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -10915,17 +11654,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -10934,9 +11662,9 @@ "license": "MIT" }, "node_modules/text-decoder": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", - "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", "license": "Apache-2.0", "dependencies": { "b4a": "^1.6.4" @@ -11012,12 +11740,12 @@ } }, "node_modules/token-types": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", - "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", + "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", "license": "MIT", "dependencies": { - "@borewit/text-codec": "^0.2.1", + "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" }, @@ -11212,14 +11940,14 @@ } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", + "peer": true, "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" }, "engines": { "node": ">= 0.6" @@ -11252,7 +11980,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11361,9 +12088,9 @@ } }, "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -11530,6 +12257,16 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -11584,9 +12321,9 @@ } }, "node_modules/watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.0.tgz", + "integrity": "sha512-e6vZvY6xboSwLz2GD36c16+O/2Z6fKvIf4pOXptw2rY9MVwE/TXc6RGqxD3I3x0a28lwBY7DE+76uTPSsBrrCA==", "dev": true, "license": "MIT", "dependencies": { @@ -11608,9 +12345,9 @@ } }, "node_modules/webdriver-bidi-protocol": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz", - "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.3.10.tgz", + "integrity": "sha512-5LAE43jAVLOhB/QqX4bwSiv0Hg1HBfMmOuwBSXHdvg4GMGu9Y0lIq7p4R/yySu6w74WmaR4GM4H9t2IwLW7hgw==", "license": "Apache-2.0" }, "node_modules/webidl-conversions": { @@ -11620,12 +12357,11 @@ "license": "BSD-2-Clause" }, "node_modules/webpack": { - "version": "5.105.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.2.tgz", - "integrity": "sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==", + "version": "5.104.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", + "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -11637,7 +12373,7 @@ "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.19.0", + "enhanced-resolve": "^5.17.4", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -11650,7 +12386,7 @@ "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.5.1", + "watchpack": "^2.4.4", "webpack-sources": "^3.3.3" }, "bin": { @@ -11675,7 +12411,6 @@ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", @@ -11781,38 +12516,15 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", - "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -11839,9 +12551,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, "license": "MIT", "dependencies": { @@ -11860,6 +12572,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", @@ -11949,9 +12693,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -12009,6 +12753,13 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -12138,7 +12889,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "deprecated": "Glob versions prior to v9 are no longer supported", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -12172,7 +12923,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 2d2a782e..36331caf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@wonderwhy-er/desktop-commander", - "version": "0.2.37", + "version": "0.2.36", "description": "MCP server for terminal operations and file editing", "mcpName": "io.github.wonderwhy-er/desktop-commander", "license": "MIT", @@ -18,6 +18,11 @@ }, "files": [ "dist", + "bin/macos", + "native/macos-ax-helper", + "build-macos-helper.sh", + "macos-control.md", + "known-limitations.md", "logo.png", "testemonials" ], @@ -32,6 +37,7 @@ "bump:minor": "node scripts/sync-version.js --bump --minor", "bump:major": "node scripts/sync-version.js --bump --major", "build": "tsc && shx cp setup-claude-server.js uninstall-claude-server.js track-installation.js dist/ && shx chmod +x dist/*.js && shx mkdir -p dist/data && shx cp src/data/onboarding-prompts.json dist/data/ && shx mkdir -p dist/remote-device/scripts && shx cp src/remote-device/scripts/blocking-offline-update.js dist/remote-device/scripts/ && node scripts/build-ui-runtime.cjs file-preview", + "build:macos-helper": "bash ./build-macos-helper.sh", "watch": "tsc --watch", "start": "node dist/index.js", "start:debug": "node --inspect-brk=9229 dist/index.js", @@ -96,7 +102,6 @@ "md-to-pdf": "^5.2.5", "open": "^10.2.0", "pdf-lib": "^1.17.1", - "pizzip": "^3.2.0", "remark": "^15.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", diff --git a/plugin.yaml b/plugin.yaml deleted file mode 100644 index ece21952..00000000 --- a/plugin.yaml +++ /dev/null @@ -1,51 +0,0 @@ -# plugin.yaml — DesktopCommanderMCP Plugin Manifest -# This file makes DesktopCommanderMCP discoverable as an agent plugin. -# Learn more: https://list.agenium.net/plugins - -name: desktop-commander-mcp -display_name: DesktopCommanderMCP -manifest_version: "1.0" -description: > - MCP server that gives AI agents terminal control, file system search, - and diff-based file editing capabilities. Run shell commands, search files, - and edit code — all through the Model Context Protocol. - -author: - name: wonderwhy-er - url: https://github.com/wonderwhy-er - -repository: https://github.com/wonderwhy-er/DesktopCommanderMCP -license: MIT - -categories: - - mcp-servers - - developer-tools - - system-utilities - -tags: - - mcp - - model-context-protocol - - terminal - - file-system - - diff-editing - - shell-commands - - code-editing - -capabilities: - - terminal-control - - file-search - - diff-editing - - process-management - - file-system-operations - -install: - npx: "@wonderwhy-er/desktop-commander" - -runtime: - language: typescript - min_node: "18" - -protocols: - - mcp -transport: - - stdio diff --git a/scripts/build-mcpb.cjs b/scripts/build-mcpb.cjs index f1dc3a40..aaf4656a 100755 --- a/scripts/build-mcpb.cjs +++ b/scripts/build-mcpb.cjs @@ -81,8 +81,12 @@ console.log('✅ Created manifest.json'); // Step 5: Copy necessary files const filesToCopy = [ 'dist', + 'bin', + 'native/macos-ax-helper', 'package.json', 'README.md', + 'macos-control.md', + 'known-limitations.md', 'LICENSE', 'PRIVACY.md', 'icon.png' @@ -205,4 +209,4 @@ console.log(''); console.log('To submit to Anthropic directory:'); console.log('- Ensure privacy policy is accessible at the GitHub URL'); console.log('- Complete destructive operation annotations (✅ Done)'); -console.log('- Submit via Anthropic desktop extensions interest form'); \ No newline at end of file +console.log('- Submit via Anthropic desktop extensions interest form'); diff --git a/server.json b/server.json index e1debe4a..305b546b 100644 --- a/server.json +++ b/server.json @@ -7,13 +7,13 @@ "url": "https://github.com/wonderwhy-er/DesktopCommanderMCP", "source": "github" }, - "version": "0.2.37", + "version": "0.2.36", "packages": [ { "registryType": "npm", "registryBaseUrl": "https://registry.npmjs.org", "identifier": "@wonderwhy-er/desktop-commander", - "version": "0.2.37", + "version": "0.2.36", "transport": { "type": "stdio" } diff --git a/src/handlers/index.ts b/src/handlers/index.ts index 1ac19090..edf78793 100644 --- a/src/handlers/index.ts +++ b/src/handlers/index.ts @@ -5,3 +5,4 @@ export * from './process-handlers.js'; export * from './edit-search-handlers.js'; export * from './search-handlers.js'; export * from './history-handlers.js'; +export * from './macos-control-handlers.js'; diff --git a/src/handlers/macos-control-handlers.ts b/src/handlers/macos-control-handlers.ts new file mode 100644 index 00000000..5d4d78be --- /dev/null +++ b/src/handlers/macos-control-handlers.ts @@ -0,0 +1,115 @@ +import { + MacosAxStatusArgsSchema, + MacosAxListAppsArgsSchema, + MacosAxListElementsArgsSchema, + MacosAxFindArgsSchema, + MacosAxGetStateArgsSchema, + MacosAxFindAndClickArgsSchema, + MacosAxClickArgsSchema, + MacosAxTypeArgsSchema, + MacosAxKeyArgsSchema, + MacosAxActivateArgsSchema, + MacosAxWaitForArgsSchema, + MacosAxBatchArgsSchema, + ElectronDebugAttachArgsSchema, + ElectronDebugEvalArgsSchema, + ElectronDebugDisconnectArgsSchema, +} from '../tools/schemas.js'; +import { ServerResult } from '../types.js'; +import { macosControlOrchestrator } from '../tools/macos-control/orchestrator.js'; + +function toServerResult(result: any): ServerResult { + if (!result?.ok) { + const message = result?.error?.message || 'macOS control request failed'; + const code = result?.error?.code ? ` (${result.error.code})` : ''; + return { + content: [{ + type: 'text', + text: `Error${code}: ${message}`, + }], + isError: true, + }; + } + + return { + content: [{ + type: 'text', + text: JSON.stringify(result.data ?? {}, null, 2), + }], + }; +} + +export async function handleMacosAxStatus(args: unknown): Promise { + MacosAxStatusArgsSchema.parse(args || {}); + return toServerResult(await macosControlOrchestrator.axStatus()); +} + +export async function handleMacosAxListApps(args: unknown): Promise { + MacosAxListAppsArgsSchema.parse(args || {}); + return toServerResult(await macosControlOrchestrator.axListApps()); +} + +export async function handleMacosAxListElements(args: unknown): Promise { + const parsed = MacosAxListElementsArgsSchema.parse(args || {}); + return toServerResult(await macosControlOrchestrator.axListElements(parsed)); +} + +export async function handleMacosAxFind(args: unknown): Promise { + const parsed = MacosAxFindArgsSchema.parse(args); + return toServerResult(await macosControlOrchestrator.axFind(parsed)); +} + +export async function handleMacosAxGetState(args: unknown): Promise { + const parsed = MacosAxGetStateArgsSchema.parse(args); + return toServerResult(await macosControlOrchestrator.axGetState(parsed)); +} + +export async function handleMacosAxFindAndClick(args: unknown): Promise { + const parsed = MacosAxFindAndClickArgsSchema.parse(args); + return toServerResult(await macosControlOrchestrator.axFindAndClick(parsed)); +} + +export async function handleMacosAxClick(args: unknown): Promise { + const parsed = MacosAxClickArgsSchema.parse(args); + return toServerResult(await macosControlOrchestrator.axClick(parsed)); +} + +export async function handleMacosAxType(args: unknown): Promise { + const parsed = MacosAxTypeArgsSchema.parse(args); + return toServerResult(await macosControlOrchestrator.axType(parsed.text)); +} + +export async function handleMacosAxKey(args: unknown): Promise { + const parsed = MacosAxKeyArgsSchema.parse(args); + return toServerResult(await macosControlOrchestrator.axKey(parsed.key, parsed.modifiers ?? [])); +} + +export async function handleMacosAxActivate(args: unknown): Promise { + const parsed = MacosAxActivateArgsSchema.parse(args); + return toServerResult(await macosControlOrchestrator.axActivate(parsed.app)); +} + +export async function handleMacosAxWaitFor(args: unknown): Promise { + const parsed = MacosAxWaitForArgsSchema.parse(args); + return toServerResult(await macosControlOrchestrator.axWaitFor(parsed)); +} + +export async function handleMacosAxBatch(args: unknown): Promise { + const parsed = MacosAxBatchArgsSchema.parse(args); + return toServerResult(await macosControlOrchestrator.axBatch(parsed.commands as any, parsed.stopOnError)); +} + +export async function handleElectronDebugAttach(args: unknown): Promise { + const parsed = ElectronDebugAttachArgsSchema.parse(args || {}); + return toServerResult(await macosControlOrchestrator.electronDebugAttach(parsed)); +} + +export async function handleElectronDebugEval(args: unknown): Promise { + const parsed = ElectronDebugEvalArgsSchema.parse(args); + return toServerResult(await macosControlOrchestrator.electronDebugEval(parsed)); +} + +export async function handleElectronDebugDisconnect(args: unknown): Promise { + const parsed = ElectronDebugDisconnectArgsSchema.parse(args); + return toServerResult(await macosControlOrchestrator.electronDebugDisconnect(parsed)); +} diff --git a/src/remote-device/remote-channel.ts b/src/remote-device/remote-channel.ts index dc08f743..704f1b7e 100644 --- a/src/remote-device/remote-channel.ts +++ b/src/remote-device/remote-channel.ts @@ -50,30 +50,14 @@ export class RemoteChannel { access_token: session.access_token, refresh_token: session.refresh_token || '' }); - - if (error) { - console.error('[DEBUG] Failed to set session:', error.message); - await captureRemote('remote_channel_set_session_error', { error }); - return { error }; - } - // Get user info const { data: { user }, error: userError } = await this.client.auth.getUser(); if (userError) { - console.error('[DEBUG] Failed to get user:', userError.message); - await captureRemote('remote_channel_get_user_error', { error: userError }); + console.debug('[DEBUG] Failed to get user:', userError.message); throw userError; } - - if (!user) { - const noUserError = new Error('No user returned after setSession'); - console.error('[DEBUG] No user returned:', noUserError.message); - await captureRemote('remote_channel_get_user_empty', {}); - throw noUserError; - } - this._user = user; - console.debug('[DEBUG] Session set successfully, user:', user.email); + console.debug('[DEBUG] Session set successfully, user:', user?.email); return { error }; } @@ -92,46 +76,25 @@ export class RemoteChannel { .eq('user_id', this.user?.id) .maybeSingle(); - if (error) { - console.error('[DEBUG] Failed to find device:', error.message); - await captureRemote('remote_channel_find_device_error', { error }); - throw error; - } + if (error) throw error; return data; } async updateDevice(deviceId: string, updates: any) { if (!this.client) throw new Error('Client not initialized'); - const { data, error } = await this.client + return await this.client .from('mcp_devices') .update(updates) - .eq('id', deviceId) - .select(); - - if (error) { - console.error('[DEBUG] Failed to update device:', error.message); - await captureRemote('remote_channel_update_device_error', { error }); - } else { - console.debug('[DEBUG] Device updated successfully'); - } - return { data, error }; + .eq('id', deviceId); } async createDevice(deviceData: DeviceData) { if (!this.client) throw new Error('Client not initialized'); - const { data, error } = await this.client + return await this.client .from('mcp_devices') .insert(deviceData) .select() .single(); - - if (error) { - console.error('[DEBUG] Failed to create device:', error.message); - await captureRemote('remote_channel_create_device_error', { error }); - throw error; - } - console.debug('[DEBUG] Device created successfully'); - return { data, error }; } async registerDevice(capabilities: any, currentDeviceId: string | undefined, deviceName: string, onToolCall: (payload: any) => void): Promise { @@ -163,12 +126,7 @@ export class RemoteChannel { // Create and subscribe to the channel console.debug('[DEBUG] Calling createChannel()'); - - // ! Ignore silently in Initialization to reconnect after - await this.createChannel().catch((error) => { - console.debug('[DEBUG] Failed to create channel, will retry after socket reconnect', error); - }); - + await this.createChannel(); } else { console.error(` - ❌ Device not found: ${currentDeviceId}`); await captureRemote('remote_channel_register_device_error', { error: 'Device not found', deviceId: currentDeviceId }); @@ -176,6 +134,20 @@ export class RemoteChannel { } } + + async subscribe(deviceId: string, onToolCall: (payload: any) => void): Promise { + if (!this.client) throw new Error('Client not initialized'); + + // Store parameters for channel recreation + this.deviceId = deviceId; + this.onToolCall = onToolCall; + + console.debug(`⏳ Subscribing to tool call channel...`); + + // Create and subscribe to the channel + await this.createChannel(); + } + /** * Create and subscribe to the channel. * This is used for both initial subscription and recreation after socket reconnects. @@ -223,7 +195,7 @@ export class RemoteChannel { captureRemote('remote_channel_subscription_error', { error: err || 'Channel error' }).catch(() => { }); reject(err || new Error('Failed to initialize tool call channel subscription')); } else if (status === 'TIMED_OUT') { - console.error('⏱️ Channel subscription timed out, Reconnecting...'); + console.error('⏱️ Channel subscription timed out'); this.setOnlineStatus(this.deviceId!, 'offline'); captureRemote('remote_channel_subscription_timeout', {}).catch(() => { }); reject(new Error('Tool call channel subscription timed out')); @@ -289,17 +261,10 @@ export class RemoteChannel { async markCallExecuting(callId: string) { if (!this.client) throw new Error('Client not initialized'); - const { error } = await this.client + await this.client .from('mcp_remote_calls') .update({ status: 'executing' }) .eq('id', callId); - - if (error) { - console.error('[DEBUG] Failed to mark call executing:', error.message); - await captureRemote('remote_channel_mark_call_executing_error', { error }); - } else { - console.debug('[DEBUG] Call marked executing:', callId); - } } async updateCallResult(callId: string, status: string, result: any = null, errorMessage: string | null = null) { @@ -312,32 +277,19 @@ export class RemoteChannel { if (result !== null) updateData.result = result; if (errorMessage !== null) updateData.error_message = errorMessage; - console.debug('[DEBUG] Updating call result:', updateData); - const { data, error } = await this.client + await this.client .from('mcp_remote_calls') .update(updateData) .eq('id', callId); - - if (error) { - console.error('[DEBUG] Failed to update call result:', error.message); - await captureRemote('remote_channel_update_call_result_error', { error }); - } else { - console.debug('[DEBUG] Call result updated successfully:', data); - } } async updateHeartbeat(deviceId: string) { if (!this.client) return; try { - const { error } = await this.client + await this.client .from('mcp_devices') .update({ last_seen: new Date().toISOString() }) .eq('id', deviceId); - - if (error) { - console.error('[DEBUG] Heartbeat update failed:', error.message); - await captureRemote('remote_channel_heartbeat_error', { error }); - } // console.log(`🔌 Heartbeat sent for device: ${deviceId}`); } catch (error: any) { console.error('Heartbeat failed:', error.message); @@ -384,14 +336,11 @@ export class RemoteChannel { .eq('id', deviceId); if (error) { - console.error(`[DEBUG] Failed to set status ${status}:`, error.message); if (status == "online") { console.error('Failed to update device status:', error.message); } await captureRemote('remote_channel_status_update_error', { error, status }); return; - } else { - console.debug(`[DEBUG] Device status set to ${status}`); } // console.log(status === 'online' ? `🔌 Device marked as ${status}` : `❌ Device marked as ${status}`); diff --git a/src/search-manager.ts b/src/search-manager.ts index 3bbe861d..8ebacb96 100644 --- a/src/search-manager.ts +++ b/src/search-manager.ts @@ -5,7 +5,6 @@ import { validatePath } from './tools/filesystem.js'; import { capture } from './utils/capture.js'; import { getRipgrepPath } from './utils/ripgrep-resolver.js'; import { isExcelFile } from './utils/files/index.js'; -import PizZip from 'pizzip'; export interface SearchResult { file: string; @@ -172,27 +171,6 @@ export interface SearchSessionOptions { }); } - // For content searches, also search DOCX files - const shouldSearchDocx = options.searchType === 'content' && - this.shouldIncludeDocxSearch(options.filePattern, validPath); - - if (shouldSearchDocx) { - this.searchDocxFiles( - validPath, - options.pattern, - options.ignoreCase !== false, - options.maxResults, - options.filePattern - ).then(docxResults => { - for (const result of docxResults) { - session.results.push(result); - session.totalMatches++; - } - }).catch((err) => { - capture('docx_search_error', { error: err instanceof Error ? err.message : String(err) }); - }); - } - // Wait for first chunk of data or early completion instead of fixed delay // Excel search runs in background and results are merged via readSearchResults const firstChunk = new Promise(resolve => { @@ -491,157 +469,6 @@ export interface SearchSessionOptions { return excelFiles; } - /** - * Determine if DOCX search should be included based on context - */ - private shouldIncludeDocxSearch(filePattern?: string, rootPath?: string): boolean { - const docxExtensions = ['.docx']; - - if (rootPath) { - const lowerPath = rootPath.toLowerCase(); - if (docxExtensions.some(ext => lowerPath.endsWith(ext))) { - return true; - } - } - - if (filePattern) { - const lowerPattern = filePattern.toLowerCase(); - if (docxExtensions.some(ext => - lowerPattern.includes(`*${ext}`) || lowerPattern.endsWith(ext) - )) { - return true; - } - } - - return false; - } - - /** - * Search DOCX files for content matches - * Extracts text from document.xml and searches it - */ - private async searchDocxFiles( - rootPath: string, - pattern: string, - ignoreCase: boolean, - maxResults?: number, - filePattern?: string - ): Promise { - const results: SearchResult[] = []; - - const flags = ignoreCase ? 'i' : ''; - let regex: RegExp; - try { - regex = new RegExp(pattern, flags); - } catch { - const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - regex = new RegExp(escaped, flags); - } - - let docxFiles = await this.findDocxFiles(rootPath); - - if (filePattern) { - const patterns = filePattern.split('|').map(p => p.trim()).filter(Boolean); - docxFiles = docxFiles.filter(filePath => { - const fileName = path.basename(filePath); - return patterns.some(pat => { - if (pat.includes('*')) { - const regexPat = pat.replace(/\./g, '\\.').replace(/\*/g, '.*'); - return new RegExp(`^${regexPat}$`, 'i').test(fileName); - } - return fileName.toLowerCase() === pat.toLowerCase(); - }); - }); - } - - for (const filePath of docxFiles) { - if (maxResults && results.length >= maxResults) break; - - try { - const buf = await fs.readFile(filePath); - const zip = new PizZip(buf); - - // Search all XML parts that can contain text - const xmlParts = ['word/document.xml', 'word/header1.xml', 'word/header2.xml', - 'word/header3.xml', 'word/footer1.xml', 'word/footer2.xml', 'word/footer3.xml']; - - for (const xmlPath of xmlParts) { - if (maxResults && results.length >= maxResults) break; - - const file = zip.file(xmlPath); - if (!file) continue; - - const xml = file.asText(); - // Extract all text with position tracking - const wtRe = /]*)?>([^<]*)<\/w:t>/g; - let m; - let lineNum = 0; - - while ((m = wtRe.exec(xml)) !== null) { - if (maxResults && results.length >= maxResults) break; - const text = m[1]; - if (!text || !text.trim()) continue; - lineNum++; - - if (regex.test(text)) { - const match = text.match(regex); - const matchContext = match - ? this.getMatchContext(text, match.index || 0, match[0].length) - : text.substring(0, 150); - - const partName = xmlPath === 'word/document.xml' ? '' : `:${xmlPath.replace('word/', '')}`; - results.push({ - file: `${filePath}${partName}`, - line: lineNum, - match: matchContext, - type: 'content' - }); - } - } - } - } catch { - continue; - } - } - - return results; - } - - /** - * Find all DOCX files in a directory recursively - */ - private async findDocxFiles(rootPath: string): Promise { - const docxFiles: string[] = []; - const isDocx = (name: string) => name.toLowerCase().endsWith('.docx'); - - async function walk(dir: string): Promise { - try { - const entries = await fs.readdir(dir, { withFileTypes: true }); - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) { - if (!entry.name.startsWith('.') && entry.name !== 'node_modules') { - await walk(fullPath); - } - } else if (entry.isFile() && isDocx(entry.name)) { - docxFiles.push(fullPath); - } - } - } catch { /* skip */ } - } - - try { - const stats = await fs.stat(rootPath); - if (stats.isFile() && isDocx(rootPath)) { - return [rootPath]; - } else if (stats.isDirectory()) { - await walk(rootPath); - } - } catch { /* skip */ } - - return docxFiles; - } - /** * Extract context around a match for display (show surrounding text) */ diff --git a/src/server.ts b/src/server.ts index 2d48a90b..8ab56feb 100644 --- a/src/server.ts +++ b/src/server.ts @@ -50,6 +50,21 @@ import { GetPromptsArgsSchema, GetRecentToolCallsArgsSchema, WritePdfArgsSchema, + MacosAxStatusArgsSchema, + MacosAxListAppsArgsSchema, + MacosAxListElementsArgsSchema, + MacosAxFindArgsSchema, + MacosAxGetStateArgsSchema, + MacosAxFindAndClickArgsSchema, + MacosAxClickArgsSchema, + MacosAxTypeArgsSchema, + MacosAxKeyArgsSchema, + MacosAxActivateArgsSchema, + MacosAxWaitForArgsSchema, + MacosAxBatchArgsSchema, + ElectronDebugAttachArgsSchema, + ElectronDebugEvalArgsSchema, + ElectronDebugDisconnectArgsSchema, } from './tools/schemas.js'; import { getConfig, setConfigValue } from './tools/config.js'; import { getUsageStats } from './tools/usage.js'; @@ -316,19 +331,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { - PDF: Extracts text content as markdown with page structure * offset/length work as page pagination (0-based) * Includes embedded images when available - - DOCX (.docx): Two modes depending on parameters: - * DEFAULT (no offset/length): Returns a text-bearing outline — shows paragraphs with text, - tables with cell content, styles, image refs. Skips shapes/drawings/SVG noise. - Each element shows its body index [0], [1], etc. - * WITH offset/length: Returns raw pretty-printed XML with line pagination. - Use this to drill into specific sections or see the actual XML for editing. - * EDITING WORKFLOW: 1) read_file to get outline, 2) read_file with offset/length - to see raw XML around what you want to edit, 3) edit_block with old_string/new_string - using XML fragments copied from the read output. - * IMPORTANT: offset MUST be non-zero to get raw XML (use offset=1 to start from line 1). - offset=0 always returns the outline regardless of length. - * For BULK changes (translation, mass replacements): use start_process with Python - zipfile module to find/replace all elements at once. ${PATH_GUIDANCE} ${CMD_PREFIX_DESCRIPTION}`, @@ -366,8 +368,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { Write or append to file contents. IMPORTANT: DO NOT use this tool to create PDF files. Use 'write_pdf' for all PDF creation tasks. - DO NOT use this tool to edit DOCX files. Use 'edit_block' with old_string/new_string instead. - To CREATE a new DOCX, use write_file with .docx extension — text content with markdown headings (#, ##, ###) is converted to styled DOCX paragraphs. CHUNKING IS STANDARD PRACTICE: Always write files in chunks of 25-30 lines maximum. This is the normal, recommended way to write files - not an emergency measure. @@ -747,17 +747,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { - new_string: Replacement text - expected_replacements: Optional number of replacements (default: 1) - DOCX FILES (.docx) - XML Find/Replace mode: - Takes same parameters as text files (old_string, new_string, expected_replacements). - Operates on the pretty-printed XML inside the DOCX — the same XML you see from - read_file with offset/length. Copy XML fragments from read output as old_string. - After editing, the XML is repacked into a valid DOCX. - Also searches headers/footers if not found in document body. - Examples: - - Replace text: old_string="Old Text" new_string="New Text" - - Change style: old_string='' new_string='' - - Add content: include surrounding XML context in old_string, add new elements in new_string - By default, replaces only ONE occurrence of the search text. To replace multiple occurrences, provide expected_replacements with the exact number of matches expected. @@ -1005,6 +994,287 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { readOnlyHint: true, }, }, + { + name: "macos_ax_status", + description: ` + Check macOS accessibility control status. + + Returns: + - whether Accessibility permissions are granted + - helper binary path/version details + - process info for permission troubleshooting + + Use this first when AX tools fail unexpectedly (permission/helper issues). + + This tool is macOS-only and returns an unsupported-platform error elsewhere. + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(MacosAxStatusArgsSchema), + annotations: { + title: "macOS AX Status", + readOnlyHint: true, + }, + }, + { + name: "macos_ax_list_apps", + description: ` + List regular (windowed) macOS applications available for AX automation. + + Returns app names and PIDs for use with other macOS AX tools. + Use this when the app name is unclear or to avoid partial-name mistakes. + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(MacosAxListAppsArgsSchema), + annotations: { + title: "macOS AX List Apps", + readOnlyHint: true, + }, + }, + { + name: "macos_ax_find", + description: ` + Find interactive accessibility elements in a target app using text and/or role filters. + + Inputs: + - app: app name or PID + - text: optional case-insensitive partial text + - role: optional AX role or alias (toggle, button, input, etc.) + - index/depth/limit: optional search controls + + Important: + - At least one of text or role is required + - Role-only search is supported (for unlabeled controls) + - If the control is unknown/unlabeled, use macos_ax_list_elements first + - Prefer macos_ax_find_and_click when you want immediate click after find + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(MacosAxFindArgsSchema), + annotations: { + title: "macOS AX Find", + readOnlyHint: true, + }, + }, + { + name: "macos_ax_list_elements", + description: ` + Inspect interactive accessibility elements with optional scope/app/text/role filters. + + Use this when the target control is unlabeled or unknown. This is the primary UI inspection tool for macOS AX automation. + Prefer this over repeated guess-based macos_ax_find calls. + + Scopes: + - top_window (default) + - app (requires app) + - all + + Typical workflow: + 1. macos_ax_activate (optional) + 2. macos_ax_list_elements (inspect current pane) + 3. macos_ax_get_state or macos_ax_find_and_click + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(MacosAxListElementsArgsSchema), + annotations: { + title: "macOS AX List Elements", + readOnlyHint: true, + }, + }, + { + name: "macos_ax_get_state", + description: ` + Find a UI element and return state fields (checked/selected/text) without clicking. + + Useful for idempotent flows like toggles in System Settings. + Supports role-only lookups for unlabeled controls (for example role=toggle). + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(MacosAxGetStateArgsSchema), + annotations: { + title: "macOS AX Get State", + readOnlyHint: true, + }, + }, + { + name: "macos_ax_find_and_click", + description: ` + Find a UI element by text and/or role, then click it immediately. + + Reduces stale-id issues and returns the matched element plus click result. + Prefer this over separate find + click when you do not need to inspect the element first. + Supports role-only search, index selection, and optional timeout polling. + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(MacosAxFindAndClickArgsSchema), + annotations: { + title: "macOS AX Find and Click", + readOnlyHint: false, + destructiveHint: true, + openWorldHint: true, + }, + }, + { + name: "macos_ax_click", + description: ` + Click a macOS UI element by stable AX id, or by app+text search. + + Preferred usage: + 1. macos_ax_list_elements or macos_ax_find to discover element id + 2. macos_ax_click with that id + + Includes stale-id refind fallback when possible. + If you are locating and clicking in one step, prefer macos_ax_find_and_click. + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(MacosAxClickArgsSchema), + annotations: { + title: "macOS AX Click", + readOnlyHint: false, + destructiveHint: true, + openWorldHint: true, + }, + }, + { + name: "macos_ax_type", + description: ` + Type text via macOS keyboard event injection into the currently focused element. + + Use macos_ax_click or macos_ax_find_and_click first to focus the target input. + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(MacosAxTypeArgsSchema), + annotations: { + title: "macOS AX Type", + readOnlyHint: false, + destructiveHint: true, + openWorldHint: true, + }, + }, + { + name: "macos_ax_key", + description: ` + Press a keyboard key with optional modifiers (cmd/shift/alt/ctrl) via macOS event injection. + + Useful for navigation and shortcuts after focus is already on the target UI. + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(MacosAxKeyArgsSchema), + annotations: { + title: "macOS AX Key", + readOnlyHint: false, + destructiveHint: true, + openWorldHint: true, + }, + }, + { + name: "macos_ax_activate", + description: ` + Activate (bring to front) an application by name or PID. + + Use before list/find/click when automating a background app or when focus matters. + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(MacosAxActivateArgsSchema), + annotations: { + title: "macOS AX Activate App", + readOnlyHint: false, + destructiveHint: false, + openWorldHint: true, + }, + }, + { + name: "macos_ax_wait_for", + description: ` + Wait for a target UI element to appear in a macOS app, polling until timeout. + + Useful after navigation where UI state changes asynchronously. + Best when you know expected text (and optionally role). + If text is unknown, use macos_ax_list_elements after a short wait. + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(MacosAxWaitForArgsSchema), + annotations: { + title: "macOS AX Wait For", + readOnlyHint: true, + }, + }, + { + name: "macos_ax_batch", + description: ` + Execute a sequence of macOS AX actions in one tool call. + + This is the preferred tool for multi-step UI automation because it reduces model round trips and runs as a single native helper request. + + Supported actions: + - activate, find, click, find_and_click, get_state, scroll, type, key, wait, wait_for + + Action notes: + - find/click/find_and_click/get_state support text and/or role filters + - click supports id lookup or app+text/role lookup + - role aliases: toggle, button, input, menu, row, link, list + - use if_exists=true to skip optional UI without failing the batch + + Recommended toggle pattern: + 1. activate / navigate + 2. wait or wait_for + 3. get_state (role=toggle) + 4. click only if needed + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(MacosAxBatchArgsSchema), + annotations: { + title: "macOS AX Batch", + readOnlyHint: false, + destructiveHint: true, + openWorldHint: true, + }, + }, + { + name: "electron_debug_attach", + description: ` + Attach to an Electron/Chromium app exposing a Chrome DevTools Protocol (CDP) endpoint. + + Connects to host+port, selects a target, and returns a sessionId for subsequent calls. + Use this first before electron_debug_eval. + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(ElectronDebugAttachArgsSchema), + annotations: { + title: "Electron Debug Attach", + readOnlyHint: false, + openWorldHint: true, + }, + }, + { + name: "electron_debug_eval", + description: ` + Evaluate JavaScript via CDP Runtime.evaluate for an attached Electron debug session. + + Use for targeted inspection or automation after electron_debug_attach returns a sessionId. + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(ElectronDebugEvalArgsSchema), + annotations: { + title: "Electron Debug Eval", + readOnlyHint: false, + destructiveHint: true, + openWorldHint: true, + }, + }, + { + name: "electron_debug_disconnect", + description: ` + Disconnect an active Electron debug session by sessionId. + + Call this when finished to clean up the debug session. + + ${CMD_PREFIX_DESCRIPTION}`, + inputSchema: zodToJsonSchema(ElectronDebugDisconnectArgsSchema), + annotations: { + title: "Electron Debug Disconnect", + readOnlyHint: false, + openWorldHint: true, + }, + }, { name: "list_processes", description: ` @@ -1348,6 +1618,67 @@ server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) result = await handlers.handleListSessions(); break; + // macOS control tools + case "macos_ax_status": + result = await handlers.handleMacosAxStatus(args); + break; + + case "macos_ax_list_apps": + result = await handlers.handleMacosAxListApps(args); + break; + + case "macos_ax_find": + result = await handlers.handleMacosAxFind(args); + break; + + case "macos_ax_list_elements": + result = await handlers.handleMacosAxListElements(args); + break; + + case "macos_ax_get_state": + result = await handlers.handleMacosAxGetState(args); + break; + + case "macos_ax_find_and_click": + result = await handlers.handleMacosAxFindAndClick(args); + break; + + case "macos_ax_click": + result = await handlers.handleMacosAxClick(args); + break; + + case "macos_ax_type": + result = await handlers.handleMacosAxType(args); + break; + + case "macos_ax_key": + result = await handlers.handleMacosAxKey(args); + break; + + case "macos_ax_activate": + result = await handlers.handleMacosAxActivate(args); + break; + + case "macos_ax_wait_for": + result = await handlers.handleMacosAxWaitFor(args); + break; + + case "macos_ax_batch": + result = await handlers.handleMacosAxBatch(args); + break; + + case "electron_debug_attach": + result = await handlers.handleElectronDebugAttach(args); + break; + + case "electron_debug_eval": + result = await handlers.handleElectronDebugEval(args); + break; + + case "electron_debug_disconnect": + result = await handlers.handleElectronDebugDisconnect(args); + break; + // Process tools case "list_processes": result = await handlers.handleListProcesses(); diff --git a/src/tools/edit.ts b/src/tools/edit.ts index 5933b9cc..1e1c599a 100644 --- a/src/tools/edit.ts +++ b/src/tools/edit.ts @@ -371,90 +371,57 @@ function highlightDifferences(expected: string, actual: string): string { export async function handleEditBlock(args: unknown): Promise { const parsed = EditBlockArgsSchema.parse(args); + // Structured files: Range rewrite // Note: Check for truthy range to handle empty strings from AI clients that send all optional params const hasRange = parsed.range !== undefined && parsed.range !== ''; const hasContent = parsed.content !== undefined && parsed.content !== ''; + if (hasRange && hasContent) { + try { + // Validate path before any filesystem operations + const validatedPath = await validatePath(parsed.file_path); - // Validate path and resolve handler once — used by both dispatch paths below - let validatedPath: string; - let handler: Awaited>; - try { - validatedPath = await validatePath(parsed.file_path); - const { getFileHandler } = await import('../utils/files/factory.js'); - handler = await getFileHandler(validatedPath); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - return createErrorResponse(errorMessage); - } - - const hasEditRange = 'editRange' in handler && typeof handler.editRange === 'function'; + const { getFileHandler } = await import('../utils/files/factory.js'); + const handler = await getFileHandler(validatedPath); - // Path 1: Range rewrite (Excel, etc.) — range + content - if (hasRange && hasContent) { - // Parse content if it's a JSON string (AI often sends arrays as JSON strings) - let content = parsed.content; - if (typeof content === 'string') { - try { - content = JSON.parse(content); - } catch { - // Leave as-is if not valid JSON - let handler decide + // Parse content if it's a JSON string (AI often sends arrays as JSON strings) + let content = parsed.content; + if (typeof content === 'string') { + try { + content = JSON.parse(content); + } catch { + // Leave as-is if not valid JSON - let handler decide + } } - } - if (hasEditRange) { - try { + // Check if handler supports range editing + if ('editRange' in handler && typeof handler.editRange === 'function') { // parsed.range is guaranteed non-empty string by hasRange check above - await handler.editRange!(validatedPath!, parsed.range!, content, parsed.options); + await handler.editRange(validatedPath, parsed.range!, content, parsed.options); return { content: [{ type: "text", text: `Successfully updated range ${parsed.range} in ${parsed.file_path}` }], }; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - return createErrorResponse(errorMessage); + } else { + return createErrorResponse(`Range-based editing not supported for ${parsed.file_path}. For text files, use old_string and new_string parameters instead. If your client requires range/content parameters, set them to empty strings ("").`); } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + return createErrorResponse(errorMessage); } - - return createErrorResponse(`Range-based editing not supported for ${parsed.file_path}. For text files, use old_string and new_string parameters instead. If your client requires range/content parameters, set them to empty strings ("").`); } - // Path 2: Text replacement — old_string + new_string + // Text files: String replacement + // Validate required parameters for text replacement if (parsed.old_string === undefined || parsed.new_string === undefined) { return createErrorResponse(`Text replacement requires both old_string and new_string parameters`); } - // If the handler implements editRange it owns text-replacement for its file type - // (e.g. DocxFileHandler does find/replace on pretty-printed XML rather than raw bytes). - // Plain text files fall through to performSearchReplace. - if (hasEditRange) { - try { - const result = await handler.editRange!(validatedPath!, '', { - old_string: parsed.old_string, - new_string: parsed.new_string, - expected_replacements: parsed.expected_replacements, - }); - - if (result.success) { - return { - content: [{ - type: "text", - text: `Successfully applied ${result.editsApplied} edit(s) to ${parsed.file_path}` - }], - }; - } - - const errorMsg = result.errors?.map(e => e.error).join('; ') || 'Unknown error'; - return createErrorResponse(errorMsg); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - return createErrorResponse(errorMessage); - } - } - - return performSearchReplace(parsed.file_path, { + const searchReplace = { search: parsed.old_string, replace: parsed.new_string - }, parsed.expected_replacements); + }; + + return performSearchReplace(parsed.file_path, searchReplace, parsed.expected_replacements); } \ No newline at end of file diff --git a/src/tools/macos-control/ax-adapter.ts b/src/tools/macos-control/ax-adapter.ts new file mode 100644 index 00000000..ff8c39fd --- /dev/null +++ b/src/tools/macos-control/ax-adapter.ts @@ -0,0 +1,557 @@ +import { spawn } from 'child_process'; +import { existsSync } from 'fs'; +import { access } from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { expandRoleAlias } from './role-aliases.js'; +import { + AxBatchCommand, + AxBatchResult, + AxAppInfo, + AxElement, + AxElementSignature, + AxStatus, + HelperRequest, + HelperResponse, + MacosControlError, + MacosControlResult, +} from './types.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const PROJECT_ROOT = path.resolve(__dirname, '../../..'); + +function normalizeHelperError(code: string): MacosControlError['code'] { + switch (code) { + case 'UNSUPPORTED_PLATFORM': + return 'UNSUPPORTED_PLATFORM'; + case 'HELPER_NOT_FOUND': + return 'HELPER_NOT_FOUND'; + case 'PERMISSION_DENIED': + return 'PERMISSION_DENIED'; + case 'INVALID_ARGUMENT': + return 'INVALID_ARGUMENT'; + case 'NOT_FOUND': + return 'NOT_FOUND'; + case 'TIMEOUT': + return 'TIMEOUT'; + case 'ACTION_FAILED': + return 'ACTION_FAILED'; + default: + return 'INTERNAL_ERROR'; + } +} + +async function isExecutable(filePath: string): Promise { + try { + await access(filePath); + return true; + } catch { + return false; + } +} + +function makeError(code: MacosControlError['code'], message: string, details?: Record): MacosControlResult { + return { + ok: false, + error: { + code, + message, + details, + }, + }; +} + +function parseBounds(value: unknown): [number, number, number, number] { + if (Array.isArray(value) && value.length === 4) { + return [ + Number(value[0]) || 0, + Number(value[1]) || 0, + Number(value[2]) || 0, + Number(value[3]) || 0, + ]; + } + return [0, 0, 0, 0]; +} + +function toAxElement(raw: any): AxElement { + return { + id: String(raw?.id ?? ''), + app: String(raw?.app ?? ''), + pid: Number(raw?.pid ?? 0), + role: String(raw?.role ?? ''), + title: raw?.title ? String(raw.title) : undefined, + label: raw?.label ? String(raw.label) : undefined, + desc: raw?.desc ? String(raw.desc) : undefined, + text: raw?.text ? String(raw.text) : undefined, + checked: typeof raw?.checked === 'boolean' ? raw.checked : undefined, + selected: typeof raw?.selected === 'boolean' ? raw.selected : undefined, + focused: typeof raw?.focused === 'boolean' ? raw.focused : undefined, + actions: Array.isArray(raw?.actions) ? raw.actions.map((a: unknown) => String(a)) : undefined, + bounds: parseBounds(raw?.bounds), + }; +} + +export class AxAdapter { + private helperPathCache: string | null = null; + private signatures = new Map(); + + private async resolveHelperPath(): Promise { + if (this.helperPathCache !== null) { + return this.helperPathCache; + } + + const envOverride = process.env.DC_MACOS_AX_HELPER_PATH; + const archName = process.arch === 'arm64' ? 'arm64' : 'x64'; + + const candidates = [ + envOverride, + path.join(PROJECT_ROOT, 'bin', 'macos', `macos-ax-helper-darwin-${archName}`), + path.join(PROJECT_ROOT, 'native', 'macos-ax-helper', '.build', 'apple', 'Products', 'Release', 'macos-ax-helper'), + path.join(PROJECT_ROOT, 'native', 'macos-ax-helper', '.build', 'release', 'macos-ax-helper'), + path.join(PROJECT_ROOT, 'native', 'macos-ax-helper', '.build', `${process.arch}-apple-macosx`, 'release', 'macos-ax-helper'), + ].filter((value): value is string => !!value); + + for (const candidate of candidates) { + if (existsSync(candidate) && await isExecutable(candidate)) { + this.helperPathCache = candidate; + return candidate; + } + } + + this.helperPathCache = null; + return null; + } + + private async runHelper(request: HelperRequest, timeoutMs = 15000): Promise> { + if (process.platform !== 'darwin') { + return makeError('UNSUPPORTED_PLATFORM', 'macOS control tools are only available on macOS.'); + } + + const helperPath = await this.resolveHelperPath(); + if (!helperPath) { + return makeError( + 'HELPER_NOT_FOUND', + 'macOS accessibility helper binary not found. Build it with ./build-macos-helper.sh', + { projectRoot: PROJECT_ROOT }, + ); + } + + const payload = JSON.stringify(request); + + return await new Promise((resolve) => { + const child = spawn(helperPath, [], { + stdio: ['pipe', 'pipe', 'pipe'], + }); + + let stdout = ''; + let stderr = ''; + let timedOut = false; + + const timer = setTimeout(() => { + timedOut = true; + child.kill('SIGKILL'); + }, timeoutMs); + + child.stdout.on('data', (chunk) => { + stdout += chunk.toString(); + }); + + child.stderr.on('data', (chunk) => { + stderr += chunk.toString(); + }); + + child.on('error', (error) => { + clearTimeout(timer); + resolve(makeError('HELPER_EXEC_FAILED', `Failed to execute AX helper: ${error.message}`)); + }); + + child.on('close', (code) => { + clearTimeout(timer); + + if (timedOut) { + resolve(makeError('TIMEOUT', 'AX helper request timed out', { timeoutMs, command: request.command })); + return; + } + + const trimmed = stdout.trim(); + if (!trimmed) { + resolve(makeError('HELPER_PROTOCOL_ERROR', 'AX helper returned empty response', { code, stderr })); + return; + } + + let parsed: HelperResponse; + try { + parsed = JSON.parse(trimmed) as HelperResponse; + } catch { + resolve(makeError('HELPER_PROTOCOL_ERROR', 'AX helper returned invalid JSON', { code, stderr, stdout: trimmed.slice(0, 400) })); + return; + } + + if (!parsed.ok) { + const helperCode = parsed.error?.code ?? 'INTERNAL_ERROR'; + resolve({ + ok: false, + error: { + code: normalizeHelperError(helperCode), + message: parsed.error?.message || 'AX helper request failed', + details: { + helperCode, + helperDetails: parsed.error?.details, + stderr, + }, + }, + }); + return; + } + + resolve({ ok: true, data: parsed.data }); + }); + + child.stdin.write(payload); + child.stdin.end(); + }); + } + + private rememberElements(elements: AxElement[]): void { + for (const element of elements) { + if (!element.id) { + continue; + } + + this.signatures.set(element.id, { + app: element.app, + role: element.role, + title: element.title, + label: element.label, + text: element.text, + bounds: element.bounds, + }); + } + } + + private elementScore(candidate: AxElement, signature: AxElementSignature): number { + let score = 0; + + if (candidate.role === signature.role) { + score += 8; + } + + if (signature.title && candidate.title === signature.title) { + score += 6; + } + + if (signature.label && candidate.label === signature.label) { + score += 4; + } + + if (signature.text && candidate.text === signature.text) { + score += 3; + } + + if (signature.bounds && candidate.bounds) { + const dx = Math.abs(candidate.bounds[0] - signature.bounds[0]); + const dy = Math.abs(candidate.bounds[1] - signature.bounds[1]); + if (dx < 8 && dy < 8) { + score += 5; + } + } + + return score; + } + + private async fallbackRefindElement(staleId: string): Promise { + const signature = this.signatures.get(staleId); + if (!signature) { + return null; + } + + const queryText = signature.title || signature.label || signature.text; + const refreshed = await this.listElements({ + scope: 'app', + app: signature.app, + text: queryText, + role: signature.role, + limit: 50, + depth: 12, + }); + + if (!refreshed.ok || !refreshed.data || refreshed.data.length === 0) { + return null; + } + + const sorted = [...refreshed.data].sort((a, b) => this.elementScore(b, signature) - this.elementScore(a, signature)); + return sorted[0] ?? null; + } + + async status(): Promise> { + const result = await this.runHelper({ command: 'status' }, 8000); + if (!result.ok) { + return result; + } + + return { + ok: true, + data: { + ...(result.data ?? { platform: process.platform, hasPermission: false }), + platform: process.platform, + helperPath: await this.resolveHelperPath() ?? undefined, + }, + }; + } + + async listApps(): Promise> { + const result = await this.runHelper<{ apps?: unknown[] }>({ command: 'list_apps' }); + if (!result.ok) { + return { + ok: false, + error: result.error, + }; + } + + const apps = Array.isArray(result.data?.apps) + ? result.data.apps.map((app: any) => ({ + name: String(app?.name ?? 'Unknown'), + pid: Number(app?.pid ?? 0), + bundleId: app?.bundleId ? String(app.bundleId) : undefined, + active: Boolean(app?.active), + })) + : []; + + return { ok: true, data: apps }; + } + + async listElements(args: { + scope?: 'top_window' | 'app' | 'all'; + app?: string; + text?: string; + role?: string; + depth?: number; + limit?: number; + }): Promise> { + const roleFilters = expandRoleAlias(args.role); + + const result = await this.runHelper<{ elements?: unknown[] }>({ + command: 'list_elements', + args: { + scope: args.scope ?? 'top_window', + app: args.app, + text: args.text, + roles: roleFilters, + depth: args.depth, + limit: args.limit, + }, + }); + + if (!result.ok) { + return { + ok: false, + error: result.error, + }; + } + + const elements = Array.isArray(result.data?.elements) + ? result.data.elements.map(toAxElement).filter((element) => element.id) + : []; + + this.rememberElements(elements); + return { ok: true, data: elements }; + } + + async find(args: { + app: string; + text?: string; + role?: string; + depth?: number; + limit?: number; + index?: number; + }): Promise> { + const index = Math.max(0, args.index ?? 0); + const roleFilters = expandRoleAlias(args.role); + const limit = Math.max(index + 1, args.limit ?? 0) || undefined; + + const result = await this.runHelper<{ elements?: unknown[] }>({ + command: 'find', + args: { + app: args.app, + text: args.text, + roles: roleFilters, + depth: args.depth, + limit, + index, + }, + }); + + if (!result.ok) { + return { + ok: false, + error: result.error, + }; + } + + const elements = Array.isArray(result.data?.elements) + ? result.data.elements.map(toAxElement).filter((element) => element.id) + : []; + + this.rememberElements(elements); + return { ok: true, data: elements }; + } + + async clickById(id: string, appHint?: string): Promise>> { + const clickResult = await this.runHelper>({ + command: 'click', + args: { + id, + app: appHint, + }, + }); + + if (clickResult.ok) { + return clickResult; + } + + if (clickResult.error?.code !== 'NOT_FOUND') { + return clickResult; + } + + const fallbackElement = await this.fallbackRefindElement(id); + if (!fallbackElement) { + return clickResult; + } + + return this.runHelper>({ + command: 'click', + args: { + id: fallbackElement.id, + app: fallbackElement.app, + }, + }); + } + + async typeText(text: string): Promise>> { + return this.runHelper>({ + command: 'type_text', + args: { text }, + }); + } + + async pressKey(key: string, modifiers: string[] = []): Promise>> { + return this.runHelper>({ + command: 'press_key', + args: { key, modifiers }, + }); + } + + async activate(app: string): Promise>> { + return this.runHelper>({ + command: 'activate', + args: { app }, + }); + } + + async waitFor(args: { + app: string; + text: string; + role?: string; + timeout_ms?: number; + depth?: number; + }): Promise> { + const result = await this.runHelper<{ element?: unknown }>({ + command: 'wait_for', + args: { + app: args.app, + text: args.text, + roles: expandRoleAlias(args.role), + timeout_ms: args.timeout_ms, + depth: args.depth, + }, + }, (args.timeout_ms ?? 5000) + 3000); + + if (!result.ok) { + return { + ok: false, + error: result.error, + }; + } + + const element = result.data?.element ? toAxElement(result.data.element) : null; + if (!element) { + return makeError('NOT_FOUND', 'Element not found before timeout'); + } + + this.rememberElements([element]); + return { ok: true, data: element }; + } + + async getState(args: { + app: string; + text?: string; + role?: string; + depth?: number; + limit?: number; + index?: number; + }): Promise>> { + const found = await this.find(args); + if (!found.ok) { + return { + ok: false, + error: found.error, + }; + } + + const element = found.data?.[0]; + if (!element) { + return makeError('NOT_FOUND', `No element matched criteria in ${args.app}`); + } + + return { + ok: true, + data: { + element, + checked: element.checked, + selected: element.selected, + text: element.text, + }, + }; + } + + async scroll(args: { + x: number; + y: number; + direction?: 'up' | 'down'; + amount?: number; + }): Promise>> { + return this.runHelper>({ + command: 'scroll', + args: { + x: args.x, + y: args.y, + direction: args.direction ?? 'down', + amount: args.amount ?? 3, + }, + }); + } + + async batch(commands: AxBatchCommand[], stopOnError: boolean): Promise> { + const waitBudgetMs = commands.reduce((sum, command) => { + if (command.action === 'wait') { + return sum + Math.max(0, command.ms ?? 500); + } + if (command.action === 'wait_for') { + return sum + Math.max(0, command.timeout_ms ?? 5000); + } + return sum; + }, 0); + const timeoutMs = Math.min(Math.max(30000, waitBudgetMs + commands.length * 4000), 5 * 60 * 1000); + + return this.runHelper({ + command: 'batch', + args: { + commands, + stop_on_error: stopOnError, + }, + }, timeoutMs); + } +} + +export const axAdapter = new AxAdapter(); diff --git a/src/tools/macos-control/cdp-adapter.ts b/src/tools/macos-control/cdp-adapter.ts new file mode 100644 index 00000000..76d10d08 --- /dev/null +++ b/src/tools/macos-control/cdp-adapter.ts @@ -0,0 +1,493 @@ +import net from 'net'; +import tls from 'tls'; +import { randomBytes, randomUUID } from 'crypto'; +import { URL } from 'url'; +import { + ElectronDebugAttachResult, + ElectronDebugEvalResult, + ElectronDebugTarget, + MacosControlErrorCode, + MacosControlResult, +} from './types.js'; + +function makeError(code: MacosControlErrorCode, message: string, details?: Record): MacosControlResult { + return { + ok: false, + error: { + code: code as any, + message, + details, + }, + }; +} + +class SimpleWebSocketClient { + private socket: net.Socket | tls.TLSSocket | null = null; + private readBuffer = Buffer.alloc(0); + private onTextMessage: ((message: string) => void) | null = null; + private onCloseHandler: (() => void) | null = null; + + setMessageHandler(handler: (message: string) => void): void { + this.onTextMessage = handler; + } + + setCloseHandler(handler: () => void): void { + this.onCloseHandler = handler; + } + + async connect(wsUrl: string, timeoutMs = 6000): Promise { + const url = new URL(wsUrl); + const isSecure = url.protocol === 'wss:'; + const port = Number(url.port || (isSecure ? 443 : 80)); + const path = `${url.pathname || '/'}${url.search || ''}`; + const key = randomBytes(16).toString('base64'); + + const socket = isSecure + ? tls.connect({ host: url.hostname, port, servername: url.hostname }) + : net.connect({ host: url.hostname, port }); + + this.socket = socket; + + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error(`WebSocket connect timeout after ${timeoutMs}ms`)); + }, timeoutMs); + + const cleanup = () => { + clearTimeout(timeout); + socket.removeListener('error', onError); + socket.removeListener('data', onData); + }; + + const onError = (error: Error) => { + cleanup(); + reject(error); + }; + + let handshakeBuffer = ''; + const onData = (chunk: Buffer) => { + handshakeBuffer += chunk.toString('utf8'); + + const headerEnd = handshakeBuffer.indexOf('\r\n\r\n'); + if (headerEnd === -1) { + return; + } + + const headers = handshakeBuffer.slice(0, headerEnd); + const remaining = Buffer.from(handshakeBuffer.slice(headerEnd + 4), 'utf8'); + + if (!headers.startsWith('HTTP/1.1 101')) { + cleanup(); + reject(new Error(`WebSocket handshake failed: ${headers.split('\r\n')[0]}`)); + return; + } + + cleanup(); + + if (remaining.length > 0) { + this.readBuffer = Buffer.concat([this.readBuffer, remaining]); + this.consumeFrames(); + } + + resolve(); + }; + + socket.once('error', onError); + socket.on('data', onData); + + const request = [ + `GET ${path} HTTP/1.1`, + `Host: ${url.hostname}:${port}`, + 'Upgrade: websocket', + 'Connection: Upgrade', + `Sec-WebSocket-Key: ${key}`, + 'Sec-WebSocket-Version: 13', + '\r\n', + ].join('\r\n'); + + socket.write(request); + }); + + socket.on('data', (chunk) => { + this.readBuffer = Buffer.concat([this.readBuffer, chunk]); + this.consumeFrames(); + }); + + socket.on('close', () => { + this.onCloseHandler?.(); + }); + + socket.on('error', () => { + this.onCloseHandler?.(); + }); + } + + sendText(text: string): void { + if (!this.socket) { + throw new Error('WebSocket is not connected'); + } + + const payload = Buffer.from(text, 'utf8'); + const frame = this.encodeClientFrame(payload, 0x1); + this.socket.write(frame); + } + + close(): void { + if (!this.socket) { + return; + } + + try { + const closeFrame = this.encodeClientFrame(Buffer.alloc(0), 0x8); + this.socket.write(closeFrame); + } catch { + // Ignore close frame errors + } + + this.socket.end(); + this.socket.destroy(); + this.socket = null; + } + + private consumeFrames(): void { + while (this.readBuffer.length >= 2) { + const b0 = this.readBuffer[0]; + const b1 = this.readBuffer[1]; + const opcode = b0 & 0x0f; + const isMasked = (b1 & 0x80) !== 0; + let payloadLength = b1 & 0x7f; + let offset = 2; + + if (payloadLength === 126) { + if (this.readBuffer.length < offset + 2) { + return; + } + payloadLength = this.readBuffer.readUInt16BE(offset); + offset += 2; + } else if (payloadLength === 127) { + if (this.readBuffer.length < offset + 8) { + return; + } + const lenBig = this.readBuffer.readBigUInt64BE(offset); + payloadLength = Number(lenBig); + offset += 8; + } + + let maskKey: Buffer | null = null; + if (isMasked) { + if (this.readBuffer.length < offset + 4) { + return; + } + maskKey = this.readBuffer.subarray(offset, offset + 4); + offset += 4; + } + + if (this.readBuffer.length < offset + payloadLength) { + return; + } + + let payload = this.readBuffer.subarray(offset, offset + payloadLength); + this.readBuffer = this.readBuffer.subarray(offset + payloadLength); + + if (isMasked && maskKey) { + const unmasked = Buffer.allocUnsafe(payload.length); + for (let i = 0; i < payload.length; i++) { + unmasked[i] = payload[i] ^ maskKey[i % 4]; + } + payload = unmasked; + } + + if (opcode === 0x1) { + this.onTextMessage?.(payload.toString('utf8')); + } else if (opcode === 0x8) { + this.close(); + return; + } else if (opcode === 0x9) { + if (this.socket) { + const pong = this.encodeClientFrame(payload, 0xA); + this.socket.write(pong); + } + } + } + } + + private encodeClientFrame(payload: Buffer, opcode: number): Buffer { + const mask = randomBytes(4); + const headerParts: Buffer[] = []; + + const firstByte = 0x80 | (opcode & 0x0f); + headerParts.push(Buffer.from([firstByte])); + + if (payload.length < 126) { + headerParts.push(Buffer.from([0x80 | payload.length])); + } else if (payload.length < 65536) { + const len = Buffer.alloc(3); + len[0] = 0x80 | 126; + len.writeUInt16BE(payload.length, 1); + headerParts.push(len); + } else { + const len = Buffer.alloc(9); + len[0] = 0x80 | 127; + len.writeBigUInt64BE(BigInt(payload.length), 1); + headerParts.push(len); + } + + headerParts.push(mask); + + const maskedPayload = Buffer.allocUnsafe(payload.length); + for (let i = 0; i < payload.length; i++) { + maskedPayload[i] = payload[i] ^ mask[i % 4]; + } + + return Buffer.concat([...headerParts, maskedPayload]); + } +} + +interface CdpPendingRequest { + resolve: (value: any) => void; + reject: (error: Error) => void; + timeout: NodeJS.Timeout; +} + +interface CdpSession { + id: string; + wsClient: SimpleWebSocketClient; + nextMessageId: number; + pending: Map; + host: string; + port: number; + target: ElectronDebugTarget; +} + +export class CdpAdapter { + private sessions = new Map(); + + private async fetchTargets(host: string, port: number): Promise> { + const listUrl = `http://${host}:${port}/json/list`; + + try { + const response = await fetch(listUrl, { method: 'GET' }); + if (!response.ok) { + return makeError('CDP_CONNECT_FAILED', `Failed to query CDP targets: HTTP ${response.status}`); + } + + const data = await response.json(); + if (!Array.isArray(data)) { + return makeError('CDP_CONNECT_FAILED', 'Invalid CDP target list response'); + } + + const targets: ElectronDebugTarget[] = data.map((item: any) => ({ + id: String(item?.id ?? ''), + type: String(item?.type ?? ''), + title: String(item?.title ?? ''), + url: String(item?.url ?? ''), + webSocketDebuggerUrl: item?.webSocketDebuggerUrl ? String(item.webSocketDebuggerUrl) : undefined, + })); + + return { ok: true, data: targets }; + } catch (error) { + return makeError('CDP_CONNECT_FAILED', `Failed to query CDP endpoint: ${error instanceof Error ? error.message : String(error)}`); + } + } + + private bindSessionHandlers(session: CdpSession): void { + session.wsClient.setMessageHandler((message) => { + try { + const parsed = JSON.parse(message); + if (typeof parsed?.id === 'number') { + const pending = session.pending.get(parsed.id); + if (!pending) { + return; + } + + clearTimeout(pending.timeout); + session.pending.delete(parsed.id); + + if (parsed.error) { + pending.reject(new Error(String(parsed.error?.message || 'CDP call failed'))); + return; + } + + pending.resolve(parsed.result ?? null); + } + } catch { + // Ignore non-JSON frames/events + } + }); + + session.wsClient.setCloseHandler(() => { + for (const [id, pending] of session.pending) { + clearTimeout(pending.timeout); + pending.reject(new Error('CDP websocket closed')); + session.pending.delete(id); + } + this.sessions.delete(session.id); + }); + } + + private async sendCommand(session: CdpSession, method: string, params?: Record, timeoutMs = 6000): Promise { + return await new Promise((resolve, reject) => { + const id = ++session.nextMessageId; + const timeout = setTimeout(() => { + session.pending.delete(id); + reject(new Error(`CDP call timeout: ${method}`)); + }, timeoutMs); + + session.pending.set(id, { resolve, reject, timeout }); + + try { + session.wsClient.sendText(JSON.stringify({ id, method, params })); + } catch (error) { + clearTimeout(timeout); + session.pending.delete(id); + reject(error instanceof Error ? error : new Error(String(error))); + } + }); + } + + async attach(args: { + host?: string; + port?: number; + targetIndex?: number; + targetId?: string; + }): Promise> { + const host = args.host || '127.0.0.1'; + const port = args.port || 9222; + + const targetsResult = await this.fetchTargets(host, port); + if (!targetsResult.ok) { + return { + ok: false, + error: targetsResult.error, + }; + } + + const targets = targetsResult.data || []; + if (targets.length === 0) { + return makeError('CDP_CONNECT_FAILED', `No CDP targets found on ${host}:${port}`); + } + + const pageTargets = targets.filter((target) => ['page', 'webview'].includes(target.type)); + const candidateTargets = pageTargets.length > 0 ? pageTargets : targets; + + let target: ElectronDebugTarget | undefined; + if (args.targetId) { + target = candidateTargets.find((item) => item.id === args.targetId); + } else { + target = candidateTargets[args.targetIndex ?? 0]; + } + + if (!target) { + return makeError('CDP_CONNECT_FAILED', 'Requested CDP target not found', { + targetId: args.targetId, + targetIndex: args.targetIndex, + availableTargets: candidateTargets.map((item) => item.id), + }); + } + + if (!target.webSocketDebuggerUrl) { + return makeError('CDP_CONNECT_FAILED', 'Selected CDP target has no websocket debugger URL', { targetId: target.id }); + } + + const wsClient = new SimpleWebSocketClient(); + try { + await wsClient.connect(target.webSocketDebuggerUrl); + } catch (error) { + return makeError('CDP_CONNECT_FAILED', `Failed to connect to CDP websocket: ${error instanceof Error ? error.message : String(error)}`); + } + + const sessionId = randomUUID(); + const session: CdpSession = { + id: sessionId, + wsClient, + nextMessageId: 0, + pending: new Map(), + host, + port, + target, + }; + + this.bindSessionHandlers(session); + this.sessions.set(sessionId, session); + + try { + await this.sendCommand(session, 'Runtime.enable'); + await this.sendCommand(session, 'Page.enable'); + } catch (error) { + wsClient.close(); + this.sessions.delete(sessionId); + return makeError('CDP_CONNECT_FAILED', `Failed to initialize CDP domains: ${error instanceof Error ? error.message : String(error)}`); + } + + return { + ok: true, + data: { + sessionId, + host, + port, + targetId: target.id, + targetTitle: target.title, + targetUrl: target.url, + availableTargets: candidateTargets.map((item) => ({ + id: item.id, + title: item.title, + url: item.url, + type: item.type, + })), + }, + }; + } + + async evaluate(args: { + sessionId: string; + expression: string; + returnByValue?: boolean; + awaitPromise?: boolean; + }): Promise> { + const session = this.sessions.get(args.sessionId); + if (!session) { + return makeError('CDP_NOT_CONNECTED', `Unknown CDP session: ${args.sessionId}`); + } + + try { + const result = await this.sendCommand(session, 'Runtime.evaluate', { + expression: args.expression, + returnByValue: args.returnByValue ?? true, + awaitPromise: args.awaitPromise ?? true, + }, 12000); + + if (result?.exceptionDetails) { + return makeError('CDP_CALL_FAILED', String(result.exceptionDetails?.text || 'Runtime.evaluate failed')); + } + + return { + ok: true, + data: { + result: result?.result?.value ?? result?.result, + type: result?.result?.type, + subtype: result?.result?.subtype, + description: result?.result?.description, + }, + }; + } catch (error) { + return makeError('CDP_CALL_FAILED', `CDP evaluate failed: ${error instanceof Error ? error.message : String(error)}`); + } + } + + async disconnect(sessionId: string): Promise> { + const session = this.sessions.get(sessionId); + if (!session) { + return makeError('CDP_NOT_CONNECTED', `Unknown CDP session: ${sessionId}`); + } + + session.wsClient.close(); + this.sessions.delete(sessionId); + + return { + ok: true, + data: { sessionId }, + }; + } +} + +export const cdpAdapter = new CdpAdapter(); diff --git a/src/tools/macos-control/orchestrator.ts b/src/tools/macos-control/orchestrator.ts new file mode 100644 index 00000000..74666316 --- /dev/null +++ b/src/tools/macos-control/orchestrator.ts @@ -0,0 +1,227 @@ +import { axAdapter } from './ax-adapter.js'; +import { cdpAdapter } from './cdp-adapter.js'; +import { + AxBatchCommand, + AxBatchResult, + AxElement, + MacosControlErrorCode, + MacosControlResult, +} from './types.js'; + +function fail(code: MacosControlErrorCode, message: string): MacosControlResult { + return { + ok: false, + error: { + code: code as any, + message, + }, + }; +} + +function isMacOS(): boolean { + return process.platform === 'darwin'; +} + +export class MacosControlOrchestrator { + async axStatus() { + return axAdapter.status(); + } + + async axListApps() { + return axAdapter.listApps(); + } + + async axListElements(args: { + scope?: 'top_window' | 'app' | 'all'; + app?: string; + text?: string; + role?: string; + depth?: number; + limit?: number; + }) { + return axAdapter.listElements(args); + } + + async axFind(args: { + app: string; + text?: string; + role?: string; + index?: number; + depth?: number; + limit?: number; + }): Promise> { + return axAdapter.find(args); + } + + async axClick(args: { + id?: string; + app?: string; + text?: string; + role?: string; + index?: number; + depth?: number; + limit?: number; + }): Promise>> { + if (args.id) { + return axAdapter.clickById(args.id, args.app); + } + + if (!args.app || !args.text) { + return fail('INVALID_ARGUMENT', 'Provide either id or app+text for macos_ax_click'); + } + + const found = await axAdapter.find({ + app: args.app, + text: args.text, + role: args.role, + depth: args.depth, + limit: args.limit, + index: args.index, + }); + + if (!found.ok) { + return { + ok: false, + error: found.error, + }; + } + + const target = found.data?.[0]; + if (!target) { + return fail('NOT_FOUND', `No element matched text "${args.text}" in ${args.app}`); + } + + return axAdapter.clickById(target.id, args.app); + } + + async axGetState(args: { + app: string; + text?: string; + role?: string; + index?: number; + depth?: number; + limit?: number; + }): Promise>> { + return axAdapter.getState(args); + } + + async axFindAndClick(args: { + app: string; + text?: string; + role?: string; + index?: number; + depth?: number; + limit?: number; + timeout_ms?: number; + if_exists?: boolean; + }): Promise>> { + const timeoutMs = args.timeout_ms ?? 0; + + if (timeoutMs > 0 && args.text) { + const waited = await this.axWaitFor({ + app: args.app, + text: args.text, + role: args.role, + timeout_ms: timeoutMs, + depth: args.depth, + }); + + if (!waited.ok) { + if (args.if_exists && waited.error?.code === 'TIMEOUT') { + return { ok: true, data: { skipped: true } }; + } + return { ok: false, error: waited.error }; + } + } + + const found = await this.axFind({ + app: args.app, + text: args.text, + role: args.role, + index: args.index, + depth: args.depth, + limit: args.limit, + }); + + if (!found.ok) { + if (args.if_exists && found.error?.code === 'NOT_FOUND') { + return { ok: true, data: { skipped: true } }; + } + return { ok: false, error: found.error }; + } + + const element = found.data?.[0]; + if (!element) { + if (args.if_exists) { + return { ok: true, data: { skipped: true } }; + } + return fail('NOT_FOUND', `No element matched criteria in ${args.app}`); + } + + const clickResult = await axAdapter.clickById(element.id, args.app); + if (!clickResult.ok) { + return { ok: false, error: clickResult.error }; + } + + return { + ok: true, + data: { + element, + click_result: clickResult.data, + }, + }; + } + + async axType(text: string) { + return axAdapter.typeText(text); + } + + async axKey(key: string, modifiers: string[] = []) { + return axAdapter.pressKey(key, modifiers); + } + + async axActivate(app: string) { + return axAdapter.activate(app); + } + + async axWaitFor(args: { + app: string; + text: string; + role?: string; + timeout_ms?: number; + depth?: number; + }) { + return axAdapter.waitFor(args); + } + + async axBatch(commands: AxBatchCommand[], stopOnError: boolean = true): Promise> { + if (!isMacOS()) { + return fail('UNSUPPORTED_PLATFORM', 'macOS control tools are only available on macOS.'); + } + return axAdapter.batch(commands, stopOnError); + } + + async electronDebugAttach(args: { + host?: string; + port?: number; + targetIndex?: number; + targetId?: string; + }) { + return cdpAdapter.attach(args); + } + + async electronDebugEval(args: { + sessionId: string; + expression: string; + returnByValue?: boolean; + awaitPromise?: boolean; + }) { + return cdpAdapter.evaluate(args); + } + + async electronDebugDisconnect(args: { sessionId: string }) { + return cdpAdapter.disconnect(args.sessionId); + } +} + +export const macosControlOrchestrator = new MacosControlOrchestrator(); diff --git a/src/tools/macos-control/role-aliases.ts b/src/tools/macos-control/role-aliases.ts new file mode 100644 index 00000000..2c8092a2 --- /dev/null +++ b/src/tools/macos-control/role-aliases.ts @@ -0,0 +1,39 @@ +export const ROLE_ALIASES: Record = { + toggle: ['AXSwitch', 'AXCheckBox', 'AXToggle'], + switch: ['AXSwitch', 'AXCheckBox', 'AXToggle'], + button: ['AXButton', 'AXToolbarButton', 'AXMenuButton'], + text: ['AXTextField', 'AXTextArea', 'AXSearchField'], + input: ['AXTextField', 'AXTextArea', 'AXSearchField', 'AXSecureTextField'], + menu: ['AXMenuItem', 'AXMenuBarItem'], + link: ['AXLink', 'AXButton'], + row: ['AXRow', 'AXCell'], + list: ['AXList', 'AXOutline', 'AXTable'], +}; + +/** + * Expands aliases like "toggle" or "axswitch" into concrete AX roles. + */ +export function expandRoleAlias(role?: string | null): string[] | undefined { + if (!role) { + return undefined; + } + + const trimmed = role.trim(); + if (!trimmed) { + return undefined; + } + + const lower = trimmed.toLowerCase(); + if (ROLE_ALIASES[lower]) { + return ROLE_ALIASES[lower]; + } + + if (lower.startsWith('ax')) { + const withoutPrefix = lower.slice(2); + if (ROLE_ALIASES[withoutPrefix]) { + return ROLE_ALIASES[withoutPrefix]; + } + } + + return [trimmed]; +} diff --git a/src/tools/macos-control/types.ts b/src/tools/macos-control/types.ts new file mode 100644 index 00000000..ac5b2675 --- /dev/null +++ b/src/tools/macos-control/types.ts @@ -0,0 +1,152 @@ +export type MacosControlErrorCode = + | 'UNSUPPORTED_PLATFORM' + | 'HELPER_NOT_FOUND' + | 'HELPER_EXEC_FAILED' + | 'HELPER_PROTOCOL_ERROR' + | 'PERMISSION_DENIED' + | 'INVALID_ARGUMENT' + | 'NOT_FOUND' + | 'TIMEOUT' + | 'ACTION_FAILED' + | 'CDP_CONNECT_FAILED' + | 'CDP_NOT_CONNECTED' + | 'CDP_CALL_FAILED' + | 'INTERNAL_ERROR'; + +export interface MacosControlError { + code: MacosControlErrorCode; + message: string; + details?: Record; + retriable?: boolean; +} + +export interface MacosControlResult { + ok: boolean; + data?: T; + error?: MacosControlError; +} + +export interface AxElement { + id: string; + app: string; + pid: number; + role: string; + title?: string; + label?: string; + desc?: string; + text?: string; + checked?: boolean; + selected?: boolean; + focused?: boolean; + actions?: string[]; + bounds: [number, number, number, number]; +} + +export interface AxAppInfo { + name: string; + pid: number; + bundleId?: string; + active: boolean; +} + +export interface AxStatus { + platform: NodeJS.Platform; + hasPermission: boolean; + helperPath?: string; + helperVersion?: string; + processInfo?: string; +} + +export interface AxBatchCommand { + action: 'activate' | 'find' | 'click' | 'find_and_click' | 'type' | 'key' | 'wait' | 'wait_for' | 'get_state' | 'scroll'; + app?: string; + text?: string; + role?: string; + id?: string; + timeout_ms?: number; + depth?: number; + limit?: number; + key?: string; + modifiers?: string[]; + index?: number; + if_exists?: boolean; + ms?: number; + x?: number; + y?: number; + direction?: 'up' | 'down'; + amount?: number; +} + +export interface AxBatchResultItem { + action: string; + success: boolean; + skipped?: boolean; + element?: AxElement; + error?: string; + [key: string]: unknown; +} + +export interface AxBatchResult { + success: boolean; + results: AxBatchResultItem[]; + failedAt: number | null; + completed: number; + total: number; +} + +export interface ElectronDebugTarget { + id: string; + type: string; + title: string; + url: string; + webSocketDebuggerUrl?: string; +} + +export interface ElectronDebugAttachResult { + sessionId: string; + host: string; + port: number; + targetId: string; + targetTitle: string; + targetUrl: string; + availableTargets: Array<{ id: string; title: string; url: string; type: string }>; +} + +export interface ElectronDebugEvalResult { + result?: unknown; + type?: string; + description?: string; + subtype?: string; +} + +export interface AxElementSignature { + app: string; + role: string; + title?: string; + label?: string; + text?: string; + bounds?: [number, number, number, number]; +} + +export interface HelperRequest { + command: string; + args?: Record; + requestId?: string; +} + +export interface HelperError { + code: string; + message: string; + details?: Record; +} + +export interface HelperResponse { + ok: boolean; + data?: T; + error?: HelperError; + meta?: { + requestId?: string; + durationMs?: number; + [key: string]: unknown; + }; +} diff --git a/src/tools/schemas.ts b/src/tools/schemas.ts index a2cdb39e..51677367 100644 --- a/src/tools/schemas.ts +++ b/src/tools/schemas.ts @@ -140,7 +140,7 @@ export const EditBlockArgsSchema = z.object({ data => { // Helper to check if value is actually provided (not undefined, not empty string) const hasValue = (v: unknown) => v !== undefined && v !== ''; - return (hasValue(data.old_string) && data.new_string !== undefined) || + return (hasValue(data.old_string) && hasValue(data.new_string)) || (hasValue(data.range) && hasValue(data.content)); }, { message: "Must provide either (old_string + new_string) or (range + content)" } @@ -215,3 +215,136 @@ export const TrackUiEventArgsSchema = z.object({ component: z.string().optional().default('file_preview'), params: z.record(z.union([z.string(), z.number(), z.boolean(), z.null()])).optional().default({}), }); + +// macOS control schemas +export const MacosAxStatusArgsSchema = z.object({}); + +export const MacosAxListAppsArgsSchema = z.object({}); + +export const MacosAxListElementsArgsSchema = z.object({ + scope: z.enum(['top_window', 'app', 'all']).optional(), + app: z.string().optional(), + text: z.string().optional(), + role: z.string().optional(), + depth: z.number().optional(), + limit: z.number().optional(), +}).refine( + data => data.scope !== 'app' || !!data.app, + { message: 'Provide app when scope=app' } +); + +export const MacosAxFindArgsSchema = z.object({ + app: z.string(), + text: z.string().optional(), + role: z.string().optional(), + index: z.number().optional(), + depth: z.number().optional(), + limit: z.number().optional(), +}).refine( + data => !!data.text || !!data.role, + { message: 'Provide text or role' } +); + +export const MacosAxGetStateArgsSchema = z.object({ + app: z.string(), + text: z.string().optional(), + role: z.string().optional(), + index: z.number().optional(), + depth: z.number().optional(), + limit: z.number().optional(), +}).refine( + data => !!data.text || !!data.role, + { message: 'Provide text or role' } +); + +export const MacosAxFindAndClickArgsSchema = z.object({ + app: z.string(), + text: z.string().optional(), + role: z.string().optional(), + index: z.number().optional(), + depth: z.number().optional(), + limit: z.number().optional(), + timeout_ms: z.number().optional(), + if_exists: z.boolean().optional(), +}).refine( + data => !!data.text || !!data.role, + { message: 'Provide text or role' } +); + +export const MacosAxClickArgsSchema = z.object({ + id: z.string().optional(), + app: z.string().optional(), + text: z.string().optional(), + role: z.string().optional(), + index: z.number().optional(), + depth: z.number().optional(), + limit: z.number().optional(), +}).refine( + data => !!data.id || (!!data.app && !!data.text), + { message: 'Provide either id, or app+text' } +); + +export const MacosAxTypeArgsSchema = z.object({ + text: z.string(), +}); + +export const MacosAxKeyArgsSchema = z.object({ + key: z.string(), + modifiers: z.array(z.string()).optional().default([]), +}); + +export const MacosAxActivateArgsSchema = z.object({ + app: z.string(), +}); + +export const MacosAxWaitForArgsSchema = z.object({ + app: z.string(), + text: z.string(), + role: z.string().optional(), + timeout_ms: z.number().optional(), + depth: z.number().optional(), +}); + +export const MacosAxBatchCommandSchema = z.object({ + action: z.enum(['activate', 'find', 'click', 'find_and_click', 'type', 'key', 'wait', 'wait_for', 'get_state', 'scroll']), + app: z.string().optional(), + text: z.string().optional(), + role: z.string().optional(), + id: z.string().optional(), + timeout_ms: z.number().optional(), + depth: z.number().optional(), + limit: z.number().optional(), + key: z.string().optional(), + modifiers: z.array(z.string()).optional(), + index: z.number().optional(), + if_exists: z.boolean().optional(), + ms: z.number().optional(), + x: z.number().optional(), + y: z.number().optional(), + direction: z.enum(['up', 'down']).optional(), + amount: z.number().int().optional(), +}); + +export const MacosAxBatchArgsSchema = z.object({ + commands: z.array(MacosAxBatchCommandSchema), + stopOnError: z.boolean().optional().default(true), +}); + +// Electron debug schemas +export const ElectronDebugAttachArgsSchema = z.object({ + host: z.string().optional(), + port: z.number().optional(), + targetIndex: z.number().optional(), + targetId: z.string().optional(), +}); + +export const ElectronDebugEvalArgsSchema = z.object({ + sessionId: z.string(), + expression: z.string(), + returnByValue: z.boolean().optional(), + awaitPromise: z.boolean().optional(), +}); + +export const ElectronDebugDisconnectArgsSchema = z.object({ + sessionId: z.string(), +}); diff --git a/src/utils/capture.ts b/src/utils/capture.ts index 8f261e11..89f1731f 100644 --- a/src/utils/capture.ts +++ b/src/utils/capture.ts @@ -14,10 +14,6 @@ try { // Will be initialized when needed let uniqueUserId = 'unknown'; -// --- Telemetry Proxy (direct BigQuery ingestion) --- -const TELEMETRY_PROXY_URL = 'https://dc-telemetry-proxy-83847352264.europe-west1.run.app/mp/collect'; -const TELEMETRY_PROXY_TOKEN = 'Od44UB_fTrVfGPGRPLr5QdVgFhuKdiGaBmvazTdxVdQ'; - /** * Sanitizes error objects to remove potentially sensitive information like file paths @@ -256,189 +252,21 @@ export const captureBase = async (captureURL: string, event: string, properties? } }; -/** - * Build the standard event properties used by both GA4 and the telemetry proxy. - * Extracted from captureBase so both paths get identical data. - */ -const buildEventProperties = async (properties?: any) => { - if (uniqueUserId === 'unknown') { - uniqueUserId = await configManager.getOrCreateClientId(); - } - - let clientContext: any = {}; - if (currentClient) { - clientContext = { - client_name: currentClient.name, - client_version: currentClient.version, - }; - } - - const sawOnboardingPage = await configManager.getValue('sawOnboardingPage'); - if (sawOnboardingPage !== undefined) { - clientContext.saw_onboarding_page = sawOnboardingPage; - } - - let sanitizedProperties: any; - try { - sanitizedProperties = properties ? JSON.parse(JSON.stringify(properties)) : {}; - } catch { - sanitizedProperties = {}; - } - - if (sanitizedProperties.error) { - if (typeof sanitizedProperties.error === 'object' && sanitizedProperties.error !== null) { - const sanitized = sanitizeError(sanitizedProperties.error); - sanitizedProperties.error = sanitized.message; - if (sanitized.code) sanitizedProperties.errorCode = sanitized.code; - } else if (typeof sanitizedProperties.error === 'string') { - sanitizedProperties.error = sanitizeError(sanitizedProperties.error).message; - } - } - - const sensitiveKeys = ['path', 'filePath', 'directory', 'file_path', 'sourcePath', 'destinationPath', 'fullPath', 'rootPath']; - for (const key of Object.keys(sanitizedProperties)) { - const lowerKey = key.toLowerCase(); - if (sensitiveKeys.some(sk => lowerKey.includes(sk)) && lowerKey !== 'fileextension') { - delete sanitizedProperties[key]; - } - } - - let isDXT = 'false'; - if (process.env.MCP_DXT) isDXT = 'true'; - - const { getSystemInfo } = await import('./system-info.js'); - const systemInfo = getSystemInfo(); - const isContainer = systemInfo.docker.isContainer ? 'true' : 'false'; - const containerType = systemInfo.docker.containerType || 'none'; - const orchestrator = systemInfo.docker.orchestrator || 'none'; - - let containerName = 'none'; - let containerImage = 'none'; - if (systemInfo.docker.isContainer && systemInfo.docker.containerEnvironment) { - const env = systemInfo.docker.containerEnvironment; - if (env.containerName) { - containerName = env.containerName - .replace(/[0-9a-f]{8,}/gi, 'ID') - .replace(/[0-9]{8,}/g, 'ID') - .substring(0, 50); - } - if (env.dockerImage) { - containerImage = env.dockerImage - .replace(/^[^/]+\/[^/]+\//, '') - .replace(/^[^/]+\//, '') - .replace(/@sha256:.*$/, '') - .substring(0, 100); - } - } - - let runtimeSource = 'unknown'; - try { - const processArgs = process.argv.join(' '); - if (processArgs.includes('@smithery/cli') || processArgs.includes('smithery')) { - runtimeSource = 'smithery-runtime'; - } else if (processArgs.includes('npx')) { - runtimeSource = 'npx-runtime'; - } else { - runtimeSource = 'direct-runtime'; - } - } catch { } - - return { - timestamp: new Date().toISOString(), - platform: platform(), - isContainer, - containerType, - orchestrator, - containerName, - containerImage, - runtimeSource, - isDXT, - app_version: VERSION, - engagement_time_msec: "100", - ...clientContext, - ...sanitizedProperties, - }; -}; - -/** - * Send event to the telemetry proxy (direct BigQuery ingestion). - * Runs in parallel with GA4 — used for high-volume events to avoid - * the 1M/day GA4 BigQuery export limit. - */ -const sendToTelemetryProxy = async (event: string, eventProperties: any) => { - try { - const telemetryEnabled = await configManager.getValue('telemetryEnabled'); - if (telemetryEnabled === false) return; - - const payload = JSON.stringify({ - client_id: uniqueUserId, - timestamp_micros: Date.now() * 1000, - events: [{ - name: event, - params: eventProperties - }] - }); - - const url = new URL(TELEMETRY_PROXY_URL); - const options = { - hostname: url.hostname, - port: 443, - path: url.pathname, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${TELEMETRY_PROXY_TOKEN}`, - 'Content-Length': Buffer.byteLength(payload) - } - }; - - const req = https.request(options, (res) => { - res.resume(); // drain response - }); - req.on('error', () => { }); // silent fail - req.setTimeout(3000, () => req.destroy()); - req.write(payload); - req.end(); - } catch { - // Silent fail — telemetry should never break functionality - } -}; - export const capture_call_tool = async (event: string, properties?: any) => { - // Old property (G-8L163XZ1CE) — keeps lower-volume tool events - const GA_OLD_ID = 'G-8L163XZ1CE'; - const GA_OLD_SECRET = 'hNxh4TK2TnSy4oLZn4RwTA'; - const GA_OLD_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_OLD_ID}&api_secret=${GA_OLD_SECRET}`; - - // New property (dc_high_volume) — receives highest-volume tool events to avoid 1M/day BQ export limit - const GA_NEW_ID = 'G-ZDF1M5403Z'; - const GA_NEW_SECRET = 'cUEilpa0SpWfc2UjblDtKQ'; - const GA_NEW_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_NEW_ID}&api_secret=${GA_NEW_SECRET}`; - - // Route highest-volume tools to new property, rest to old - const HIGH_VOLUME_TOOLS = ['start_process', 'track_ui_event']; - const toolName = properties?.name; - const gaUrl = HIGH_VOLUME_TOOLS.includes(toolName) ? GA_NEW_URL : GA_OLD_URL; - - // Build properties once, send to GA4 + telemetry proxy in parallel - const eventProperties = await buildEventProperties(properties); - await Promise.all([ - captureBase(gaUrl, event, properties), // GA4 (routed by tool name) - sendToTelemetryProxy(event, eventProperties), // direct BigQuery (all events) - ]); + const GA_MEASUREMENT_ID = 'G-8L163XZ1CE'; // Replace with your GA4 Measurement ID + const GA_API_SECRET = 'hNxh4TK2TnSy4oLZn4RwTA'; // Replace with your GA4 API Secret + const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`; + const GA_DEBUG_BASE_URL = `https://www.google-analytics.com/debug/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`; + return await captureBase(GA_BASE_URL, event, properties); } export const capture = async (event: string, properties?: any) => { - const GA_MEASUREMENT_ID = 'G-F3GK01G39Y'; - const GA_API_SECRET = 'SqdcIAweSQS1RQErURMdEA'; + const GA_MEASUREMENT_ID = 'G-F3GK01G39Y'; // Replace with your GA4 Measurement ID + const GA_API_SECRET = 'SqdcIAweSQS1RQErURMdEA'; // Replace with your GA4 API Secret const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`; + const GA_DEBUG_BASE_URL = `https://www.google-analytics.com/debug/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`; - // Build properties once, send to both GA4 and telemetry proxy in parallel - const eventProperties = await buildEventProperties(properties); - await Promise.all([ - captureBase(GA_BASE_URL, event, properties), // existing GA4 - sendToTelemetryProxy(event, eventProperties), // new: direct BigQuery - ]); + return await captureBase(GA_BASE_URL, event, properties); } export const capture_ui_event = async (event: string, properties?: any) => { diff --git a/src/utils/files/base.ts b/src/utils/files/base.ts index 852e0b21..775acdb6 100644 --- a/src/utils/files/base.ts +++ b/src/utils/files/base.ts @@ -130,9 +130,6 @@ export interface FileMetadata { totalPages?: number; pages?: PdfPageItem[]; - /** For DOCX files */ - isDocx?: boolean; - /** Error information if operation failed */ error?: boolean; errorMessage?: string; @@ -215,7 +212,7 @@ export interface FileInfo { permissions: string; /** File type classification */ - fileType: 'text' | 'excel' | 'image' | 'binary' | 'docx'; + fileType: 'text' | 'excel' | 'image' | 'binary'; /** Type-specific metadata */ metadata?: FileMetadata; diff --git a/src/utils/files/docx.ts b/src/utils/files/docx.ts deleted file mode 100644 index 407d59f3..00000000 --- a/src/utils/files/docx.ts +++ /dev/null @@ -1,765 +0,0 @@ -/** - * DOCX File Handler - * - * Approach: expose DOCX as filtered/raw XML through existing read_file + edit_block. - * - * READ (default): Returns a text-bearing outline — skips shapes, drawings, SVG noise. - * Shows paragraphs with text, tables with cell content, style info, and image refs. - * Each element shows its raw XML tag context so Claude can target it for editing. - * - * READ (with offset/length): Returns raw pretty-printed XML with line pagination, - * so Claude can drill into specific sections when the outline isn't enough. - * - * EDIT (old_string/new_string): Find/replace on the pretty-printed XML, then - * compact and repack into valid DOCX. Works exactly like text file editing. - * - * Round-trip: DOCX → unzip → pretty-print → [outline or raw] → edit → compact → repack - */ - -import fs from 'fs/promises'; -import PizZip from 'pizzip'; -import { FileHandler, FileResult, FileInfo, ReadOptions, EditResult } from './base.js'; - -// ════════════════════════════════════════════════════════════════ -// XML Pretty-Print / Compact -// ════════════════════════════════════════════════════════════════ - -/** - * Pretty-print XML: split tags onto separate lines with indentation. - * Preserves text node content exactly. compact→pretty→compact is lossless. - */ -function prettyPrintXml(xml: string): string { - const parts = xml.split(/(?<=>)(?=<)/); - const lines: string[] = []; - let depth = 0; - - for (const part of parts) { - const trimmed = part.trim(); - if (!trimmed) continue; - - const isClosing = trimmed.startsWith(''); - const isProcessingInstruction = trimmed.startsWith(' text nodes. - */ -function compactXml(prettyXml: string): string { - return prettyXml.split('\n').map(l => l.trimStart()).join(''); -} - -// ════════════════════════════════════════════════════════════════ -// DOCX ZIP helpers -// ════════════════════════════════════════════════════════════════ - -interface DocxZipContents { - zip: PizZip; - documentXml: string; - /** All XML parts keyed by path (e.g. "word/header1.xml") */ - xmlParts: Map; -} - -function loadDocxZip(buf: Buffer): DocxZipContents { - const zip = new PizZip(buf); - const docFile = zip.file('word/document.xml'); - if (!docFile) throw new Error('Invalid DOCX: missing word/document.xml'); - - const xmlParts = new Map(); - // Collect all XML parts for potential editing - const zipFiles = zip.files; - for (const relativePath of Object.keys(zipFiles)) { - if (relativePath.endsWith('.xml') || relativePath.endsWith('.rels')) { - try { - xmlParts.set(relativePath, zipFiles[relativePath].asText()); - } catch { /* skip binary entries */ } - } - } - - return { - zip, - documentXml: docFile.asText(), - xmlParts, - }; -} - -// ════════════════════════════════════════════════════════════════ -// Outline extraction — the key feature -// ════════════════════════════════════════════════════════════════ - -interface OutlineItem { - index: number; - tag: string; - style?: string; - text?: string; - textLength?: number; - tableInfo?: string; - imageInfo?: string; - skippedReason?: string; - /** Raw XML of this element (for small items) or truncated preview */ - xml?: string; -} - -/** - * Extract a text-bearing outline from document.xml. - * - * Walks direct children of and for each: - * - w:p (paragraph): extracts text from elements, shows style - * - w:tbl (table): extracts cell text for each row - * - mc:AlternateContent / shapes / drawings: shows size, skips content - * - w:sdt: looks inside for text/tables - * - * Returns a human-readable outline with enough context for editing. - */ -function extractOutline(xml: string): string { - const lines: string[] = []; - - // Parse body children using regex — faster and more reliable than DOM for outline - // Find ... - const bodyMatch = xml.match(/]*>([\s\S]*)<\/w:body>/); - if (!bodyMatch) return '[No w:body found in document.xml]'; - - const bodyContent = bodyMatch[1]; - - // Split into top-level children of w:body - // We need to find top-level elements, respecting nesting - const children = splitTopLevelElements(bodyContent); - - let paragraphCount = 0; - let tableCount = 0; - let imageCount = 0; - - for (let i = 0; i < children.length; i++) { - const child = children[i]; - const tagMatch = child.match(/^<(\S+?)[\s>\/]/); - if (!tagMatch) continue; - const tag = tagMatch[1]; - - if (tag === 'w:p') { - const text = extractAllText(child); - const textFragments = extractTextFragments(child); - const style = extractParagraphStyle(child); - const hasDrawing = child.includes(' 0) { - const joined = textFragments.join(''); - if (joined.length > 500) { - line += `\n ${textFragments.slice(0, 8).join('')}...`; - } else { - line += `\n ${joined}`; - } - } else if (!hasDrawing) { - line += ' (empty)'; - } - - lines.push(line); - paragraphCount++; - } else if (tag === 'w:tbl') { - const rows = extractTableRows(child); - let line = `[${i}] w:tbl (${rows.length} rows)`; - const style = extractTableStyle(child); - if (style) line += ` style="${style}"`; - // Show all rows (tables usually contain the real content) - for (let r = 0; r < rows.length; r++) { - const cells = rows[r].map(c => { - if (!c || c.trim() === '') return ''; - return c.length > 60 ? c.substring(0, 60) + '…' : c; - }); - line += `\n row${r}: [${cells.join(' | ')}]`; - } - lines.push(line); - tableCount++; - } else if (tag === 'w:sdt') { - // Structured document tag — look inside for content - const sdtFragments = extractTextFragments(child); - const innerTables = (child.match(/]/g) || []).length; - let line = `[${i}] w:sdt`; - if (innerTables > 0) { - line += ` (contains ${innerTables} table${innerTables > 1 ? 's' : ''})`; - tableCount += innerTables; - } - if (sdtFragments.length > 0) { - const joined = sdtFragments.join(''); - if (joined.length > 150) { - line += `\n ${sdtFragments.slice(0, 3).join('')}...`; - } else { - line += `\n ${joined}`; - } - } - lines.push(line); - } else if (tag === 'w:sectPr') { - lines.push(`[${i}] w:sectPr (section properties)`); - } else if (tag === 'mc:AlternateContent') { - lines.push(`[${i}] mc:AlternateContent [drawing, ${(child.length / 1024).toFixed(1)}KB — skipped]`); - imageCount++; - } else { - lines.push(`[${i}] ${tag} (${(child.length / 1024).toFixed(1)}KB)`); - } - } - - // Summary header - const header = `DOCX Outline: ${children.length} body children, ${paragraphCount} paragraphs, ${tableCount} tables, ${imageCount} images\n` + - `Edit with: edit_block(file, old_string="old text", new_string="new text")\n` + - `Raw XML: use read_file with offset=1 to see pretty-printed XML for advanced edits.\n` + - '─'.repeat(70); - - return header + '\n' + lines.join('\n'); -} - -// ════════════════════════════════════════════════════════════════ -// XML text extraction helpers (regex-based, no DOM needed) -// ════════════════════════════════════════════════════════════════ - -/** Extract all ... text content from an XML fragment */ -function extractAllText(xml: string): string { - const texts: string[] = []; - // Match or but NOT , , , etc. - const re = /]*)?>([^<]*)<\/w:t>/g; - let m; - while ((m = re.exec(xml)) !== null) { - if (m[1]) texts.push(m[1]); - } - return texts.join('').trim(); -} - -/** Extract ... elements as XML fragments for use in edit_block */ -function extractTextFragments(xml: string): string[] { - const fragments: string[] = []; - const re = /]*)?>([^<]*)<\/w:t>/g; - let m; - while ((m = re.exec(xml)) !== null) { - if (m[1] && m[1].trim()) fragments.push(m[0]); - } - return fragments; -} - -/** Extract paragraph style id from w:pPr/w:pStyle */ -function extractParagraphStyle(xml: string): string | null { - const m = xml.match(/... using nesting-aware extraction - const rowElements = extractNestedElements(tableXml, 'w:tr'); - for (const rowXml of rowElements) { - const cells: string[] = []; - const cellElements = extractNestedElements(rowXml, 'w:tc'); - for (const cellXml of cellElements) { - cells.push(extractAllText(cellXml)); - } - if (cells.length > 0) rows.push(cells); - } - return rows; -} - -/** - * Extract all occurrences of a named element from XML, respecting nesting. - * Returns array of full element strings including open/close tags. - */ -function extractNestedElements(xml: string, tagName: string): string[] { - const results: string[] = []; - const openTag = `<${tagName}`; - const closeTag = ``; - let searchFrom = 0; - - while (searchFrom < xml.length) { - const openPos = xml.indexOf(openTag, searchFrom); - if (openPos === -1) break; - - // Check it's actually a tag start (followed by space, >, or /) - const charAfter = xml[openPos + openTag.length]; - if (charAfter !== ' ' && charAfter !== '>' && charAfter !== '/') { - searchFrom = openPos + 1; - continue; - } - - // Check for self-closing - const nextClose = xml.indexOf('>', openPos); - if (nextClose !== -1 && xml[nextClose - 1] === '/') { - results.push(xml.substring(openPos, nextClose + 1)); - searchFrom = nextClose + 1; - continue; - } - - // Find matching close tag respecting nesting - let depth = 1; - let pos = nextClose + 1; - while (depth > 0 && pos < xml.length) { - const nextOpen = xml.indexOf(openTag, pos); - const nextCloseTag = xml.indexOf(closeTag, pos); - - if (nextCloseTag === -1) break; // malformed XML - - if (nextOpen !== -1 && nextOpen < nextCloseTag) { - // Check it's actually an open tag - const ca = xml[nextOpen + openTag.length]; - if (ca === ' ' || ca === '>' || ca === '/') { - depth++; - } - pos = nextOpen + openTag.length; - } else { - depth--; - if (depth === 0) { - results.push(xml.substring(openPos, nextCloseTag + closeTag.length)); - } - pos = nextCloseTag + closeTag.length; - } - } - searchFrom = pos; - } - return results; -} - -/** - * Split XML into top-level elements respecting nesting depth. - * E.g. for body content, returns each direct child element as a string. - */ -function splitTopLevelElements(xml: string): string[] { - const elements: string[] = []; - let depth = 0; - let currentStart = -1; - - // Simple state machine: track < > and nesting - let i = 0; - while (i < xml.length) { - if (xml[i] === '<') { - // Check what kind of tag - if (xml[i + 1] === '/') { - // Closing tag - depth--; - if (depth === 0) { - // Find end of this closing tag - const closeEnd = xml.indexOf('>', i); - if (closeEnd === -1) break; // malformed XML — bail out - if (currentStart !== -1) { - elements.push(xml.substring(currentStart, closeEnd + 1).trim()); - currentStart = -1; - } - i = closeEnd + 1; - continue; - } - } else if (xml[i + 1] === '?' || xml[i + 1] === '!') { - // Processing instruction or comment — skip - const end = xml.indexOf('>', i); - if (end === -1) break; // malformed XML — bail out - i = end + 1; - continue; - } else { - // Opening tag - if (depth === 0) currentStart = i; - // Check for self-closing - const tagEnd = xml.indexOf('>', i); - if (tagEnd === -1) break; // malformed XML — bail out - if (xml[tagEnd - 1] === '/') { - // Self-closing - if (depth === 0) { - elements.push(xml.substring(currentStart, tagEnd + 1).trim()); - currentStart = -1; - i = tagEnd + 1; - continue; - } - } else { - depth++; - } - i = tagEnd + 1; - continue; - } - } - i++; - } - - return elements.filter(e => e.length > 0); -} - -/** - * Extract outline info for headers and footers from the DOCX zip. - */ -function extractHeaderFooterOutline(zip: PizZip): string { - const parts: string[] = []; - const zipFiles = zip.files; - - for (const relativePath of Object.keys(zipFiles)) { - if ((relativePath.startsWith('word/header') || relativePath.startsWith('word/footer')) - && relativePath.endsWith('.xml')) { - try { - const xml = zipFiles[relativePath].asText(); - const text = extractAllText(xml); - const name = relativePath.replace('word/', ''); - if (text) { - parts.push(`${name}: "${text.length > 100 ? text.substring(0, 100) + '...' : text}"`); - } else { - parts.push(`${name}: (no text content)`); - } - } catch { /* skip */ } - } - } - - return parts.length > 0 ? '\n\nHeaders/Footers:\n' + parts.join('\n') : ''; -} - -// ════════════════════════════════════════════════════════════════ -// DOCX creation helpers -// ════════════════════════════════════════════════════════════════ - -function escapeXml(text: string): string { - return text.replace(/&/g, '&').replace(//g, '>') - .replace(/"/g, '"').replace(/'/g, '''); -} - -function createMinimalDocxZip(documentXml: string): PizZip { - const zip = new PizZip(); - - zip.file('[Content_Types].xml', - `` + - `` + - `` + - `` + - `` + - `` + - ``); - - zip.file('_rels/.rels', - `` + - `` + - `` + - ``); - - zip.file('word/_rels/document.xml.rels', - `` + - `` + - `` + - ``); - - zip.file('word/styles.xml', - `` + - `` + - `` + - `` + - `` + - `` + - `` + - `` + - `` + - `` + - ``); - - zip.file('word/document.xml', documentXml); - - return zip; -} - -// ════════════════════════════════════════════════════════════════ -// Count occurrences helper -// ════════════════════════════════════════════════════════════════ - -function countOccurrences(haystack: string, needle: string): number { - let count = 0; - let pos = haystack.indexOf(needle); - while (pos !== -1) { - count++; - pos = haystack.indexOf(needle, pos + 1); - } - return count; -} - -// ════════════════════════════════════════════════════════════════ -// DocxFileHandler — implements FileHandler -// ════════════════════════════════════════════════════════════════ - -export class DocxFileHandler implements FileHandler { - private readonly extensions = ['.docx']; - - canHandle(path: string): boolean { - return this.extensions.some(e => path.toLowerCase().endsWith(e)); - } - - /** - * Read DOCX content. - * - * Default (offset=0, no explicit length or default length): returns outline - * With offset/length: returns raw pretty-printed XML with line pagination - */ - async read(path: string, options?: ReadOptions): Promise { - const buf = await fs.readFile(path); - const { zip, documentXml } = loadDocxZip(buf); - const pretty = prettyPrintXml(documentXml); - const allLines = pretty.split('\n'); - const totalLines = allLines.length; - const offset = options?.offset ?? 0; - const length = options?.length; - - // If user explicitly requests non-zero offset, give raw XML with pagination - const wantsRaw = offset !== 0; - - if (wantsRaw) { - let startLine: number; - let sliceLength: number; - - if (offset < 0) { - startLine = Math.max(0, totalLines + offset); - sliceLength = totalLines - startLine; - } else { - startLine = offset; - sliceLength = length ?? totalLines; - } - - const sliced = allLines.slice(startLine, startLine + sliceLength); - const remaining = totalLines - (startLine + sliced.length); - const status = `[DOCX XML: lines ${startLine}-${startLine + sliced.length - 1} of ${totalLines} (${remaining} remaining)]`; - - return { - content: status + '\n' + sliced.join('\n'), - mimeType: 'application/xml', - metadata: { isDocx: true, lineCount: totalLines }, - }; - } - - // Default: return outline - const outline = extractOutline(documentXml); - const headerFooterInfo = extractHeaderFooterOutline(zip); - const rawSizeKB = (documentXml.length / 1024).toFixed(1); - - return { - content: outline + headerFooterInfo + - `\n\nRaw XML: ${totalLines} lines, ${rawSizeKB}KB.` + - `\nFor bulk changes (translation, mass find/replace): use start_process with a Python script using zipfile to edit elements.`, - mimeType: 'text/plain', - metadata: { isDocx: true, lineCount: totalLines }, - }; - } - - /** - * Write/create a DOCX file. - * Content is plain text — each line becomes a paragraph. - * Lines starting with # become headings (# = Heading1, ## = Heading2, etc.) - */ - async write(path: string, content: any, mode?: 'rewrite' | 'append'): Promise { - if (mode === 'append') { - throw new Error('DOCX append not supported. Use edit_block to modify existing DOCX files.'); - } - - const text = typeof content === 'string' ? content : String(content); - const lines = text.split('\n'); - - // Build paragraph XML from lines - const paragraphs: string[] = []; - for (const line of lines) { - const headingMatch = line.match(/^(#{1,6})\s+(.+)/); - if (headingMatch) { - const level = headingMatch[1].length; - const headingText = headingMatch[2]; - paragraphs.push( - `` + - `${escapeXml(headingText)}` - ); - } else if (line.trim() === '') { - paragraphs.push(``); - } else { - paragraphs.push( - `${escapeXml(line)}` - ); - } - } - - const docXml = `` + - `` + - `${paragraphs.join('')}` + - `` + - ``; - - const zip = createMinimalDocxZip(docXml); - const buf = zip.generate({ type: 'nodebuffer', compression: 'DEFLATE', compressionOptions: { level: 6 } }); - await fs.writeFile(path, buf); - } - - /** - * Edit DOCX via find/replace on pretty-printed XML. - * - * Works on the same representation that read() returns when using offset/length, - * so XML fragments copied from read output work as search strings. - * After editing, XML is compacted and repacked into the DOCX. - */ - async editRange( - path: string, - _range: string, - content: any, - options?: Record, - ): Promise { - try { - let oldStr: string; - let newStr: string; - let expectedReplacements = 1; - - if (typeof content === 'object' && content !== null) { - oldStr = content.oldStr || content.old_string || content.search || ''; - newStr = content.newStr || content.new_string || content.replace || ''; - expectedReplacements = content.expectedReplacements || content.expected_replacements || 1; - } else { - return { - success: false, editsApplied: 0, - errors: [{ location: 'docx', error: 'DOCX editing requires old_string and new_string' }], - }; - } - - if (!oldStr) { - return { - success: false, editsApplied: 0, - errors: [{ location: 'docx', error: 'old_string cannot be empty' }], - }; - } - - // Load and pretty-print - const buf = await fs.readFile(path); - const zip = new PizZip(buf); - const docFile = zip.file('word/document.xml'); - if (!docFile) throw new Error('Invalid DOCX: missing word/document.xml'); - - const rawXml = docFile.asText(); - const pretty = prettyPrintXml(rawXml); - - // Also check headers/footers for the search string - let targetPretty = pretty; - let targetFile = 'word/document.xml'; - let matchCount = countOccurrences(pretty, oldStr); - - // If not found in document.xml, search through headers/footers - if (matchCount === 0) { - const xmlFiles = ['word/header1.xml', 'word/header2.xml', 'word/header3.xml', - 'word/footer1.xml', 'word/footer2.xml', 'word/footer3.xml']; - for (const xmlPath of xmlFiles) { - const f = zip.file(xmlPath); - if (!f) continue; - const partPretty = prettyPrintXml(f.asText()); - const c = countOccurrences(partPretty, oldStr); - if (c > 0) { - targetPretty = partPretty; - targetFile = xmlPath; - matchCount = c; - break; - } - } - } - - if (matchCount === 0) { - return { - success: false, editsApplied: 0, - errors: [{ location: targetFile, error: `Search string not found in DOCX` }], - }; - } - - if (matchCount !== expectedReplacements) { - return { - success: false, editsApplied: 0, - errors: [{ - location: targetFile, - error: `Expected ${expectedReplacements} occurrence(s) but found ${matchCount}. ` + - `Set expected_replacements to ${matchCount} to replace all, ` + - `or add more context to make the search unique.`, - }], - }; - } - - // Apply replacement - let edited = targetPretty; - if (expectedReplacements === 1) { - const idx = edited.indexOf(oldStr); - edited = edited.substring(0, idx) + newStr + edited.substring(idx + oldStr.length); - } else { - edited = edited.split(oldStr).join(newStr); - } - - // Compact and repack - const compacted = compactXml(edited); - zip.file(targetFile, compacted); - - const outBuf = zip.generate({ - type: 'nodebuffer', - compression: 'DEFLATE', - compressionOptions: { level: 6 }, - }); - await fs.writeFile(path, outBuf); - - return { success: true, editsApplied: matchCount }; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - return { - success: false, editsApplied: 0, - errors: [{ location: 'docx', error: errorMessage }], - }; - } - } - - /** - * Get DOCX file info - */ - async getInfo(path: string): Promise { - const stats = await fs.stat(path); - - let metadata: any = { isDocx: true }; - try { - const buf = await fs.readFile(path); - const { documentXml } = loadDocxZip(buf); - const text = extractAllText(documentXml); - const wordCount = text.split(/\s+/).filter(w => w.length > 0).length; - const paragraphCount = (documentXml.match(/]/g) || []).length; - const tableCount = (documentXml.match(/]/g) || []).length; - const imageCount = (documentXml.match(/]/g) || []).length; - - metadata = { - isDocx: true, - paragraphCount, - tableCount, - imageCount, - wordCount, - }; - } catch { /* return basic info */ } - - return { - size: stats.size, - created: stats.birthtime, - modified: stats.mtime, - accessed: stats.atime, - isDirectory: false, - isFile: true, - permissions: (stats.mode & 0o777).toString(8), - fileType: 'docx', - metadata, - }; - } -} diff --git a/src/utils/files/factory.ts b/src/utils/files/factory.ts index 054107cb..34a2e1f4 100644 --- a/src/utils/files/factory.ts +++ b/src/utils/files/factory.ts @@ -12,7 +12,6 @@ import { ImageFileHandler } from './image.js'; import { BinaryFileHandler } from './binary.js'; import { ExcelFileHandler } from './excel.js'; import { PdfFileHandler } from './pdf.js'; -import { DocxFileHandler } from './docx.js'; // Singleton instances of each handler let excelHandler: ExcelFileHandler | null = null; @@ -20,7 +19,6 @@ let imageHandler: ImageFileHandler | null = null; let textHandler: TextFileHandler | null = null; let binaryHandler: BinaryFileHandler | null = null; let pdfHandler: PdfFileHandler | null = null; -let docxHandler: DocxFileHandler | null = null; /** * Initialize handlers (lazy initialization) @@ -50,11 +48,6 @@ function getPdfHandler(): PdfFileHandler { return pdfHandler; } -function getDocxHandler(): DocxFileHandler { - if (!docxHandler) docxHandler = new DocxFileHandler(); - return docxHandler; -} - /** * Get the appropriate file handler for a given file path * @@ -63,23 +56,17 @@ function getDocxHandler(): DocxFileHandler { * BinaryFileHandler uses async isBinaryFile for content-based detection. * * Priority order: - * 1. DOCX files (extension based) - * 2. PDF files (extension based) - * 3. Excel files (xlsx, xls, xlsm) - extension based - * 4. Image files (png, jpg, gif, webp) - extension based - * 5. Binary files - content-based detection via isBinaryFile - * 6. Text files (default) + * 1. PDF files (extension based) + * 2. Excel files (xlsx, xls, xlsm) - extension based + * 3. Image files (png, jpg, gif, webp) - extension based + * 4. Binary files - content-based detection via isBinaryFile + * 5. Text files (default) * * @param filePath File path to get handler for * @returns FileHandler instance that can handle this file */ export async function getFileHandler(filePath: string): Promise { - // Check DOCX first (extension-based, sync) - if (getDocxHandler().canHandle(filePath)) { - return getDocxHandler(); - } - - // Check PDF (extension-based, sync) + // Check PDF first (extension-based, sync) if (getPdfHandler().canHandle(filePath)) { return getPdfHandler(); } diff --git a/src/utils/system-info.ts b/src/utils/system-info.ts index d9a3137b..30308482 100644 --- a/src/utils/system-info.ts +++ b/src/utils/system-info.ts @@ -213,7 +213,7 @@ function discoverContainerMounts(isContainer: boolean): DockerMount[] { const device = parts[0]; const mountPoint = parts[1]; const fsType = parts[2]; - const options = parts[3].split(','); + const options = parts[3]; // Skip system mount points const isSystemMountPoint = @@ -826,4 +826,4 @@ When users ask about file locations, check these mounted paths first.`; } return guidance; -} +} \ No newline at end of file diff --git a/src/utils/usageTracker.ts b/src/utils/usageTracker.ts index bc210bdb..3c01633c 100644 --- a/src/utils/usageTracker.ts +++ b/src/utils/usageTracker.ts @@ -424,12 +424,6 @@ class UsageTracker { return false; } - // Check local config override (user can disable via config) - const localOverride = await configManager.getValue('onboarding_injection'); - if (localOverride === false) { - return false; - } - // Check if onboarding is disabled via command line argument if ((global as any).disableOnboarding) { return false; diff --git a/src/version.ts b/src/version.ts index d864f8e2..831705d3 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.2.37'; +export const VERSION = '0.2.36'; diff --git a/test/test-electron-debug.js b/test/test-electron-debug.js new file mode 100644 index 00000000..ff8c9d74 --- /dev/null +++ b/test/test-electron-debug.js @@ -0,0 +1,57 @@ +#!/usr/bin/env node +import assert from 'assert'; + +import { cdpAdapter } from '../dist/tools/macos-control/cdp-adapter.js'; + +async function testDisconnectedCalls() { + console.log('\n--- Test: electron debug disconnected behavior ---'); + + const evalResult = await cdpAdapter.evaluate({ + sessionId: 'missing-session', + expression: '1 + 1', + }); + + assert.strictEqual(evalResult.ok, false); + assert.strictEqual(evalResult.error?.code, 'CDP_NOT_CONNECTED'); + + const disconnectResult = await cdpAdapter.disconnect('missing-session'); + assert.strictEqual(disconnectResult.ok, false); + assert.strictEqual(disconnectResult.error?.code, 'CDP_NOT_CONNECTED'); + + console.log('✓ electron debug adapter handles missing sessions correctly'); +} + +async function testAttachFailureShape() { + console.log('\n--- Test: electron debug attach failure shape ---'); + + const attachResult = await cdpAdapter.attach({ + host: '127.0.0.1', + port: 59999, + }); + + assert.strictEqual(attachResult.ok, false); + assert.strictEqual(attachResult.error?.code, 'CDP_CONNECT_FAILED'); + assert.ok(attachResult.error?.message); + + console.log('✓ electron debug attach returns typed errors'); +} + +export default async function runTests() { + try { + await testDisconnectedCalls(); + await testAttachFailureShape(); + console.log('\n✅ Electron debug tests passed!'); + return true; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error('❌ test-electron-debug failed:', message); + if (error instanceof Error && error.stack) { + console.error(error.stack); + } + return false; + } +} + +if (import.meta.url === `file://${process.argv[1]}`) { + runTests().then((success) => process.exit(success ? 0 : 1)); +} diff --git a/test/test-macos-control.js b/test/test-macos-control.js new file mode 100644 index 00000000..febbaca0 --- /dev/null +++ b/test/test-macos-control.js @@ -0,0 +1,76 @@ +#!/usr/bin/env node +import assert from 'assert'; + +import { + MacosAxFindArgsSchema, + MacosAxClickArgsSchema, + MacosAxBatchArgsSchema, +} from '../dist/tools/schemas.js'; +import { macosControlOrchestrator } from '../dist/tools/macos-control/orchestrator.js'; + +async function testSchemas() { + console.log('\n--- Test: macOS control schemas ---'); + + const findParsed = MacosAxFindArgsSchema.parse({ + app: 'System Settings', + text: 'Bluetooth', + role: 'toggle', + }); + assert.strictEqual(findParsed.app, 'System Settings'); + + const clickById = MacosAxClickArgsSchema.parse({ id: '123-abc' }); + assert.strictEqual(clickById.id, '123-abc'); + + const clickByText = MacosAxClickArgsSchema.parse({ app: 'Finder', text: 'Downloads' }); + assert.strictEqual(clickByText.app, 'Finder'); + + const batchParsed = MacosAxBatchArgsSchema.parse({ + commands: [ + { action: 'wait', ms: 250 }, + { action: 'activate', app: 'Finder' } + ] + }); + assert.strictEqual(batchParsed.commands.length, 2); + assert.strictEqual(batchParsed.stopOnError, true); + + console.log('✓ macOS control schemas parse correctly'); +} + +async function testPlatformGating() { + console.log('\n--- Test: macOS control platform gating ---'); + + if (process.platform !== 'darwin') { + const batchResult = await macosControlOrchestrator.axBatch([ + { action: 'wait', ms: 100 } + ]); + + assert.strictEqual(batchResult.ok, false); + assert.strictEqual(batchResult.error?.code, 'UNSUPPORTED_PLATFORM'); + console.log('✓ non-macOS gating works'); + return; + } + + const statusResult = await macosControlOrchestrator.axStatus(); + assert.ok(typeof statusResult.ok === 'boolean'); + console.log('✓ macOS status call executed'); +} + +export default async function runTests() { + try { + await testSchemas(); + await testPlatformGating(); + console.log('\n✅ macOS control tests passed!'); + return true; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error('❌ test-macos-control failed:', message); + if (error instanceof Error && error.stack) { + console.error(error.stack); + } + return false; + } +} + +if (import.meta.url === `file://${process.argv[1]}`) { + runTests().then((success) => process.exit(success ? 0 : 1)); +}