quickshell-mcp 2.2.0__tar.gz → 2.4.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.
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/PKG-INFO +1 -1
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/pyproject.toml +1 -1
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/capabilities/assistant.py +2 -0
- quickshell_mcp-2.4.0/quickshell_mcp/capabilities/inspection.py +38 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/capabilities/registry.py +17 -2
- quickshell_mcp-2.4.0/quickshell_mcp/capabilities/runtime.py +35 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/server.py +208 -0
- quickshell_mcp-2.4.0/quickshell_mcp/sources/runtime_profile.py +81 -0
- quickshell_mcp-2.4.0/quickshell_mcp/sources/runtime_session.py +287 -0
- quickshell_mcp-2.4.0/quickshell_mcp/sources/ui_runtime.py +277 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/scripts/smoke_test.py +15 -0
- quickshell_mcp-2.4.0/tests/fixtures/fake_qs.sh +13 -0
- quickshell_mcp-2.4.0/tests/fixtures/runtime-shell/main.qml +48 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_capabilities.py +19 -1
- quickshell_mcp-2.4.0/tests/test_runtime.py +201 -0
- quickshell_mcp-2.4.0/tests/test_ui_runtime.py +170 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/.coderabbit.yaml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/.github/ISSUE_TEMPLATE/config_help.yml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/.github/pull_request_template.md +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/.github/workflows/ci.yml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/.github/workflows/semantic-release.yml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/.gitignore +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/.releaserc.js +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/AGENTS.md +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/CLAUDE.md +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/CODE_OF_CONDUCT.md +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/CONTRIBUTING.md +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/Dockerfile +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/LICENSE +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/README.md +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/flake.lock +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/flake.nix +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/__init__.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/__main__.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/caches.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/capabilities/__init__.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/capabilities/debugging.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/capabilities/generation.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/capabilities/knowledge.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/capabilities/migration.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/capabilities/project.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/capabilities/validation.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/config.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/extraction.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/__init__.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/assistant.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/compat.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/docs.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/examples.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/explain_error.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/find_pattern.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/generate.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/implementations.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/migrate.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/project.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/project_intel.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/project_validate.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/qt_docs.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/refactor.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/search_all.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/style_match.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/sources/validate.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/utils.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/quickshell_mcp/versions.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/server.json +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/conftest.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/about.html +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/example_readme.md +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/examples_contents_root.json +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/examples_repo_info.json +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/guide_index.html +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/hyprland_monitor.html +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/impl_caelestia_anchoranim.qml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/impl_caelestia_repo_info.json +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/impl_caelestia_tree.json +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/impl_dots_audio.qml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/impl_dots_repo_info.json +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/impl_dots_tree.json +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/impl_noctalia_nbutton.qml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/impl_noctalia_tree.json +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/projects/medium/components/ClockWidget.qml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/projects/medium/components/VolumeWidget.qml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/projects/medium/config.toml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/projects/medium/main.qml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/projects/medium/services/VolumeService.qml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/projects/small/config.json +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/projects/small/main.qml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/projects/small/util.js +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/projects/small/widgets/VolumeWidget.qml +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/qml_language.html +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/qt_qml_qtquick_rectangle.html +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/qt_qtquick_controls_qmlmodule.html +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/fixtures/qt_qtquick_qmlmodule.html +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_assistant.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_code_quality.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_compat.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_content_search.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_diagnostics.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_disk_cache.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_errors_retry.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_examples.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_explain_error.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_extraction.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_find_pattern.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_generate.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_implementations.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_index_search.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_live.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_migrate.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_project.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_project_generate.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_project_intel.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_project_validate.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_qt_docs.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_search_all.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_validate.py +0 -0
- {quickshell_mcp-2.2.0 → quickshell_mcp-2.4.0}/tests/test_versions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quickshell-mcp
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: MCP server that fetches live Quickshell docs from quickshell.org so the model reads real, current docs instead of hallucinating.
|
|
5
5
|
Project-URL: Homepage, https://github.com/franklinnolasco7/quickshell-mcp
|
|
6
6
|
Project-URL: Repository, https://github.com/franklinnolasco7/quickshell-mcp
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "quickshell-mcp"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.4.0"
|
|
4
4
|
description = "MCP server that fetches live Quickshell docs from quickshell.org so the model reads real, current docs instead of hallucinating."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""The inspection capability: read and drive the live UI of a managed runtime
|
|
2
|
+
session.
|
|
3
|
+
|
|
4
|
+
Read-only by default (windows, screenshots, ui_tree, ui_find, ui_get_property,
|
|
5
|
+
screenshot_diff). ui_set_property and ui_invoke are mutating; ui_eval is
|
|
6
|
+
high-risk. All operate on a managed runtime session, never on unrelated
|
|
7
|
+
user processes.
|
|
8
|
+
|
|
9
|
+
Depends on: knowledge, project, runtime.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from ..sources.ui_runtime import ( # noqa: F401
|
|
15
|
+
_screenshot,
|
|
16
|
+
_screenshot_diff,
|
|
17
|
+
_ui_eval,
|
|
18
|
+
_ui_find,
|
|
19
|
+
_ui_get_property,
|
|
20
|
+
_ui_invoke,
|
|
21
|
+
_ui_set_property,
|
|
22
|
+
_ui_tree,
|
|
23
|
+
_ui_windows,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
CAPABILITY_NAME = "inspection"
|
|
27
|
+
CAPABILITY_TOOLS = (
|
|
28
|
+
"quickshell_windows",
|
|
29
|
+
"quickshell_screenshot",
|
|
30
|
+
"quickshell_screenshot_diff",
|
|
31
|
+
"quickshell_ui_tree",
|
|
32
|
+
"quickshell_ui_find",
|
|
33
|
+
"quickshell_ui_get_property",
|
|
34
|
+
"quickshell_ui_set_property",
|
|
35
|
+
"quickshell_ui_invoke",
|
|
36
|
+
"quickshell_ui_eval",
|
|
37
|
+
)
|
|
38
|
+
CAPABILITY_DEPENDS_ON = ("knowledge", "project", "runtime")
|
|
@@ -25,7 +25,17 @@ from __future__ import annotations
|
|
|
25
25
|
|
|
26
26
|
from dataclasses import dataclass
|
|
27
27
|
|
|
28
|
-
from . import
|
|
28
|
+
from . import (
|
|
29
|
+
assistant,
|
|
30
|
+
debugging,
|
|
31
|
+
generation,
|
|
32
|
+
inspection,
|
|
33
|
+
knowledge,
|
|
34
|
+
migration,
|
|
35
|
+
project,
|
|
36
|
+
runtime,
|
|
37
|
+
validation,
|
|
38
|
+
)
|
|
29
39
|
|
|
30
40
|
__all__ = [
|
|
31
41
|
"ALL_CAPABILITIES",
|
|
@@ -58,6 +68,9 @@ _MUTATING_CAPABILITIES = ("runtime", "testing")
|
|
|
58
68
|
# capability's default safety level.
|
|
59
69
|
_TOOL_SAFETY_LEVELS: dict[str, str] = {
|
|
60
70
|
"quickshell_apply_patch": "mutating",
|
|
71
|
+
"quickshell_ui_set_property": "mutating",
|
|
72
|
+
"quickshell_ui_invoke": "mutating",
|
|
73
|
+
"quickshell_ui_eval": "high-risk",
|
|
61
74
|
}
|
|
62
75
|
|
|
63
76
|
|
|
@@ -72,6 +85,8 @@ _CAPABILITY_MODULES = (
|
|
|
72
85
|
migration,
|
|
73
86
|
debugging,
|
|
74
87
|
project,
|
|
88
|
+
runtime,
|
|
89
|
+
inspection,
|
|
75
90
|
assistant,
|
|
76
91
|
)
|
|
77
92
|
|
|
@@ -93,7 +108,7 @@ PLANNED_CAPABILITIES: dict[str, Capability] = {
|
|
|
93
108
|
status="planned",
|
|
94
109
|
safety_level=_safety_level(name),
|
|
95
110
|
)
|
|
96
|
-
for name in ("
|
|
111
|
+
for name in ("testing", "performance")
|
|
97
112
|
}
|
|
98
113
|
|
|
99
114
|
ALL_CAPABILITIES: dict[str, Capability] = {**CAPABILITIES, **PLANNED_CAPABILITIES}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""The runtime capability: manage isolated Quickshell runtime sessions.
|
|
2
|
+
|
|
3
|
+
Sessions are launched from explicit runtime profiles with isolated XDG dirs,
|
|
4
|
+
tracked in a registry, and inspectable via status, logs, and ping. The
|
|
5
|
+
capability is mutating: starting, stopping, and resetting sessions alter
|
|
6
|
+
process state and must be explicitly invoked.
|
|
7
|
+
|
|
8
|
+
Depends on: knowledge, project (runtime tools use project detection and
|
|
9
|
+
documentation for diagnostics).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from ..sources.runtime_profile import _RuntimeProfile # noqa: F401
|
|
15
|
+
from ..sources.runtime_session import ( # noqa: F401
|
|
16
|
+
_SESSION_REGISTRY,
|
|
17
|
+
_logs,
|
|
18
|
+
_ping,
|
|
19
|
+
_qs_binary,
|
|
20
|
+
_reset_session,
|
|
21
|
+
_start_session,
|
|
22
|
+
_status_session,
|
|
23
|
+
_stop_session,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
CAPABILITY_NAME = "runtime"
|
|
27
|
+
CAPABILITY_TOOLS = (
|
|
28
|
+
"quickshell_runtime_start",
|
|
29
|
+
"quickshell_runtime_stop",
|
|
30
|
+
"quickshell_runtime_reset",
|
|
31
|
+
"quickshell_runtime_status",
|
|
32
|
+
"quickshell_runtime_logs",
|
|
33
|
+
"quickshell_runtime_ping",
|
|
34
|
+
)
|
|
35
|
+
CAPABILITY_DEPENDS_ON = ("knowledge", "project")
|
|
@@ -61,6 +61,17 @@ from .capabilities.generation import ( # noqa: F401
|
|
|
61
61
|
_refactor,
|
|
62
62
|
_style_match,
|
|
63
63
|
)
|
|
64
|
+
from .capabilities.inspection import ( # noqa: F401
|
|
65
|
+
_screenshot,
|
|
66
|
+
_screenshot_diff,
|
|
67
|
+
_ui_eval,
|
|
68
|
+
_ui_find,
|
|
69
|
+
_ui_get_property,
|
|
70
|
+
_ui_invoke,
|
|
71
|
+
_ui_set_property,
|
|
72
|
+
_ui_tree,
|
|
73
|
+
_ui_windows,
|
|
74
|
+
)
|
|
64
75
|
from .capabilities.knowledge import ( # noqa: F401
|
|
65
76
|
_GITHUB_API,
|
|
66
77
|
_IMPL_QUERY_STOPWORDS,
|
|
@@ -117,6 +128,17 @@ from .capabilities.project import ( # noqa: F401
|
|
|
117
128
|
_search_project,
|
|
118
129
|
_validate_project,
|
|
119
130
|
)
|
|
131
|
+
from .capabilities.runtime import ( # noqa: F401
|
|
132
|
+
_SESSION_REGISTRY,
|
|
133
|
+
_logs,
|
|
134
|
+
_ping,
|
|
135
|
+
_qs_binary,
|
|
136
|
+
_reset_session,
|
|
137
|
+
_RuntimeProfile,
|
|
138
|
+
_start_session,
|
|
139
|
+
_status_session,
|
|
140
|
+
_stop_session,
|
|
141
|
+
)
|
|
120
142
|
from .capabilities.validation import ( # noqa: F401
|
|
121
143
|
_api_in_version,
|
|
122
144
|
_changelog_hits,
|
|
@@ -163,6 +185,14 @@ from .versions import ( # noqa: F401
|
|
|
163
185
|
mcp = FastMCP("quickshell-mcp")
|
|
164
186
|
|
|
165
187
|
|
|
188
|
+
def _require_session(session_id: str):
|
|
189
|
+
"""Look up a tracked runtime session, raising ValueError if unknown."""
|
|
190
|
+
session = _SESSION_REGISTRY.get(session_id)
|
|
191
|
+
if session is None:
|
|
192
|
+
raise ValueError(f"Unknown runtime session '{session_id}'")
|
|
193
|
+
return session
|
|
194
|
+
|
|
195
|
+
|
|
166
196
|
@mcp.tool()
|
|
167
197
|
def quickshell_list_versions(refresh: bool = False) -> dict:
|
|
168
198
|
"""List all Quickshell documentation versions currently published on
|
|
@@ -1016,6 +1046,184 @@ def quickshell_project_migrate(project: str, from_version: str, to_version: str)
|
|
|
1016
1046
|
return _migrate_project(project, from_version=from_version, to_version=to_version)
|
|
1017
1047
|
|
|
1018
1048
|
|
|
1049
|
+
@mcp.tool()
|
|
1050
|
+
def quickshell_runtime_start(
|
|
1051
|
+
project: str,
|
|
1052
|
+
entrypoint: str | None = None,
|
|
1053
|
+
config_dir: str | None = None,
|
|
1054
|
+
environment: dict[str, str] | None = None,
|
|
1055
|
+
compositor: str | None = None,
|
|
1056
|
+
arguments: list[str] | None = None,
|
|
1057
|
+
) -> dict:
|
|
1058
|
+
"""Start a managed, isolated Quickshell runtime session for a project.
|
|
1059
|
+
|
|
1060
|
+
Launches ``qs`` with isolated XDG directories so it never touches your
|
|
1061
|
+
real desktop session or other quickshell instances. Returns a session id
|
|
1062
|
+
and tracks the process for later status, logs, ping, stop, and reset.
|
|
1063
|
+
This is a mutating operation: it launches a process."""
|
|
1064
|
+
_record_tool("quickshell_runtime_start")
|
|
1065
|
+
profile = _RuntimeProfile(
|
|
1066
|
+
project_root=project,
|
|
1067
|
+
entrypoint=entrypoint,
|
|
1068
|
+
config_dir=config_dir,
|
|
1069
|
+
environment=environment or {},
|
|
1070
|
+
compositor=compositor,
|
|
1071
|
+
arguments=arguments or [],
|
|
1072
|
+
)
|
|
1073
|
+
return _start_session(profile).to_dict()
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
@mcp.tool()
|
|
1077
|
+
def quickshell_runtime_stop(session_id: str) -> dict:
|
|
1078
|
+
"""Stop a managed runtime session safely (SIGTERM, then SIGKILL on timeout).
|
|
1079
|
+
|
|
1080
|
+
Handles already-exited and orphaned processes; stops only the tracked
|
|
1081
|
+
session's process group, never unrelated user processes. Mutating."""
|
|
1082
|
+
_record_tool("quickshell_runtime_stop")
|
|
1083
|
+
session = _require_session(session_id)
|
|
1084
|
+
_stop_session(session)
|
|
1085
|
+
return session.to_dict()
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
@mcp.tool()
|
|
1089
|
+
def quickshell_runtime_reset(session_id: str) -> dict:
|
|
1090
|
+
"""Reset a managed runtime session to a clean state.
|
|
1091
|
+
|
|
1092
|
+
Stops the current session, cleans up its isolated temp dirs, and starts a
|
|
1093
|
+
fresh session with the same profile under a new session id. Mutating."""
|
|
1094
|
+
_record_tool("quickshell_runtime_reset")
|
|
1095
|
+
session = _require_session(session_id)
|
|
1096
|
+
fresh = _reset_session(session)
|
|
1097
|
+
return fresh.to_dict()
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
@mcp.tool()
|
|
1101
|
+
def quickshell_runtime_status(session_id: str) -> dict:
|
|
1102
|
+
"""Return structured status for a runtime session: session id, running
|
|
1103
|
+
state, PID, startup duration, exit code, and profile identity. Read-only."""
|
|
1104
|
+
_record_tool("quickshell_runtime_status")
|
|
1105
|
+
session = _require_session(session_id)
|
|
1106
|
+
return _status_session(session)
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
@mcp.tool()
|
|
1110
|
+
def quickshell_runtime_logs(
|
|
1111
|
+
session_id: str,
|
|
1112
|
+
stream: str | None = None,
|
|
1113
|
+
severity: str | None = None,
|
|
1114
|
+
text: str | None = None,
|
|
1115
|
+
limit: int = 200,
|
|
1116
|
+
) -> dict:
|
|
1117
|
+
"""Return structured logs from a runtime session with optional filtering
|
|
1118
|
+
by stream (stdout/stderr), text, and a bounded limit. Read-only."""
|
|
1119
|
+
_record_tool("quickshell_runtime_logs")
|
|
1120
|
+
session = _require_session(session_id)
|
|
1121
|
+
lines = _logs(session, stream=stream, severity=severity, text=text, limit=limit)
|
|
1122
|
+
return {"session_id": session_id, "logs": lines, "count": len(lines)}
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
@mcp.tool()
|
|
1126
|
+
def quickshell_runtime_ping(session_id: str) -> dict:
|
|
1127
|
+
"""Lightweight readiness/health check for a runtime session.
|
|
1128
|
+
|
|
1129
|
+
Distinguishes: process_running, exited (with exit code), or unhealthy.
|
|
1130
|
+
Fast, read-only."""
|
|
1131
|
+
_record_tool("quickshell_runtime_ping")
|
|
1132
|
+
session = _require_session(session_id)
|
|
1133
|
+
return _ping(session)
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
@mcp.tool()
|
|
1137
|
+
def quickshell_windows(session_id: str) -> dict:
|
|
1138
|
+
"""Enumerate windows/surfaces belonging to a managed Quickshell runtime
|
|
1139
|
+
session. Read-only. Requires a compositor adapter; when unavailable
|
|
1140
|
+
returns an empty list with an explanatory note (never fails)."""
|
|
1141
|
+
_record_tool("quickshell_windows")
|
|
1142
|
+
return _ui_windows(session_id)
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
@mcp.tool()
|
|
1146
|
+
def quickshell_screenshot(session_id: str) -> dict:
|
|
1147
|
+
"""Capture a screenshot from a managed Quickshell runtime session.
|
|
1148
|
+
|
|
1149
|
+
Requires the ``grim`` compositor screenshot tool on PATH. Returns the
|
|
1150
|
+
image path or an "unavailable" note. Read-only."""
|
|
1151
|
+
_record_tool("quickshell_screenshot")
|
|
1152
|
+
return _screenshot(session_id)
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
@mcp.tool()
|
|
1156
|
+
def quickshell_screenshot_diff(baseline: str, actual: str, output: str | None = None) -> dict:
|
|
1157
|
+
"""Compare two runtime screenshots with ImageMagick ``compare``.
|
|
1158
|
+
|
|
1159
|
+
Returns whether they differ, the diff image path, and a metric.
|
|
1160
|
+
Deterministic for CI. Read-only."""
|
|
1161
|
+
_record_tool("quickshell_screenshot_diff")
|
|
1162
|
+
return _screenshot_diff(baseline, actual, output=output)
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
@mcp.tool()
|
|
1166
|
+
def quickshell_ui_tree(session_id: str, depth: int = 3) -> dict:
|
|
1167
|
+
"""Inspect the live QML object tree of a managed runtime session.
|
|
1168
|
+
|
|
1169
|
+
Returns a compact, depth-limited hierarchy from the injected
|
|
1170
|
+
'inspector' IpcHandler target. Read-only."""
|
|
1171
|
+
_record_tool("quickshell_ui_tree")
|
|
1172
|
+
return _ui_tree(session_id, depth=depth)
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
@mcp.tool()
|
|
1176
|
+
def quickshell_ui_find(session_id: str, query: str) -> dict:
|
|
1177
|
+
"""Search the live QML object tree of a managed runtime session by name,
|
|
1178
|
+
type, text, or property. Returns stable references for later runtime
|
|
1179
|
+
tools. Read-only."""
|
|
1180
|
+
_record_tool("quickshell_ui_find")
|
|
1181
|
+
return _ui_find(session_id, query)
|
|
1182
|
+
|
|
1183
|
+
|
|
1184
|
+
@mcp.tool()
|
|
1185
|
+
def quickshell_ui_get_property(session_id: str, target: str, property_name: str) -> dict:
|
|
1186
|
+
"""Read a live QML property value from a managed runtime object via
|
|
1187
|
+
``qs ipc prop get``. Validates the object/property exist. Read-only."""
|
|
1188
|
+
_record_tool("quickshell_ui_get_property")
|
|
1189
|
+
return _ui_get_property(session_id, target, property_name)
|
|
1190
|
+
|
|
1191
|
+
|
|
1192
|
+
@mcp.tool()
|
|
1193
|
+
def quickshell_ui_set_property(
|
|
1194
|
+
session_id: str, target: str, property_name: str, value: str
|
|
1195
|
+
) -> dict:
|
|
1196
|
+
"""Set a live QML property on a managed runtime object. Mutating.
|
|
1197
|
+
|
|
1198
|
+
Requires an explicit runtime session, validates the property exists,
|
|
1199
|
+
returns the old and new values, and never modifies project files."""
|
|
1200
|
+
_record_tool("quickshell_ui_set_property")
|
|
1201
|
+
return _ui_set_property(session_id, target, property_name, value)
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
@mcp.tool()
|
|
1205
|
+
def quickshell_ui_invoke(
|
|
1206
|
+
session_id: str, target: str, method: str, arguments: list[str] | None = None
|
|
1207
|
+
) -> dict:
|
|
1208
|
+
"""Invoke a QML method on a managed runtime object via ``qs ipc call``.
|
|
1209
|
+
Mutating. Validates the method and arguments; enforces runtime session
|
|
1210
|
+
boundaries; no arbitrary process/system calls."""
|
|
1211
|
+
_record_tool("quickshell_ui_invoke")
|
|
1212
|
+
return _ui_invoke(session_id, target, method, arguments=arguments)
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
@mcp.tool()
|
|
1216
|
+
def quickshell_ui_eval(session_id: str, js_code: str, timeout: int = 5) -> dict:
|
|
1217
|
+
"""HIGH-RISK: evaluate QML/JavaScript in a managed runtime session.
|
|
1218
|
+
|
|
1219
|
+
Explicitly opt-in, session-scoped, with an execution timeout and output
|
|
1220
|
+
limits. No filesystem or process access is granted. Prefer
|
|
1221
|
+
quickshell_ui_get_property / quickshell_ui_invoke for controlled reads
|
|
1222
|
+
and calls."""
|
|
1223
|
+
_record_tool("quickshell_ui_eval")
|
|
1224
|
+
return _ui_eval(session_id, js_code, timeout=timeout)
|
|
1225
|
+
|
|
1226
|
+
|
|
1019
1227
|
@mcp.tool()
|
|
1020
1228
|
def quickshell_stats() -> dict:
|
|
1021
1229
|
"""Report session usage stats for this MCP server: per-tool call counts,
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Runtime profiles: explicit, inspectable launch configuration for managed
|
|
2
|
+
Quickshell runtime sessions.
|
|
3
|
+
|
|
4
|
+
A profile describes *how* to run a Quickshell project in isolation: the
|
|
5
|
+
project root, entrypoint, config directory, environment overrides, compositor
|
|
6
|
+
or backend, command-line arguments, and optional fixture data. Profiles are
|
|
7
|
+
pure data — nothing here launches processes.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class _RuntimeProfile:
|
|
18
|
+
"""Explicit launch configuration for a managed Quickshell session."""
|
|
19
|
+
|
|
20
|
+
project_root: str
|
|
21
|
+
entrypoint: str | None = None
|
|
22
|
+
config_dir: str | None = None
|
|
23
|
+
environment: dict[str, str] = field(default_factory=dict)
|
|
24
|
+
compositor: str | None = None
|
|
25
|
+
arguments: list[str] = field(default_factory=list)
|
|
26
|
+
fixture_data: dict[str, str] = field(default_factory=dict)
|
|
27
|
+
|
|
28
|
+
def resolved_entrypoint(self) -> str:
|
|
29
|
+
"""The QML entrypoint: the configured one, or the project's detected
|
|
30
|
+
entrypoint, or ``shell.qml`` / ``main.qml`` under the project root."""
|
|
31
|
+
if self.entrypoint:
|
|
32
|
+
return str(Path(self.entrypoint).expanduser().resolve())
|
|
33
|
+
root = Path(self.project_root).expanduser().resolve()
|
|
34
|
+
for name in ("shell.qml", "main.qml", "config.qml"):
|
|
35
|
+
candidate = root / name
|
|
36
|
+
if candidate.is_file():
|
|
37
|
+
return str(candidate)
|
|
38
|
+
raise ValueError(
|
|
39
|
+
f"No entrypoint found under {root}; pass entrypoint= or add shell.qml/main.qml."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def isolated_environment(self, instance_id: str) -> dict[str, str]:
|
|
43
|
+
"""Environment for launching the session: inherited variables plus
|
|
44
|
+
profile overrides and isolated XDG dirs for the instance."""
|
|
45
|
+
import os
|
|
46
|
+
import tempfile
|
|
47
|
+
|
|
48
|
+
base = dict(os.environ)
|
|
49
|
+
base.update(self.environment)
|
|
50
|
+
prefix = Path(tempfile.gettempdir()) / f"qs-mcp-{instance_id}"
|
|
51
|
+
# Isolate per-session state so a managed shell never touches the
|
|
52
|
+
# user's real XDG dirs or other quickshell instances.
|
|
53
|
+
base.update(
|
|
54
|
+
{
|
|
55
|
+
"QUICKSHELL_INSTANCE_ID": instance_id,
|
|
56
|
+
"XDG_CONFIG_HOME": str(prefix / "config"),
|
|
57
|
+
"XDG_CACHE_HOME": str(prefix / "cache"),
|
|
58
|
+
"XDG_DATA_HOME": str(prefix / "data"),
|
|
59
|
+
"XDG_STATE_HOME": str(prefix / "state"),
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
return base
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> dict[str, object]:
|
|
65
|
+
root = Path(self.project_root).expanduser().resolve()
|
|
66
|
+
entrypoint: str | None
|
|
67
|
+
if self.entrypoint or root.is_dir():
|
|
68
|
+
try:
|
|
69
|
+
entrypoint = self.resolved_entrypoint()
|
|
70
|
+
except ValueError:
|
|
71
|
+
entrypoint = self.entrypoint
|
|
72
|
+
else:
|
|
73
|
+
entrypoint = self.entrypoint
|
|
74
|
+
return {
|
|
75
|
+
"project_root": self.project_root,
|
|
76
|
+
"entrypoint": entrypoint,
|
|
77
|
+
"config_dir": self.config_dir,
|
|
78
|
+
"compositor": self.compositor,
|
|
79
|
+
"arguments": self.arguments,
|
|
80
|
+
"fixture_data": self.fixture_data,
|
|
81
|
+
}
|