cue-ai 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cue-ai",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "cue — Agent Profile Manager for Claude Code & Codex. Pick a profile, launch with the right skills, MCPs, and plugins.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -5,7 +5,7 @@
5
5
  * import type { Profile, NpxSkillRef, MCPRef, SkillRef } from "../../profiles/_types";
6
6
  */
7
7
 
8
- export type AgentKind = "claude-code" | "codex";
8
+ export type AgentKind = "claude-code" | "codex" | "cursor" | "cline" | "windsurf" | "gemini" | "copilot" | "roo" | "amp" | "aider";
9
9
 
10
10
  export interface AgentScoped {
11
11
  agents?: AgentKind[];
@@ -9,7 +9,10 @@ skills:
9
9
  - meta/analyze
10
10
  - meta/cue-usage
11
11
  - meta/builtin-manager
12
+ - meta/kiro-powers
12
13
  - caveman/caveman
13
14
  - caveman/caveman-commit
15
+ mcps:
16
+ - cue-tty-watch # Claude's eyes inside X displays / tmux panes (screenshot, capture-pane, send-keys)
14
17
  plugins:
15
18
  - claude-mem@thedotmack
@@ -6,7 +6,6 @@ skills:
6
6
  local:
7
7
  - "*/*"
8
8
  mcps:
9
- - claude-mem
10
9
  - Higgsfield
11
10
  - colony
12
11
  - coolify
@@ -15,7 +14,6 @@ mcps:
15
14
  - hostinger-api
16
15
  - letsfg
17
16
  - marva-blog
18
- - medusadocs
19
17
  - obsidian-vault
20
18
  - polymarket-live
21
19
  - recodee
@@ -1,13 +1,14 @@
1
- name: readme-writer-svg
1
+ name: readme-writer
2
2
  icon: "🎨"
3
3
  description: "Beautiful README design with SVG diagrams — architecture flows, terminal mockups, and editorial visuals"
4
4
  inherits: core
5
5
  skills:
6
6
  local:
7
7
  - design/readme-svg-design
8
+ - design/headless-gif-demo
8
9
  npx:
9
10
  - repo: yizhiyanhua-ai/fireworks-tech-graph
10
11
  skills: [fireworks-tech-graph]
11
12
  - repo: cathrynlavery/diagram-design
12
13
  skills: [diagram-design]
13
- mcps: []
14
+ mcps: [] # cue-tty-watch comes from core via inheritance
@@ -0,0 +1,19 @@
1
+ name: threejs
2
+ icon: "🎲"
3
+ description: "Three.js 3D development — geometry, materials, shaders, animation, postprocessing, and interaction"
4
+ inherits: core
5
+ skills:
6
+ npx:
7
+ - repo: CloudAI-X/threejs-skills
8
+ skills:
9
+ - threejs-fundamentals
10
+ - threejs-geometry
11
+ - threejs-materials
12
+ - threejs-lighting
13
+ - threejs-textures
14
+ - threejs-animation
15
+ - threejs-loaders
16
+ - threejs-shaders
17
+ - threejs-postprocessing
18
+ - threejs-interaction
19
+ mcps: []
@@ -10,6 +10,9 @@
10
10
  "env": {
11
11
  "KROSCHUORDER_API_URL": "<redacted>"
12
12
  }
13
+ },
14
+ "cue-tty-watch": {
15
+ "command": "/home/deadpool/Documents/cue/resources/mcps/cue-tty-watch/bin/cue-tty-watch"
13
16
  }
14
17
  },
15
18
  "source": "claude",
@@ -110,6 +110,9 @@
110
110
  "~/Documents/soul/mcps/mcps/soul-skills/server.py"
111
111
  ],
112
112
  "command": "~/.local/bin/uv"
113
+ },
114
+ "cue-tty-watch": {
115
+ "command": "/home/deadpool/Documents/cue/resources/mcps/cue-tty-watch/bin/cue-tty-watch"
113
116
  }
114
117
  },
115
118
  "source": "claude_runtime",
@@ -79,6 +79,9 @@
79
79
  ],
80
80
  "command": "~/.local/bin/uv",
81
81
  "enabled": false
82
+ },
83
+ "cue-tty-watch": {
84
+ "command": "/home/deadpool/Documents/cue/resources/mcps/cue-tty-watch/bin/cue-tty-watch"
82
85
  }
83
86
  },
84
87
  "source": "codex",
@@ -0,0 +1,80 @@
1
+ # cue-tty-watch
2
+
3
+ > MCP server that gives Claude eyes inside X displays and tmux panes.
4
+
5
+ Built for the kitty + Xvfb + tmux GIF-capture pipeline ([`scripts/record-demo-kitty.sh`](../../../scripts/record-demo-kitty.sh)) where the only way to know what the headless terminal is doing was to wait for a human to take a screenshot. With this MCP, Claude can `screenshot` and `tmux_pane` directly.
6
+
7
+ ## Tools
8
+
9
+ | Tool | Purpose | Returns |
10
+ |---|---|---|
11
+ | `screenshot` | PNG of an X display (default `:99`) | Image |
12
+ | `tmux_pane` | Rendered text of a tmux pane | Text |
13
+ | `send_keys_tmux` | `tmux send-keys` into a pane | Text confirmation |
14
+ | `send_keys_xdotool` | `xdotool key` to a window by class | Text confirmation |
15
+ | `list_xwindows` | Windows on a display + their class/geom | Text table |
16
+
17
+ ## Requirements
18
+
19
+ Already on most Linux dev boxes; install with `apt` if missing:
20
+
21
+ ```bash
22
+ sudo apt install xvfb x11-apps imagemagick xdotool
23
+ # tmux and bun should already be there
24
+ ```
25
+
26
+ The `xwd` binary (from `x11-apps`) is what captures the display; ImageMagick's `convert` turns the XWD blob into PNG.
27
+
28
+ ## Install dependencies
29
+
30
+ ```bash
31
+ cd resources/mcps/cue-tty-watch
32
+ bun install
33
+ ```
34
+
35
+ ## Register with Claude Code
36
+
37
+ Add to your `~/.claude.json` (or `~/.config/claude/claude.json`) under `mcpServers`:
38
+
39
+ ```json
40
+ {
41
+ "mcpServers": {
42
+ "cue-tty-watch": {
43
+ "command": "/absolute/path/to/cue/resources/mcps/cue-tty-watch/bin/cue-tty-watch"
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ Then **restart Claude Code** for the MCP to load.
50
+
51
+ ## Register with cue profile
52
+
53
+ Add to any profile's `profile.yaml` that needs it (typically `readme-writer` for demo recording):
54
+
55
+ ```yaml
56
+ mcps:
57
+ - cue-tty-watch
58
+ ```
59
+
60
+ ## Usage from Claude
61
+
62
+ Once registered, Claude can call:
63
+
64
+ ```
65
+ screenshot(display=":99") # → see what's on Xvfb
66
+ tmux_pane(socket="cue-demo", session="demo") # → see what tmux is showing
67
+ send_keys_tmux(socket="cue-demo", session="demo",
68
+ keys=["cue list", "Enter"]) # → drive a demo
69
+ list_xwindows(display=":99") # → find a window by class
70
+ ```
71
+
72
+ ## Why this exists
73
+
74
+ Claude Code's built-in tools (`Bash`, `Read`, `Write`, etc.) can run commands and read files but can't *see* a graphical session. When debugging the kitty-graphics GIF capture, every iteration meant: run script (~50 s), wait for a human screenshot, infer what went wrong, patch, repeat.
75
+
76
+ With this MCP, the loop is: `send_keys → screenshot → adjust → send_keys → screenshot`, all inside one Claude turn.
77
+
78
+ ## License
79
+
80
+ MIT (same as parent repo).
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ # cue-tty-watch — launcher shim for the MCP server
3
+ # Resolves the bun binary at run time so it works from any Claude Code session.
4
+
5
+ set -e
6
+
7
+ # Find bun: prefer ~/.bun/bin, then $PATH
8
+ if [ -x "$HOME/.bun/bin/bun" ]; then
9
+ BUN="$HOME/.bun/bin/bun"
10
+ elif command -v bun >/dev/null 2>&1; then
11
+ BUN="$(command -v bun)"
12
+ else
13
+ echo "cue-tty-watch: bun not found — install from https://bun.sh" >&2
14
+ exit 1
15
+ fi
16
+
17
+ DIR="$(dirname "$(readlink -f "$0")")/.."
18
+ exec "$BUN" run "$DIR/server.ts"
@@ -0,0 +1,198 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "cue-tty-watch",
7
+ "dependencies": {
8
+ "@modelcontextprotocol/sdk": "^1.20.0",
9
+ "zod": "^3.23.0",
10
+ },
11
+ },
12
+ },
13
+ "packages": {
14
+ "@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="],
15
+
16
+ "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "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", "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" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
17
+
18
+ "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
19
+
20
+ "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
21
+
22
+ "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
23
+
24
+ "body-parser": ["body-parser@2.2.2", "", { "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" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
25
+
26
+ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
27
+
28
+ "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
29
+
30
+ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
31
+
32
+ "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="],
33
+
34
+ "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
35
+
36
+ "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
37
+
38
+ "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
39
+
40
+ "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
41
+
42
+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
43
+
44
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
45
+
46
+ "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
47
+
48
+ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
49
+
50
+ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
51
+
52
+ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
53
+
54
+ "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
55
+
56
+ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
57
+
58
+ "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="],
59
+
60
+ "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
61
+
62
+ "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
63
+
64
+ "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
65
+
66
+ "eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="],
67
+
68
+ "express": ["express@5.2.1", "", { "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" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
69
+
70
+ "express-rate-limit": ["express-rate-limit@8.5.2", "", { "dependencies": { "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A=="],
71
+
72
+ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
73
+
74
+ "fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="],
75
+
76
+ "finalhandler": ["finalhandler@2.1.1", "", { "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" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
77
+
78
+ "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
79
+
80
+ "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
81
+
82
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
83
+
84
+ "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
85
+
86
+ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
87
+
88
+ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
89
+
90
+ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
91
+
92
+ "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="],
93
+
94
+ "hono": ["hono@4.12.22", "", {}, "sha512-7fvVPbB92zNRsQke+uiRGwtTuef0tB2Dg4hWxYfFNvkQhIltWoyi0ONReM5LWA+jJWS3nfT5lTq+qbsIpX0IQw=="],
95
+
96
+ "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
97
+
98
+ "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
99
+
100
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
101
+
102
+ "ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="],
103
+
104
+ "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
105
+
106
+ "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
107
+
108
+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
109
+
110
+ "jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="],
111
+
112
+ "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
113
+
114
+ "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
115
+
116
+ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
117
+
118
+ "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
119
+
120
+ "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
121
+
122
+ "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
123
+
124
+ "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
125
+
126
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
127
+
128
+ "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
129
+
130
+ "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
131
+
132
+ "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
133
+
134
+ "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
135
+
136
+ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
137
+
138
+ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
139
+
140
+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
141
+
142
+ "path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
143
+
144
+ "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
145
+
146
+ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
147
+
148
+ "qs": ["qs@6.15.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw=="],
149
+
150
+ "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
151
+
152
+ "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
153
+
154
+ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
155
+
156
+ "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
157
+
158
+ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
159
+
160
+ "send": ["send@1.2.1", "", { "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" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
161
+
162
+ "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
163
+
164
+ "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
165
+
166
+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
167
+
168
+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
169
+
170
+ "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
171
+
172
+ "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="],
173
+
174
+ "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
175
+
176
+ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
177
+
178
+ "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
179
+
180
+ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
181
+
182
+ "type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="],
183
+
184
+ "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
185
+
186
+ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
187
+
188
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
189
+
190
+ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
191
+
192
+ "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
193
+
194
+ "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
195
+
196
+ "type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
197
+ }
198
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "cue-tty-watch",
3
+ "version": "0.1.0",
4
+ "description": "MCP server giving Claude eyes inside X displays, tmux panes, and Xvfb sessions — screenshot, capture-pane, send-keys.",
5
+ "type": "module",
6
+ "private": true,
7
+ "scripts": {
8
+ "start": "bun server.ts"
9
+ },
10
+ "engines": {
11
+ "bun": ">=1.0.0"
12
+ },
13
+ "dependencies": {
14
+ "@modelcontextprotocol/sdk": "^1.20.0",
15
+ "zod": "^3.23.0"
16
+ }
17
+ }
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * cue-tty-watch — MCP server giving Claude eyes inside X displays and tmux panes.
4
+ *
5
+ * Tools:
6
+ * screenshot → PNG of an X display (default :99 for Xvfb capture flows)
7
+ * tmux_pane → text contents of a tmux pane
8
+ * send_keys_tmux → tmux send-keys (Enter, Up, Down, "literal text", etc.)
9
+ * send_keys_xdotool → xdotool key against a window by --class
10
+ * list_xwindows → enumerate windows + their class on a display
11
+ *
12
+ * Designed for the kitty + Xvfb + tmux demo-capture pipeline in scripts/record-demo-kitty.sh.
13
+ */
14
+
15
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
16
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
17
+ import { spawnSync } from "node:child_process";
18
+ import { z } from "zod";
19
+
20
+ const server = new McpServer(
21
+ { name: "cue-tty-watch", version: "0.1.0" },
22
+ { capabilities: { tools: {} } }
23
+ );
24
+
25
+ // ─── helpers ─────────────────────────────────────────────────────────────────
26
+
27
+ const MAX_BUF = 100 * 1024 * 1024;
28
+
29
+ function err(text: string) {
30
+ return { content: [{ type: "text" as const, text }], isError: true };
31
+ }
32
+
33
+ function text(text: string) {
34
+ return { content: [{ type: "text" as const, text }] };
35
+ }
36
+
37
+ function image(base64: string) {
38
+ return { content: [{ type: "image" as const, data: base64, mimeType: "image/png" }] };
39
+ }
40
+
41
+ // ─── 1. screenshot ───────────────────────────────────────────────────────────
42
+
43
+ server.tool(
44
+ "screenshot",
45
+ "Capture a frame of an X display as a PNG. Use this to SEE what's actually rendering inside a kitty/tmux/Xvfb session. Default display ':99' matches the Xvfb capture pipeline in scripts/record-demo-kitty.sh. Image is downscaled to max 1400px width to keep payload reasonable.",
46
+ {
47
+ display: z.string().default(":99").describe("X display, e.g. ':99' (Xvfb) or ':0' (real)"),
48
+ max_width: z.number().int().min(200).max(2400).default(1400).describe("Resize to at most this width"),
49
+ },
50
+ async ({ display, max_width }) => {
51
+ const xwd = spawnSync("xwd", ["-root", "-silent", "-display", display], {
52
+ stdio: ["ignore", "pipe", "pipe"],
53
+ maxBuffer: MAX_BUF,
54
+ });
55
+ if (xwd.status !== 0) return err(`xwd failed (display ${display}): ${xwd.stderr?.toString() ?? "no output"}`);
56
+
57
+ const conv = spawnSync("convert", ["xwd:-", "-resize", `${max_width}>`, "png:-"], {
58
+ input: xwd.stdout,
59
+ stdio: ["pipe", "pipe", "pipe"],
60
+ maxBuffer: MAX_BUF,
61
+ });
62
+ if (conv.status !== 0) return err(`ImageMagick convert failed: ${conv.stderr?.toString() ?? "no output"}`);
63
+
64
+ return image(conv.stdout.toString("base64"));
65
+ }
66
+ );
67
+
68
+ // ─── 2. tmux_pane ────────────────────────────────────────────────────────────
69
+
70
+ server.tool(
71
+ "tmux_pane",
72
+ "Read the current rendered text of a tmux pane. Use to verify what command was typed, what output appeared, whether a prompt is waiting.",
73
+ {
74
+ socket: z.string().default("").describe("tmux -L socket name (e.g. 'cue-demo'). Empty = default socket."),
75
+ session: z.string().describe("Session name, e.g. 'demo'"),
76
+ target: z.string().default("0").describe("Window.pane target, e.g. '0' or '0.1'"),
77
+ history_lines: z.number().int().min(0).max(5000).default(0).describe("Include this many scrollback lines (0 = visible only)"),
78
+ },
79
+ async ({ socket, session, target, history_lines }) => {
80
+ const args: string[] = [];
81
+ if (socket) args.push("-L", socket);
82
+ args.push("capture-pane", "-p", "-t", `${session}:${target}`);
83
+ if (history_lines > 0) args.push("-S", `-${history_lines}`);
84
+
85
+ const res = spawnSync("tmux", args, { encoding: "utf8" });
86
+ if (res.status !== 0) return err(`tmux capture-pane failed: ${res.stderr}`);
87
+ return text(res.stdout || "(empty pane)");
88
+ }
89
+ );
90
+
91
+ // ─── 3. send_keys_tmux ───────────────────────────────────────────────────────
92
+
93
+ server.tool(
94
+ "send_keys_tmux",
95
+ "Send keystrokes into a tmux pane via tmux send-keys. Each item in 'keys' is a separate argument: 'Enter', 'Up', 'Down', 'C-c', 'BSpace', or literal text strings.",
96
+ {
97
+ socket: z.string().default("").describe("tmux -L socket name. Empty = default socket."),
98
+ session: z.string().describe("Session name"),
99
+ target: z.string().default("0").describe("Window.pane target"),
100
+ keys: z.array(z.string()).min(1).describe("Array of args passed to tmux send-keys. Example: ['echo hi', 'Enter']"),
101
+ },
102
+ async ({ socket, session, target, keys }) => {
103
+ const args: string[] = [];
104
+ if (socket) args.push("-L", socket);
105
+ args.push("send-keys", "-t", `${session}:${target}`, ...keys);
106
+
107
+ const res = spawnSync("tmux", args, { encoding: "utf8" });
108
+ if (res.status !== 0) return err(`tmux send-keys failed: ${res.stderr}`);
109
+ return text(`sent ${keys.length} arg(s) to ${session}:${target}`);
110
+ }
111
+ );
112
+
113
+ // ─── 4. send_keys_xdotool ────────────────────────────────────────────────────
114
+
115
+ server.tool(
116
+ "send_keys_xdotool",
117
+ "Send keystrokes directly to an X window matched by WM_CLASS. Use when targeting a kitty window before tmux is attached, or for keys tmux can't pass cleanly.",
118
+ {
119
+ display: z.string().default(":99"),
120
+ window_class: z.string().describe("WM_CLASS to match (e.g. 'cue-demo')"),
121
+ keys: z.array(z.string()).min(1).describe("xdotool key specs, e.g. ['Down', 'Down', 'Return']"),
122
+ delay_ms: z.number().int().min(0).max(2000).default(80).describe("Inter-key delay"),
123
+ },
124
+ async ({ display, window_class, keys, delay_ms }) => {
125
+ const env = { ...process.env, DISPLAY: display };
126
+ const find = spawnSync("xdotool", ["search", "--class", window_class], { env, encoding: "utf8" });
127
+ if (find.status !== 0 || !find.stdout.trim()) {
128
+ return err(`No window matching class '${window_class}' on display ${display}`);
129
+ }
130
+ const wid = find.stdout.trim().split("\n")[0]!;
131
+
132
+ for (const k of keys) {
133
+ const r = spawnSync("xdotool", ["key", "--window", wid, "--delay", String(delay_ms), k], { env });
134
+ if (r.status !== 0) return err(`xdotool key '${k}' failed`);
135
+ }
136
+ return text(`sent ${keys.length} key(s) to window ${wid} (class=${window_class})`);
137
+ }
138
+ );
139
+
140
+ // ─── 5. list_xwindows ────────────────────────────────────────────────────────
141
+
142
+ server.tool(
143
+ "list_xwindows",
144
+ "List X windows on a display with their class and geometry. Use to find the right window_class before sending keys.",
145
+ {
146
+ display: z.string().default(":99"),
147
+ },
148
+ async ({ display }) => {
149
+ const env = { ...process.env, DISPLAY: display };
150
+ const search = spawnSync("xdotool", ["search", ".*"], { env, encoding: "utf8" });
151
+ if (search.status !== 0) return err(`xdotool search failed: ${search.stderr}`);
152
+
153
+ const wids = search.stdout.trim().split("\n").filter(Boolean);
154
+ if (!wids.length) return text("(no windows)");
155
+
156
+ const rows: string[] = [];
157
+ rows.push("WID CLASS GEOMETRY");
158
+ for (const wid of wids) {
159
+ const cls = spawnSync("xdotool", ["getwindowclassname", wid], { env, encoding: "utf8" });
160
+ const geo = spawnSync("xdotool", ["getwindowgeometry", "--shell", wid], { env, encoding: "utf8" });
161
+ const className = (cls.stdout || "").trim();
162
+ if (!className) continue; // skip unmapped / decoration-only windows
163
+
164
+ const geoVars: Record<string, string> = {};
165
+ for (const line of (geo.stdout || "").split("\n")) {
166
+ const m = line.match(/^(\w+)=(.+)$/);
167
+ if (m) geoVars[m[1]!] = m[2]!;
168
+ }
169
+ const geomStr = geoVars.WIDTH && geoVars.HEIGHT
170
+ ? `${geoVars.WIDTH}x${geoVars.HEIGHT}+${geoVars.X ?? "?"}+${geoVars.Y ?? "?"}`
171
+ : "?";
172
+ rows.push(`${wid.padEnd(11)} ${className.padEnd(30)} ${geomStr}`);
173
+ }
174
+ return text(rows.join("\n"));
175
+ }
176
+ );
177
+
178
+ // ─── start ───────────────────────────────────────────────────────────────────
179
+
180
+ const transport = new StdioServerTransport();
181
+ await server.connect(transport);