deskpilot 0.2.0__tar.gz
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.
- deskpilot-0.2.0/.claude-plugin/marketplace.json +17 -0
- deskpilot-0.2.0/.claude-plugin/plugin.json +13 -0
- deskpilot-0.2.0/.github/ISSUE_TEMPLATE/bug.yml +29 -0
- deskpilot-0.2.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- deskpilot-0.2.0/.github/ISSUE_TEMPLATE/feature.yml +16 -0
- deskpilot-0.2.0/.github/dependabot.yml +20 -0
- deskpilot-0.2.0/.github/pull_request_template.md +13 -0
- deskpilot-0.2.0/.github/workflows/ci.yml +99 -0
- deskpilot-0.2.0/.github/workflows/release.yml +75 -0
- deskpilot-0.2.0/.github/zizmor.yml +5 -0
- deskpilot-0.2.0/.gitignore +5 -0
- deskpilot-0.2.0/.mergepatrol/mergepatrol.yml +20 -0
- deskpilot-0.2.0/AGENTS.md +50 -0
- deskpilot-0.2.0/CHANGELOG.md +58 -0
- deskpilot-0.2.0/CONTRIBUTING.md +33 -0
- deskpilot-0.2.0/LICENSE +21 -0
- deskpilot-0.2.0/PKG-INFO +131 -0
- deskpilot-0.2.0/README.md +114 -0
- deskpilot-0.2.0/SECURITY.md +21 -0
- deskpilot-0.2.0/pyproject.toml +47 -0
- deskpilot-0.2.0/scripts/changelog.py +53 -0
- deskpilot-0.2.0/skills/deskpilot/SKILL.md +56 -0
- deskpilot-0.2.0/src/deskpilot/__init__.py +1 -0
- deskpilot-0.2.0/src/deskpilot/cli.py +173 -0
- deskpilot-0.2.0/src/deskpilot/desk.py +221 -0
- deskpilot-0.2.0/src/deskpilot/hypr.py +222 -0
- deskpilot-0.2.0/src/deskpilot/keys.py +76 -0
- deskpilot-0.2.0/src/deskpilot/mcp_server.py +119 -0
- deskpilot-0.2.0/src/deskpilot/overlay.py +105 -0
- deskpilot-0.2.0/src/deskpilot/plugin/Makefile +14 -0
- deskpilot-0.2.0/src/deskpilot/plugin/deskpilot.cpp +1115 -0
- deskpilot-0.2.0/src/deskpilot/plugin.py +181 -0
- deskpilot-0.2.0/src/deskpilot/screen.py +138 -0
- deskpilot-0.2.0/src/deskpilot/uinput.py +96 -0
- deskpilot-0.2.0/tests/test_core.py +447 -0
- deskpilot-0.2.0/uv.lock +919 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
3
|
+
"name": "deskpilot",
|
|
4
|
+
"description": "deskpilot: desktop control for agents on Hyprland, without taking the user's pointer.",
|
|
5
|
+
"owner": {
|
|
6
|
+
"name": "Cillian Berragan",
|
|
7
|
+
"url": "https://github.com/cjber"
|
|
8
|
+
},
|
|
9
|
+
"plugins": [
|
|
10
|
+
{
|
|
11
|
+
"name": "deskpilot",
|
|
12
|
+
"description": "Click, scroll and type in Hyprland windows from a screenshot, in Wayland, XWayland and Wine apps, while the user keeps their pointer and focus.",
|
|
13
|
+
"source": "./",
|
|
14
|
+
"category": "productivity"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "deskpilot",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Desktop control for agents on Hyprland: screenshot a window, then click, scroll and type at points in it while the user keeps their own pointer and keyboard focus. Works in native Wayland, XWayland and Wine apps, games included.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Cillian Berragan",
|
|
7
|
+
"url": "https://github.com/cjber"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/cjber/deskpilot",
|
|
10
|
+
"repository": "https://github.com/cjber/deskpilot",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": ["agent-skills", "hyprland", "wayland", "desktop-automation", "computer-use", "mcp"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Bug
|
|
2
|
+
description: deskpilot did the wrong thing
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: what
|
|
7
|
+
attributes:
|
|
8
|
+
label: What happened
|
|
9
|
+
description: The command you ran, what you expected, and what happened instead. A click or key that reached the wrong window is the most serious kind of bug, so say so.
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: window
|
|
14
|
+
attributes:
|
|
15
|
+
label: Target window
|
|
16
|
+
description: The window's entry from `deskpilot --json windows`, and whether it is native Wayland, XWayland or Wine.
|
|
17
|
+
render: json
|
|
18
|
+
- type: input
|
|
19
|
+
id: hyprland
|
|
20
|
+
attributes:
|
|
21
|
+
label: Hyprland version
|
|
22
|
+
description: Output of `hyprctl version | head -1`.
|
|
23
|
+
validations:
|
|
24
|
+
required: true
|
|
25
|
+
- type: input
|
|
26
|
+
id: plugin
|
|
27
|
+
attributes:
|
|
28
|
+
label: Plugin status
|
|
29
|
+
description: Output of `deskpilot plugin status`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: Feature
|
|
2
|
+
description: Suggest an action or a change
|
|
3
|
+
labels: [enhancement]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: problem
|
|
7
|
+
attributes:
|
|
8
|
+
label: Problem
|
|
9
|
+
description: What an agent cannot do today, and in which app.
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: user
|
|
14
|
+
attributes:
|
|
15
|
+
label: What the user must keep
|
|
16
|
+
description: How the user's own pointer, focus and typing should be affected while it runs.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: uv
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: daily
|
|
7
|
+
cooldown:
|
|
8
|
+
default-days: 1
|
|
9
|
+
groups:
|
|
10
|
+
dependencies:
|
|
11
|
+
patterns: ["*"]
|
|
12
|
+
- package-ecosystem: github-actions
|
|
13
|
+
directory: /
|
|
14
|
+
schedule:
|
|
15
|
+
interval: daily
|
|
16
|
+
cooldown:
|
|
17
|
+
default-days: 1
|
|
18
|
+
groups:
|
|
19
|
+
actions:
|
|
20
|
+
patterns: ["*"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
## What changed and why
|
|
2
|
+
|
|
3
|
+
<!-- One or two sentences. Link the issue it fixes: "Fixes #123". -->
|
|
4
|
+
|
|
5
|
+
## Evidence
|
|
6
|
+
|
|
7
|
+
<!-- How you checked it. For a change to clicks or keys, name the clients you tried (Wayland, XWayland, Wine). -->
|
|
8
|
+
|
|
9
|
+
## Checklist
|
|
10
|
+
|
|
11
|
+
- [ ] The checks in `AGENTS.md` pass
|
|
12
|
+
- [ ] `CHANGELOG.md` has a line under Unreleased, if a user would notice the change
|
|
13
|
+
- [ ] `README.md` and `skills/deskpilot/SKILL.md` match any command or flag change
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
lint:
|
|
17
|
+
name: Lint and types
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 10
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
persist-credentials: false
|
|
25
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
26
|
+
with:
|
|
27
|
+
version: "0.12.18"
|
|
28
|
+
python-version: "3.13"
|
|
29
|
+
enable-cache: false
|
|
30
|
+
- run: uv sync --locked
|
|
31
|
+
- run: uv run --no-sync ruff check .
|
|
32
|
+
- run: uv run --no-sync ruff format --check .
|
|
33
|
+
- run: uv run --no-sync ty check
|
|
34
|
+
- name: Check CHANGELOG.md
|
|
35
|
+
run: python3 scripts/changelog.py --check
|
|
36
|
+
|
|
37
|
+
test:
|
|
38
|
+
name: Test Python ${{ matrix.python }}
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
timeout-minutes: 10
|
|
41
|
+
strategy:
|
|
42
|
+
matrix:
|
|
43
|
+
python: ["3.12", "3.14"]
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
46
|
+
with:
|
|
47
|
+
persist-credentials: false
|
|
48
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
49
|
+
with:
|
|
50
|
+
version: "0.12.18"
|
|
51
|
+
python-version: ${{ matrix.python }}
|
|
52
|
+
enable-cache: false
|
|
53
|
+
- run: uv sync --locked
|
|
54
|
+
- run: uv run --no-sync pytest -q
|
|
55
|
+
|
|
56
|
+
workflows:
|
|
57
|
+
name: Workflow checks
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
timeout-minutes: 10
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
62
|
+
with:
|
|
63
|
+
persist-credentials: false
|
|
64
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
65
|
+
with:
|
|
66
|
+
version: "0.12.18"
|
|
67
|
+
enable-cache: false
|
|
68
|
+
- name: Check workflow syntax
|
|
69
|
+
env:
|
|
70
|
+
ACTIONLINT_SHA256: 8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8
|
|
71
|
+
run: |
|
|
72
|
+
curl --fail --silent --show-error --location \
|
|
73
|
+
https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz \
|
|
74
|
+
--output "$RUNNER_TEMP/actionlint.tar.gz"
|
|
75
|
+
printf '%s %s\n' "$ACTIONLINT_SHA256" "$RUNNER_TEMP/actionlint.tar.gz" | sha256sum --check
|
|
76
|
+
tar -xzf "$RUNNER_TEMP/actionlint.tar.gz" -C "$RUNNER_TEMP" actionlint
|
|
77
|
+
"$RUNNER_TEMP/actionlint" -color
|
|
78
|
+
- name: Check workflow security
|
|
79
|
+
run: uvx zizmor==1.30.1 --offline --pedantic .github/workflows
|
|
80
|
+
|
|
81
|
+
secrets:
|
|
82
|
+
name: Secret scanning
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
timeout-minutes: 10
|
|
85
|
+
steps:
|
|
86
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
87
|
+
with:
|
|
88
|
+
persist-credentials: false
|
|
89
|
+
fetch-depth: 0
|
|
90
|
+
- name: Scan repository history
|
|
91
|
+
env:
|
|
92
|
+
GITLEAKS_SHA256: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb
|
|
93
|
+
run: |
|
|
94
|
+
curl --fail --silent --show-error --location \
|
|
95
|
+
https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz \
|
|
96
|
+
--output "$RUNNER_TEMP/gitleaks.tar.gz"
|
|
97
|
+
printf '%s %s\n' "$GITLEAKS_SHA256" "$RUNNER_TEMP/gitleaks.tar.gz" | sha256sum --check
|
|
98
|
+
tar -xzf "$RUNNER_TEMP/gitleaks.tar.gz" -C "$RUNNER_TEMP" gitleaks
|
|
99
|
+
"$RUNNER_TEMP/gitleaks" git --redact --no-banner --log-opts="--all" .
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Pushing a vX.Y.Z tag publishes a GitHub release whose notes are that version's CHANGELOG.md entry, then uploads
|
|
4
|
+
# the package to PyPI through trusted publishing. The tag must be on main, where CI has already checked the commit.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*.*.*"]
|
|
8
|
+
|
|
9
|
+
permissions: {}
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: false
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
release:
|
|
17
|
+
name: GitHub release
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 10
|
|
20
|
+
permissions:
|
|
21
|
+
contents: write # create the release
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
24
|
+
with:
|
|
25
|
+
fetch-depth: 0
|
|
26
|
+
persist-credentials: false
|
|
27
|
+
|
|
28
|
+
- name: Check the tag is on main and matches both versions
|
|
29
|
+
run: |
|
|
30
|
+
set -euo pipefail
|
|
31
|
+
git merge-base --is-ancestor "$GITHUB_SHA" origin/main
|
|
32
|
+
version=$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')
|
|
33
|
+
plugin=$(jq -r .version .claude-plugin/plugin.json)
|
|
34
|
+
if [ "v$version" != "$GITHUB_REF_NAME" ] || [ "$plugin" != "$version" ]; then
|
|
35
|
+
echo "::error::tag $GITHUB_REF_NAME, pyproject.toml $version and plugin.json $plugin must match"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
echo "VERSION=$version" >> "$GITHUB_ENV"
|
|
39
|
+
|
|
40
|
+
- name: GitHub release
|
|
41
|
+
env:
|
|
42
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
43
|
+
run: |
|
|
44
|
+
set -euo pipefail
|
|
45
|
+
python3 scripts/changelog.py "$VERSION" > "$RUNNER_TEMP/notes.md"
|
|
46
|
+
# A rerun finds the release already there and leaves it.
|
|
47
|
+
if ! gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
|
|
48
|
+
gh release create "$GITHUB_REF_NAME" --verify-tag --title "$GITHUB_REF_NAME" --notes-file "$RUNNER_TEMP/notes.md"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
pypi:
|
|
52
|
+
name: PyPI
|
|
53
|
+
needs: release
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
timeout-minutes: 10
|
|
56
|
+
environment:
|
|
57
|
+
name: pypi
|
|
58
|
+
url: https://pypi.org/project/deskpilot/
|
|
59
|
+
permissions:
|
|
60
|
+
id-token: write # trusted publishing
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
63
|
+
with:
|
|
64
|
+
persist-credentials: false
|
|
65
|
+
|
|
66
|
+
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
67
|
+
with:
|
|
68
|
+
version: "0.12.18"
|
|
69
|
+
enable-cache: false
|
|
70
|
+
|
|
71
|
+
- name: Build
|
|
72
|
+
run: uv build
|
|
73
|
+
|
|
74
|
+
- name: Publish
|
|
75
|
+
run: uv publish --trusted-publishing always
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Mergepatrol reads this file from the default branch. The dashboard opens a PR to change it.
|
|
2
|
+
version: 1
|
|
3
|
+
label: mergepatrol
|
|
4
|
+
batch_size: 4
|
|
5
|
+
resolver:
|
|
6
|
+
provider: anthropic
|
|
7
|
+
model: claude-opus-5-5
|
|
8
|
+
effort: medium
|
|
9
|
+
# Run in Mergepatrol's sandbox after an agent resolves a conflict or commits a fix, before anything is pushed.
|
|
10
|
+
# They mirror CI's lint and test jobs. The job image has no uv, so the first command installs the version CI uses.
|
|
11
|
+
verify:
|
|
12
|
+
- curl -LsSf https://astral.sh/uv/0.12.18/install.sh | sh
|
|
13
|
+
- ~/.local/bin/uv sync --locked
|
|
14
|
+
- ~/.local/bin/uv run --no-sync ruff check .
|
|
15
|
+
- ~/.local/bin/uv run --no-sync ruff format --check .
|
|
16
|
+
- ~/.local/bin/uv run --no-sync ty check
|
|
17
|
+
- ~/.local/bin/uv run --no-sync pytest -q
|
|
18
|
+
fix_ci:
|
|
19
|
+
enabled: true
|
|
20
|
+
attempts: 2
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# deskpilot
|
|
2
|
+
|
|
3
|
+
Desktop control for agents on Hyprland: screenshot a window, click or type at a point in it, and leave the user's
|
|
4
|
+
own pointer and keyboard focus where they were. The CLI and the MCP server are thin layers over the same `Desk`.
|
|
5
|
+
|
|
6
|
+
## Checks
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
uv sync --locked
|
|
10
|
+
uv run ruff check . && uv run ruff format --check .
|
|
11
|
+
uv run ty check
|
|
12
|
+
uv run pytest
|
|
13
|
+
python3 scripts/changelog.py --check
|
|
14
|
+
claude plugin validate --strict . # if the Claude Code CLI is installed
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
CI also runs actionlint, zizmor and gitleaks. The plugin is not built in CI: it needs the exact Hyprland it loads
|
|
18
|
+
into, so check a change to `plugin/deskpilot.cpp` with `deskpilot plugin load` on a real session.
|
|
19
|
+
|
|
20
|
+
## Layout
|
|
21
|
+
|
|
22
|
+
- `src/deskpilot/cli.py`: argument parsing and output; every command calls `Desk`
|
|
23
|
+
- `src/deskpilot/mcp_server.py`: the same actions as MCP tools over stdio, one `Desk` for the session
|
|
24
|
+
- `src/deskpilot/desk.py`: the actions an agent takes, shared by the CLI and the MCP server
|
|
25
|
+
- `src/deskpilot/hypr.py`: hyprctl: windows, monitors, focus, the cursor, and picking the live instance
|
|
26
|
+
- `src/deskpilot/plugin.py`: builds, loads and talks to the compositor plugin
|
|
27
|
+
- `src/deskpilot/plugin/deskpilot.cpp`: the Hyprland plugin that runs each click, scroll and keystroke inside the
|
|
28
|
+
compositor and puts the user's pointer and focus back
|
|
29
|
+
- `src/deskpilot/screen.py`: grim toplevel screenshots, shot ids, and the mapping from shot pixels back to the screen
|
|
30
|
+
- `src/deskpilot/overlay.py`: the agent cursor, drawn by `cua-driver`
|
|
31
|
+
- `src/deskpilot/uinput.py`: the virtual mouse and keyboard behind `--take-pointer` and `--take-focus`
|
|
32
|
+
- `src/deskpilot/keys.py`: key names, chords and US-layout text to Linux key codes
|
|
33
|
+
|
|
34
|
+
## Rules
|
|
35
|
+
|
|
36
|
+
- The user's pointer and focus are theirs. Only `--take-pointer`, `--take-focus` and `--show` may move them; every
|
|
37
|
+
other action goes through the plugin, which must restore both and replay input the user made meanwhile. A window
|
|
38
|
+
that is not on screen is an error, never a reason to focus it.
|
|
39
|
+
- A point is resolved against a recorded shot. A shot is of one window at one size; any other use is refused.
|
|
40
|
+
- A plugin build loads only into the Hyprland it was built against. Builds are named after the Hyprland commit and
|
|
41
|
+
the plugin source hash, so a changed source or an upgrade rebuilds rather than reusing a stale build.
|
|
42
|
+
- Each dispatcher tries Hyprland 0.57's Lua form first and falls back to the string form. Keep both working.
|
|
43
|
+
- A window name that matches several windows is an error listing them, and a point outside the window or under
|
|
44
|
+
another window is refused. Never guess a target.
|
|
45
|
+
- The agent cursor is feedback, never input: any overlay failure turns it off and the action goes ahead.
|
|
46
|
+
- Text is typed on a US layout. A character it cannot type is an error, never skipped.
|
|
47
|
+
- Tests never touch the real compositor, `/dev/uinput`, `grim` or `cua-driver`. Pass fakes into `Desk` and
|
|
48
|
+
`Hyprland`, as `tests/test_core.py` does.
|
|
49
|
+
- Every user-visible change gets a line under `## [Unreleased]` in `CHANGELOG.md`, and a command or flag change
|
|
50
|
+
updates `README.md` and `skills/deskpilot/SKILL.md` too.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
What changed in each release, in the terms someone using deskpilot would notice. Dates are UTC.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the versions follow
|
|
6
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). Each version's entry is also its GitHub release notes.
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.2.0] - 2026-11-10
|
|
11
|
+
|
|
12
|
+
- **Install from PyPI.** Each release is published to PyPI, so `uv tool install deskpilot` installs it.
|
|
13
|
+
- **Windows off screen are refused, not focused.** `shot`, `click`, `move` and `scroll` on a window on a hidden
|
|
14
|
+
workspace or scrolled off its monitor used to focus it, switching your workspace. They now refuse it, and
|
|
15
|
+
`--show` brings it into view on request. `windows` reports `visible` for each window.
|
|
16
|
+
- **Screenshots of the window itself.** `shot` captures the window by its toplevel, so a window on top no longer
|
|
17
|
+
shows in the image, and reads its scale from the image, so scaled outputs map clicks correctly.
|
|
18
|
+
- **Shot ids.** Each `shot` has an id; `--shot ID` (or `shot` over MCP) ties a click to that image, and a window
|
|
19
|
+
resized since is refused instead of clicked in the wrong place. The MCP `shot` tool returns the id with the image.
|
|
20
|
+
- **MCP errors say why.** A refused or failed action over MCP returned only `Error executing tool <name>`; it now
|
|
21
|
+
returns the reason, such as the window covering the point or the names an ambiguous window matched.
|
|
22
|
+
- **The plugin loads itself.** The first action after Hyprland restarts or upgrades builds and loads the plugin.
|
|
23
|
+
- **Clicks keep your keyboard focus.** A click or scroll no longer focuses or raises the target window while it runs,
|
|
24
|
+
so keys you type meanwhile stay in your own window. An XWayland app that asks to be activated when clicked is not
|
|
25
|
+
given focus while deskpilot acts on it.
|
|
26
|
+
- **Agents can act at the same time.** Clicks, scrolls and keys from several callers queue and run in turn instead of
|
|
27
|
+
failing with `busy`. Up to 32 wait at once; past that an action is refused with a clear error.
|
|
28
|
+
- **Typed text arrives whole.** `deskpilot type` and the MCP `type_text` send the text as one action, so another
|
|
29
|
+
agent's keys never land in the middle of it, and long text works. `--delay` now sets how long each key is held as
|
|
30
|
+
well as the gap before the next.
|
|
31
|
+
- **Typing leaves your window alone.** Keys go only to the target app, so the window you are using no longer sees
|
|
32
|
+
its keyboard focus leave and return, and its pointer no longer flickers while an agent types.
|
|
33
|
+
- **Scrolling ignores your compositor settings.** A scroll reaches the app as plain wheel notches, without your
|
|
34
|
+
Hyprland mouse binds or scroll factors acting on it.
|
|
35
|
+
- **Clicks check what is on top before every press.** Each press and wheel notch asks Hyprland what is at the point,
|
|
36
|
+
so a window, bar or popup that appears mid-click is refused instead of clicked.
|
|
37
|
+
|
|
38
|
+
## [0.1.0] - 2026-11-09
|
|
39
|
+
|
|
40
|
+
- **Clicks that leave your pointer alone.** `deskpilot click`, `scroll` and keys sent with `--window` run inside
|
|
41
|
+
Hyprland through the deskpilot plugin: your pointer and keyboard focus go back where they were, and any mouse
|
|
42
|
+
motion you made meanwhile is applied. Native Wayland, XWayland and Wine windows all receive a real click.
|
|
43
|
+
- **Screenshot coordinates.** `deskpilot shot` saves a window's screenshot and remembers its scale, so `click`,
|
|
44
|
+
`move` and `scroll` take a point straight from the image. A point outside the window, or under another window, is
|
|
45
|
+
refused.
|
|
46
|
+
- **No guessed windows.** A window is named by address, class, or part of its class or title, and a name that
|
|
47
|
+
matches several windows is an error listing them.
|
|
48
|
+
- **Typing.** `deskpilot type` and `deskpilot key` type US-layout text and chords. `--delay` spaces keys for games,
|
|
49
|
+
which read input once per frame.
|
|
50
|
+
- **Taking over when an app needs it.** `--take-pointer` and `--take-focus` move your own pointer or focus and press
|
|
51
|
+
through a virtual input device, for a hover the app must see or an app that ignores keys while unfocused.
|
|
52
|
+
- **An agent cursor.** With `cua-driver` installed, an overlay arrow glides to each point deskpilot acts on.
|
|
53
|
+
- **Plugin management.** `deskpilot plugin load` builds the plugin for the running Hyprland and loads it; run it
|
|
54
|
+
again after each Hyprland upgrade.
|
|
55
|
+
- **MCP server.** `deskpilot mcp` serves the same actions over stdio, keeping one input device and one agent cursor
|
|
56
|
+
open for the session.
|
|
57
|
+
- **Works with Hyprland 0.57 and older.** Each dispatcher tries the Lua form first and falls back to the string
|
|
58
|
+
form, and a shell with a stale `HYPRLAND_INSTANCE_SIGNATURE` reaches the one live Hyprland.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Bug reports and pull requests are welcome. [AGENTS.md](AGENTS.md) has the layout and the rules a change must keep.
|
|
4
|
+
|
|
5
|
+
## Before you start
|
|
6
|
+
|
|
7
|
+
- **Bugs:** open an issue with the command you ran, the target window from `deskpilot --json windows`, your
|
|
8
|
+
Hyprland version and `deskpilot plugin status`. A click or key that reached the wrong window is the most serious
|
|
9
|
+
kind of bug, so say so in the title.
|
|
10
|
+
- **New actions and larger changes:** open an issue first, so we can agree how the user's pointer and focus are
|
|
11
|
+
kept.
|
|
12
|
+
- **Security issues:** report them privately, as [SECURITY.md](SECURITY.md) describes.
|
|
13
|
+
|
|
14
|
+
## Making a change
|
|
15
|
+
|
|
16
|
+
Run the checks in [AGENTS.md](AGENTS.md#checks). A change needs:
|
|
17
|
+
|
|
18
|
+
- passing checks
|
|
19
|
+
- a test for each new refusal, including the case that must refuse
|
|
20
|
+
- a line under Unreleased in `CHANGELOG.md` if a user would notice it
|
|
21
|
+
- matching updates to `README.md` and `skills/deskpilot/SKILL.md` if a command or flag changed
|
|
22
|
+
|
|
23
|
+
Tests must never touch the real desktop. Pass fakes into `Desk` and `Hyprland` as `tests/test_core.py` does. A change
|
|
24
|
+
to the plugin also needs a manual check on a running Hyprland, with the clients you tried named in the PR.
|
|
25
|
+
|
|
26
|
+
Keep PRs to one idea and use [Conventional Commits](https://www.conventionalcommits.org/) for titles.
|
|
27
|
+
|
|
28
|
+
## Releasing
|
|
29
|
+
|
|
30
|
+
Bump `version` in `pyproject.toml` and `.claude-plugin/plugin.json`, run `uv lock`, move the Unreleased lines under
|
|
31
|
+
a new `## [X.Y.Z] - YYYY-MM-DD` heading, and merge. Then tag the merged commit `vX.Y.Z` and push the tag. The release
|
|
32
|
+
workflow checks the tag is on main and matches both versions, and publishes a GitHub release whose notes are that
|
|
33
|
+
version's changelog entry.
|
deskpilot-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cillian Berragan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
deskpilot-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: deskpilot
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Desktop control for agents on Hyprland: window-relative clicks that leave the user's pointer alone, and typing
|
|
5
|
+
Project-URL: Homepage, https://github.com/cjber/deskpilot
|
|
6
|
+
Project-URL: Repository, https://github.com/cjber/deskpilot
|
|
7
|
+
Project-URL: Issues, https://github.com/cjber/deskpilot/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/cjber/deskpilot/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Cillian Berragan <cjberragan@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
|
+
Requires-Dist: evdev>=1.7
|
|
14
|
+
Requires-Dist: mcp>=2.3
|
|
15
|
+
Requires-Dist: pillow>=10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# deskpilot
|
|
19
|
+
|
|
20
|
+
Desktop control for agents on Hyprland. An agent takes a screenshot of a window, picks a point in it, and clicks
|
|
21
|
+
or types there, in any app: native Wayland, XWayland and Wine programs such as the Battle.net launcher and games.
|
|
22
|
+
|
|
23
|
+
The user keeps their own pointer and keyboard focus. Clicks, scrolls and keys run inside the compositor through
|
|
24
|
+
the deskpilot Hyprland plugin: in one step it holds the user's mouse input, delivers the event to the target window
|
|
25
|
+
through the user's own seat, then puts the pointer back where it was, applying any mouse motion the user made
|
|
26
|
+
meanwhile. Only pointer focus moves, so keyboard focus stays on the user's window and the target is not raised, even
|
|
27
|
+
with `focus_on_activate` set. Wayland, XWayland and Wine clients all receive the press as a real click. The plugin refuses
|
|
28
|
+
a click when another window covers the point.
|
|
29
|
+
|
|
30
|
+
An agent cursor shows where deskpilot acts: [cua-driver](https://github.com/trycua/cua)'s overlay arrow glides to
|
|
31
|
+
each point, drawn above every window and separate from the user's pointer. Without `cua-driver` on `PATH`, or with
|
|
32
|
+
`DESKPILOT_CURSOR=off`, actions run without it.
|
|
33
|
+
|
|
34
|
+
Keys sent to a window (`--window`) go to that window only, with each stroke complete before focus returns, and
|
|
35
|
+
the user's modifiers never leak into them. X11 apps take keys only from the window with X input focus, so an
|
|
36
|
+
XWayland target gets X focus for each stroke and sees a focus-in and focus-out around it; Hyprland's own focus
|
|
37
|
+
does not move.
|
|
38
|
+
|
|
39
|
+
`--take-pointer` and `--take-focus` use a virtual input device made with `/dev/uinput` instead, which the
|
|
40
|
+
compositor treats as real hardware. Tools that synthesize input another way, such as `xdotool` (XTest) or `wtype`
|
|
41
|
+
(the virtual keyboard protocol), are ignored by some clients: Battle.net drops `xdotool` clicks, and a game drops
|
|
42
|
+
`wtype` keys.
|
|
43
|
+
|
|
44
|
+
## Requirements
|
|
45
|
+
|
|
46
|
+
- Hyprland, with `hyprctl` on `PATH`. Version 0.57's Lua dispatchers and the older string ones both work.
|
|
47
|
+
- The plugin build tools: a C++26 compiler, `make`, Hyprland's headers (`pkg-config hyprland`, shipped with
|
|
48
|
+
Arch's `hyprland` package), `libxcb` and `libxkbcommon`.
|
|
49
|
+
- Optional: `cua-driver` for the agent cursor.
|
|
50
|
+
- `grim` for screenshots.
|
|
51
|
+
- For `--take-pointer`, `--take-focus` and keys without `--window`: write access to `/dev/uinput`, usually through
|
|
52
|
+
the `input` group or a udev ACL. Check with `test -w /dev/uinput`.
|
|
53
|
+
- Python 3.12 or later and `uv`.
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
uv tool install deskpilot
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The agent skill, which teaches an agent the steps below, installs in Claude Code with:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
/plugin install deskpilot --marketplace cjber/deskpilot
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Use
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
deskpilot windows # every window: address, class, title, geometry, whether on screen
|
|
71
|
+
deskpilot shot battle.net.exe # screenshot; prints its id, image path and scale
|
|
72
|
+
deskpilot click battle.net.exe 920 367 --shot ID # a point in that screenshot
|
|
73
|
+
deskpilot type "/reload" --window wow.exe --delay 0.12
|
|
74
|
+
deskpilot key ctrl+a --window kitty
|
|
75
|
+
deskpilot wait-for wow.exe --timeout 120
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
A window is named by its address, its class, or a substring of its class or title. A name that matches more
|
|
79
|
+
than one window is an error listing them, so a click never lands on a guessed window.
|
|
80
|
+
|
|
81
|
+
`shot` captures the window itself, so a window on top does not show in it, and saves it under an id. `move`,
|
|
82
|
+
`click` and `scroll` take pixels in the shot named by `--shot`, or the window's latest one, whatever scale it was
|
|
83
|
+
taken at. A window resized since that shot is refused, as is a point outside the window. Pass `--scale 1` to give
|
|
84
|
+
window pixels instead.
|
|
85
|
+
|
|
86
|
+
A window on a hidden workspace or scrolled off its monitor is reported as `"visible": false`, and `shot`, `move`,
|
|
87
|
+
`click` and `scroll` refuse it rather than move the user's view. `--show` focuses it to bring it on screen first.
|
|
88
|
+
|
|
89
|
+
The first action that needs the plugin builds and loads it for the running Hyprland, so it comes back by itself
|
|
90
|
+
after Hyprland restarts or upgrades. `deskpilot plugin load` does the same ahead of time.
|
|
91
|
+
|
|
92
|
+
`move` only shows the agent cursor. Pass `--take-pointer` to `move`, `click` or `scroll` to move the user's own
|
|
93
|
+
pointer there and press through the input device instead, for a hover the app must see.
|
|
94
|
+
|
|
95
|
+
A shell started before Hyprland restarted keeps a stale `HYPRLAND_INSTANCE_SIGNATURE`. deskpilot then talks to
|
|
96
|
+
the one instance whose socket answers, and with several live instances it stops and names them.
|
|
97
|
+
|
|
98
|
+
`key` and `type` without `--window` press keys into whatever has focus. `--take-focus` focuses the window and
|
|
99
|
+
leaves it focused, for an app that ignores keys while unfocused.
|
|
100
|
+
|
|
101
|
+
Games read keys once per frame, so type into them with `--delay 0.1` or more: each key is held that long, with the
|
|
102
|
+
same gap before the next.
|
|
103
|
+
|
|
104
|
+
Clicks, scrolls and keys from several agents at once run one after another in the order they arrive, so one `type`
|
|
105
|
+
is never interleaved with another's keys. Up to 32 wait behind the running one; past that an action is refused.
|
|
106
|
+
|
|
107
|
+
## MCP
|
|
108
|
+
|
|
109
|
+
`deskpilot mcp` serves the same actions over stdio: `windows`, `focus`, `shot` (returns the image and its shot
|
|
110
|
+
id), `click`, `move`, `scroll`, `key`, `type_text` and `wait_for`. `click`, `move` and `scroll` take `shot`, `show`
|
|
111
|
+
and `take_pointer`; `key` and `type_text` take `take_focus`. It keeps one input
|
|
112
|
+
device and one agent cursor open for the session.
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
claude mcp add deskpilot -- deskpilot mcp
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Limits
|
|
119
|
+
|
|
120
|
+
- Text is typed on a US layout; a character that layout cannot type is an error rather than skipped.
|
|
121
|
+
- Hyprland plugins load only into the exact Hyprland they were built against, so `deskpilot plugin load` builds
|
|
122
|
+
one per Hyprland commit and plugin source under `~/.local/share/deskpilot`, and replaces a loaded build of
|
|
123
|
+
other source.
|
|
124
|
+
- Each CLI action that uses the input device opens a new one and waits about a second for libinput to add it. The MCP server
|
|
125
|
+
avoids that wait.
|
|
126
|
+
- Hyprland only, Linux only.
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
[AGENTS.md](AGENTS.md) has the checks, the layout and the rules a change must keep, and
|
|
131
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) how to report a bug or send a change.
|