androidctl 0.1.0__py3-none-any.whl
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.
- androidctl/__init__.py +5 -0
- androidctl/__main__.py +4 -0
- androidctl/_version.py +1 -0
- androidctl/app.py +73 -0
- androidctl/cli_options.py +27 -0
- androidctl/command_payloads.py +264 -0
- androidctl/command_views.py +157 -0
- androidctl/commands/__init__.py +1 -0
- androidctl/commands/actions.py +236 -0
- androidctl/commands/adb_wireless.py +157 -0
- androidctl/commands/close.py +30 -0
- androidctl/commands/connect.py +69 -0
- androidctl/commands/execute.py +179 -0
- androidctl/commands/list_apps.py +26 -0
- androidctl/commands/observe.py +26 -0
- androidctl/commands/open.py +41 -0
- androidctl/commands/plumbing.py +58 -0
- androidctl/commands/run_pipeline.py +307 -0
- androidctl/commands/screenshot.py +29 -0
- androidctl/commands/setup.py +301 -0
- androidctl/commands/wait.py +60 -0
- androidctl/daemon/__init__.py +1 -0
- androidctl/daemon/client.py +348 -0
- androidctl/daemon/discovery.py +190 -0
- androidctl/daemon/launcher.py +26 -0
- androidctl/daemon/owner.py +349 -0
- androidctl/errors/__init__.py +1 -0
- androidctl/errors/mapping.py +149 -0
- androidctl/errors/models.py +16 -0
- androidctl/exit_codes.py +8 -0
- androidctl/output.py +147 -0
- androidctl/parsing/__init__.py +1 -0
- androidctl/parsing/duration.py +17 -0
- androidctl/parsing/open_target.py +51 -0
- androidctl/parsing/refs.py +12 -0
- androidctl/parsing/screen_id.py +10 -0
- androidctl/parsing/wait.py +70 -0
- androidctl/renderers/__init__.py +110 -0
- androidctl/renderers/_paths.py +109 -0
- androidctl/renderers/xml.py +234 -0
- androidctl/renderers/xml_projection.py +732 -0
- androidctl/resources/__init__.py +1 -0
- androidctl/resources/androidctl-agent-0.1.0-release.apk +0 -0
- androidctl/setup/__init__.py +1 -0
- androidctl/setup/accessibility.py +159 -0
- androidctl/setup/adb.py +586 -0
- androidctl/setup/apk_resource.py +29 -0
- androidctl/setup/pairing.py +70 -0
- androidctl/setup/verify.py +175 -0
- androidctl/workspace/__init__.py +3 -0
- androidctl/workspace/resolve.py +27 -0
- androidctl-0.1.0.dist-info/METADATA +217 -0
- androidctl-0.1.0.dist-info/RECORD +187 -0
- androidctl-0.1.0.dist-info/WHEEL +5 -0
- androidctl-0.1.0.dist-info/entry_points.txt +3 -0
- androidctl-0.1.0.dist-info/licenses/LICENSE +674 -0
- androidctl-0.1.0.dist-info/top_level.txt +3 -0
- androidctl_contracts/__init__.py +55 -0
- androidctl_contracts/_version.py +1 -0
- androidctl_contracts/_wire_helpers.py +31 -0
- androidctl_contracts/base.py +142 -0
- androidctl_contracts/command_catalog.py +414 -0
- androidctl_contracts/command_results.py +630 -0
- androidctl_contracts/daemon_api.py +335 -0
- androidctl_contracts/errors.py +44 -0
- androidctl_contracts/paths.py +5 -0
- androidctl_contracts/public_screen.py +579 -0
- androidctl_contracts/user_state.py +23 -0
- androidctl_contracts/vocabulary.py +82 -0
- androidctld/__init__.py +5 -0
- androidctld/__main__.py +63 -0
- androidctld/_version.py +1 -0
- androidctld/actions/__init__.py +1 -0
- androidctld/actions/action_target.py +142 -0
- androidctld/actions/capabilities.py +539 -0
- androidctld/actions/executor.py +894 -0
- androidctld/actions/focus_confirmation.py +177 -0
- androidctld/actions/focused_input_admissibility.py +120 -0
- androidctld/actions/fresh_current.py +176 -0
- androidctld/actions/postconditions.py +473 -0
- androidctld/actions/repair.py +101 -0
- androidctld/actions/request_builder.py +204 -0
- androidctld/actions/settle.py +146 -0
- androidctld/actions/submit_confirmation.py +211 -0
- androidctld/actions/submit_routing.py +311 -0
- androidctld/actions/type_confirmation.py +257 -0
- androidctld/app_targets.py +71 -0
- androidctld/artifacts/__init__.py +1 -0
- androidctld/artifacts/models.py +26 -0
- androidctld/artifacts/screen_lookup.py +241 -0
- androidctld/artifacts/screen_payloads.py +109 -0
- androidctld/artifacts/writer.py +286 -0
- androidctld/auth/__init__.py +1 -0
- androidctld/auth/active_registry.py +266 -0
- androidctld/auth/secret_files.py +52 -0
- androidctld/auth/token_store.py +59 -0
- androidctld/commands/__init__.py +1 -0
- androidctld/commands/assembly.py +231 -0
- androidctld/commands/command_models.py +254 -0
- androidctld/commands/dispatch.py +99 -0
- androidctld/commands/executor.py +31 -0
- androidctld/commands/from_boundary.py +175 -0
- androidctld/commands/handlers/__init__.py +15 -0
- androidctld/commands/handlers/action.py +439 -0
- androidctld/commands/handlers/connect.py +94 -0
- androidctld/commands/handlers/list_apps.py +215 -0
- androidctld/commands/handlers/observe.py +121 -0
- androidctld/commands/handlers/screenshot.py +105 -0
- androidctld/commands/handlers/wait.py +286 -0
- androidctld/commands/models.py +65 -0
- androidctld/commands/open_targets.py +56 -0
- androidctld/commands/orchestration.py +353 -0
- androidctld/commands/registry.py +116 -0
- androidctld/commands/result_builders.py +40 -0
- androidctld/commands/result_models.py +555 -0
- androidctld/commands/results.py +108 -0
- androidctld/commands/semantic_command_names.py +17 -0
- androidctld/commands/semantic_error_mapping.py +93 -0
- androidctld/commands/semantic_truth.py +135 -0
- androidctld/commands/service.py +67 -0
- androidctld/config.py +75 -0
- androidctld/daemon/__init__.py +1 -0
- androidctld/daemon/active_slot.py +326 -0
- androidctld/daemon/envelope.py +30 -0
- androidctld/daemon/http_host.py +123 -0
- androidctld/daemon/ingress.py +112 -0
- androidctld/daemon/ownership_probe.py +204 -0
- androidctld/daemon/server.py +286 -0
- androidctld/daemon/service.py +99 -0
- androidctld/device/__init__.py +1 -0
- androidctld/device/action_models.py +154 -0
- androidctld/device/action_serialization.py +121 -0
- androidctld/device/adapters.py +220 -0
- androidctld/device/bootstrap.py +153 -0
- androidctld/device/connectors.py +231 -0
- androidctld/device/errors.py +100 -0
- androidctld/device/interfaces.py +58 -0
- androidctld/device/parsing.py +320 -0
- androidctld/device/rpc.py +483 -0
- androidctld/device/schema.py +114 -0
- androidctld/device/types.py +161 -0
- androidctld/errors/__init__.py +94 -0
- androidctld/logging/__init__.py +22 -0
- androidctld/observation.py +98 -0
- androidctld/protocol.py +53 -0
- androidctld/refs/__init__.py +1 -0
- androidctld/refs/models.py +54 -0
- androidctld/refs/repair.py +284 -0
- androidctld/refs/service.py +422 -0
- androidctld/rendering/__init__.py +1 -0
- androidctld/rendering/screen_xml.py +256 -0
- androidctld/runtime/__init__.py +21 -0
- androidctld/runtime/kernel.py +548 -0
- androidctld/runtime/lifecycle.py +19 -0
- androidctld/runtime/models.py +48 -0
- androidctld/runtime/screen_state.py +117 -0
- androidctld/runtime/state_repo.py +70 -0
- androidctld/runtime/store.py +76 -0
- androidctld/runtime_policy.py +127 -0
- androidctld/schema/__init__.py +5 -0
- androidctld/schema/base.py +132 -0
- androidctld/schema/core.py +35 -0
- androidctld/schema/daemon_api.py +108 -0
- androidctld/schema/persistence.py +161 -0
- androidctld/schema/persistence_io.py +41 -0
- androidctld/schema/validation_errors.py +309 -0
- androidctld/semantics/__init__.py +1 -0
- androidctld/semantics/compiler.py +610 -0
- androidctld/semantics/continuity.py +107 -0
- androidctld/semantics/labels.py +252 -0
- androidctld/semantics/models.py +25 -0
- androidctld/semantics/policy.py +23 -0
- androidctld/semantics/public_models.py +123 -0
- androidctld/semantics/registries.py +13 -0
- androidctld/semantics/submit_refs.py +417 -0
- androidctld/semantics/surface.py +254 -0
- androidctld/semantics/targets.py +167 -0
- androidctld/snapshots/__init__.py +1 -0
- androidctld/snapshots/models.py +219 -0
- androidctld/snapshots/refresh.py +273 -0
- androidctld/snapshots/schema.py +74 -0
- androidctld/snapshots/service.py +138 -0
- androidctld/text_equivalence.py +67 -0
- androidctld/waits/__init__.py +1 -0
- androidctld/waits/evaluators.py +216 -0
- androidctld/waits/loop.py +305 -0
- androidctld/waits/matcher.py +41 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Minimal contracts package boundary exports for supported public surfaces."""
|
|
2
|
+
|
|
3
|
+
from ._version import __version__
|
|
4
|
+
from .command_catalog import (
|
|
5
|
+
DAEMON_COMMAND_KINDS,
|
|
6
|
+
LIST_APPS_RESULT_COMMAND_NAMES,
|
|
7
|
+
PUBLIC_COMMAND_NAMES,
|
|
8
|
+
RESULT_COMMAND_NAMES,
|
|
9
|
+
RETAINED_RESULT_COMMAND_NAMES,
|
|
10
|
+
SEMANTIC_RESULT_COMMAND_NAMES,
|
|
11
|
+
is_list_apps_result_command,
|
|
12
|
+
is_public_command,
|
|
13
|
+
is_retained_result_command,
|
|
14
|
+
is_semantic_result_command,
|
|
15
|
+
result_family_for_command,
|
|
16
|
+
result_family_for_daemon_kind,
|
|
17
|
+
result_family_for_public_command,
|
|
18
|
+
retained_envelope_kind_for_command,
|
|
19
|
+
retained_envelope_kind_for_public_command,
|
|
20
|
+
)
|
|
21
|
+
from .command_results import CommandResultCore, ListAppsResult, RetainedResultEnvelope
|
|
22
|
+
from .daemon_api import (
|
|
23
|
+
CommandRunRequest,
|
|
24
|
+
DaemonCommandPayload,
|
|
25
|
+
)
|
|
26
|
+
from .errors import DaemonError, DaemonErrorCode
|
|
27
|
+
from .vocabulary import PublicResultFamily, RetainedEnvelopeKind
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"DAEMON_COMMAND_KINDS",
|
|
31
|
+
"LIST_APPS_RESULT_COMMAND_NAMES",
|
|
32
|
+
"PUBLIC_COMMAND_NAMES",
|
|
33
|
+
"RESULT_COMMAND_NAMES",
|
|
34
|
+
"RETAINED_RESULT_COMMAND_NAMES",
|
|
35
|
+
"SEMANTIC_RESULT_COMMAND_NAMES",
|
|
36
|
+
"CommandResultCore",
|
|
37
|
+
"CommandRunRequest",
|
|
38
|
+
"DaemonCommandPayload",
|
|
39
|
+
"DaemonError",
|
|
40
|
+
"DaemonErrorCode",
|
|
41
|
+
"ListAppsResult",
|
|
42
|
+
"PublicResultFamily",
|
|
43
|
+
"RetainedEnvelopeKind",
|
|
44
|
+
"RetainedResultEnvelope",
|
|
45
|
+
"__version__",
|
|
46
|
+
"is_list_apps_result_command",
|
|
47
|
+
"is_public_command",
|
|
48
|
+
"is_retained_result_command",
|
|
49
|
+
"is_semantic_result_command",
|
|
50
|
+
"result_family_for_command",
|
|
51
|
+
"result_family_for_daemon_kind",
|
|
52
|
+
"result_family_for_public_command",
|
|
53
|
+
"retained_envelope_kind_for_command",
|
|
54
|
+
"retained_envelope_kind_for_public_command",
|
|
55
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Private helpers for shared daemon wire model behavior."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .base import to_camel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _drop_unset_keys(
|
|
11
|
+
data: dict[str, Any],
|
|
12
|
+
*,
|
|
13
|
+
fields_set: set[str],
|
|
14
|
+
optional_fields: set[str],
|
|
15
|
+
) -> dict[str, Any]:
|
|
16
|
+
for field_name in optional_fields:
|
|
17
|
+
if field_name in fields_set:
|
|
18
|
+
continue
|
|
19
|
+
data.pop(field_name, None)
|
|
20
|
+
data.pop(to_camel(field_name), None)
|
|
21
|
+
return data
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _validate_absolute_path(value: str | None) -> str | None:
|
|
25
|
+
if value is None:
|
|
26
|
+
return None
|
|
27
|
+
if not value.startswith(("/", "\\")) and not (
|
|
28
|
+
len(value) >= 3 and value[1] == ":" and value[2] in {"/", "\\"}
|
|
29
|
+
):
|
|
30
|
+
raise ValueError("daemon-wire paths must be absolute")
|
|
31
|
+
return value
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Shared boundary-model conventions for contracts wire models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal, cast
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, ValidationError
|
|
9
|
+
from pydantic_core import InitErrorDetails
|
|
10
|
+
from typing_extensions import Self
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from pydantic._internal._model_construction import ModelMetaclass as _ModelMetaclass
|
|
14
|
+
else:
|
|
15
|
+
_ModelMetaclass = type(BaseModel)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def to_camel(name: str) -> str:
|
|
19
|
+
parts = name.split("_")
|
|
20
|
+
if len(parts) == 1:
|
|
21
|
+
return name
|
|
22
|
+
head, *tail = parts
|
|
23
|
+
return head + "".join(
|
|
24
|
+
segment[:1].upper() + segment[1:] for segment in tail if segment
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _DaemonWireModelMeta(_ModelMetaclass):
|
|
29
|
+
def __call__(cls, *args: Any, **kwargs: Any) -> Any:
|
|
30
|
+
model_cls = cast(type[DaemonWireModel], cls)
|
|
31
|
+
if kwargs and model_cls._contains_alias_kwargs(kwargs):
|
|
32
|
+
raise ValidationError.from_exception_data(
|
|
33
|
+
model_cls.__name__,
|
|
34
|
+
model_cls._alias_init_errors(kwargs),
|
|
35
|
+
)
|
|
36
|
+
return super().__call__(*args, **kwargs)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DaemonWireModel(BaseModel, metaclass=_DaemonWireModelMeta):
|
|
40
|
+
"""Base model for shared daemon wire contracts."""
|
|
41
|
+
|
|
42
|
+
model_config = ConfigDict(
|
|
43
|
+
extra="forbid",
|
|
44
|
+
alias_generator=to_camel,
|
|
45
|
+
validate_by_alias=True,
|
|
46
|
+
validate_by_name=True,
|
|
47
|
+
use_enum_values=False,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def model_validate(
|
|
52
|
+
cls,
|
|
53
|
+
obj: Any,
|
|
54
|
+
*,
|
|
55
|
+
strict: bool | None = None,
|
|
56
|
+
extra: Literal["allow", "ignore", "forbid"] | None = None,
|
|
57
|
+
from_attributes: bool | None = None,
|
|
58
|
+
context: Any | None = None,
|
|
59
|
+
by_alias: bool | None = None,
|
|
60
|
+
by_name: bool | None = None,
|
|
61
|
+
) -> Self:
|
|
62
|
+
return super().model_validate(
|
|
63
|
+
obj,
|
|
64
|
+
strict=strict,
|
|
65
|
+
extra=extra,
|
|
66
|
+
from_attributes=from_attributes,
|
|
67
|
+
context=context,
|
|
68
|
+
by_alias=True if by_alias is None else by_alias,
|
|
69
|
+
by_name=False if by_name is None else by_name,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def model_validate_json(
|
|
74
|
+
cls,
|
|
75
|
+
json_data: str | bytes | bytearray,
|
|
76
|
+
*,
|
|
77
|
+
strict: bool | None = None,
|
|
78
|
+
extra: Literal["allow", "ignore", "forbid"] | None = None,
|
|
79
|
+
context: Any | None = None,
|
|
80
|
+
by_alias: bool | None = None,
|
|
81
|
+
by_name: bool | None = None,
|
|
82
|
+
) -> Self:
|
|
83
|
+
return super().model_validate_json(
|
|
84
|
+
json_data,
|
|
85
|
+
strict=strict,
|
|
86
|
+
extra=extra,
|
|
87
|
+
context=context,
|
|
88
|
+
by_alias=True if by_alias is None else by_alias,
|
|
89
|
+
by_name=False if by_name is None else by_name,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def model_dump(self, **kwargs: Any) -> dict[str, Any]:
|
|
93
|
+
if kwargs.get("by_alias") is None:
|
|
94
|
+
kwargs["by_alias"] = True
|
|
95
|
+
if "mode" not in kwargs:
|
|
96
|
+
kwargs["mode"] = "json"
|
|
97
|
+
return super().model_dump(**kwargs)
|
|
98
|
+
|
|
99
|
+
def model_dump_json(self, **kwargs: Any) -> str:
|
|
100
|
+
if kwargs.get("by_alias") is None:
|
|
101
|
+
kwargs["by_alias"] = True
|
|
102
|
+
return super().model_dump_json(**kwargs)
|
|
103
|
+
|
|
104
|
+
def model_copy(
|
|
105
|
+
self,
|
|
106
|
+
*,
|
|
107
|
+
update: Mapping[str, Any] | None = None,
|
|
108
|
+
deep: bool = False,
|
|
109
|
+
) -> Self:
|
|
110
|
+
model_type = type(self)
|
|
111
|
+
if update and model_type._contains_alias_kwargs(update):
|
|
112
|
+
raise ValidationError.from_exception_data(
|
|
113
|
+
model_type.__name__,
|
|
114
|
+
model_type._alias_init_errors(update),
|
|
115
|
+
)
|
|
116
|
+
return super().model_copy(update=update, deep=deep)
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def _contains_alias_kwargs(cls, data: Mapping[str, Any]) -> bool:
|
|
120
|
+
return any(
|
|
121
|
+
field.alias is not None
|
|
122
|
+
and field.alias != field_name
|
|
123
|
+
and field.alias in data
|
|
124
|
+
for field_name, field in cls.model_fields.items()
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def _alias_init_errors(cls, data: Mapping[str, Any]) -> list[InitErrorDetails]:
|
|
129
|
+
return [
|
|
130
|
+
InitErrorDetails(
|
|
131
|
+
type="extra_forbidden",
|
|
132
|
+
loc=(field.alias,),
|
|
133
|
+
input=data[field.alias],
|
|
134
|
+
)
|
|
135
|
+
for field_name, field in cls.model_fields.items()
|
|
136
|
+
if field.alias is not None
|
|
137
|
+
and field.alias != field_name
|
|
138
|
+
and field.alias in data
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
__all__ = ["DaemonWireModel", "to_camel"]
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
"""Local shared command catalog used to sync routing and result shapes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from .vocabulary import (
|
|
9
|
+
PublicResultCategory,
|
|
10
|
+
PublicResultFamily,
|
|
11
|
+
RetainedEnvelopeKind,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
CommandRoute = Literal["commands_run", "runtime_close"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class CommandCatalogEntry:
|
|
19
|
+
public_name: str
|
|
20
|
+
route: CommandRoute
|
|
21
|
+
daemon_kind: str | None
|
|
22
|
+
result_command: str
|
|
23
|
+
result_family: PublicResultFamily
|
|
24
|
+
result_category: PublicResultCategory | None
|
|
25
|
+
retained_envelope_kind: RetainedEnvelopeKind | None = None
|
|
26
|
+
|
|
27
|
+
def __post_init__(self) -> None:
|
|
28
|
+
if self.result_family is PublicResultFamily.SEMANTIC:
|
|
29
|
+
if self.result_category is None:
|
|
30
|
+
raise ValueError("semantic result family requires result_category")
|
|
31
|
+
if self.retained_envelope_kind is not None:
|
|
32
|
+
raise ValueError(
|
|
33
|
+
"semantic result family forbids retained_envelope_kind"
|
|
34
|
+
)
|
|
35
|
+
elif self.result_family is PublicResultFamily.RETAINED:
|
|
36
|
+
if self.retained_envelope_kind is None:
|
|
37
|
+
raise ValueError(
|
|
38
|
+
"retained result family requires retained_envelope_kind"
|
|
39
|
+
)
|
|
40
|
+
if self.result_category is not None:
|
|
41
|
+
raise ValueError("retained result family forbids result_category")
|
|
42
|
+
elif self.result_family is PublicResultFamily.LIST_APPS:
|
|
43
|
+
if self.result_category is not None:
|
|
44
|
+
raise ValueError("listApps result family forbids result_category")
|
|
45
|
+
if self.retained_envelope_kind is not None:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
"listApps result family forbids retained_envelope_kind"
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError(f"unsupported result family: {self.result_family!r}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
_COMMAND_CATALOG: tuple[CommandCatalogEntry, ...] = (
|
|
54
|
+
CommandCatalogEntry(
|
|
55
|
+
public_name="connect",
|
|
56
|
+
route="commands_run",
|
|
57
|
+
daemon_kind="connect",
|
|
58
|
+
result_command="connect",
|
|
59
|
+
result_family=PublicResultFamily.RETAINED,
|
|
60
|
+
result_category=None,
|
|
61
|
+
retained_envelope_kind=RetainedEnvelopeKind.BOOTSTRAP,
|
|
62
|
+
),
|
|
63
|
+
CommandCatalogEntry(
|
|
64
|
+
public_name="observe",
|
|
65
|
+
route="commands_run",
|
|
66
|
+
daemon_kind="observe",
|
|
67
|
+
result_command="observe",
|
|
68
|
+
result_family=PublicResultFamily.SEMANTIC,
|
|
69
|
+
result_category=PublicResultCategory.OBSERVE,
|
|
70
|
+
),
|
|
71
|
+
CommandCatalogEntry(
|
|
72
|
+
public_name="open",
|
|
73
|
+
route="commands_run",
|
|
74
|
+
daemon_kind="open",
|
|
75
|
+
result_command="open",
|
|
76
|
+
result_family=PublicResultFamily.SEMANTIC,
|
|
77
|
+
result_category=PublicResultCategory.OPEN,
|
|
78
|
+
),
|
|
79
|
+
CommandCatalogEntry(
|
|
80
|
+
public_name="tap",
|
|
81
|
+
route="commands_run",
|
|
82
|
+
daemon_kind="tap",
|
|
83
|
+
result_command="tap",
|
|
84
|
+
result_family=PublicResultFamily.SEMANTIC,
|
|
85
|
+
result_category=PublicResultCategory.TRANSITION,
|
|
86
|
+
),
|
|
87
|
+
CommandCatalogEntry(
|
|
88
|
+
public_name="long-tap",
|
|
89
|
+
route="commands_run",
|
|
90
|
+
daemon_kind="longTap",
|
|
91
|
+
result_command="long-tap",
|
|
92
|
+
result_family=PublicResultFamily.SEMANTIC,
|
|
93
|
+
result_category=PublicResultCategory.TRANSITION,
|
|
94
|
+
),
|
|
95
|
+
CommandCatalogEntry(
|
|
96
|
+
public_name="focus",
|
|
97
|
+
route="commands_run",
|
|
98
|
+
daemon_kind="focus",
|
|
99
|
+
result_command="focus",
|
|
100
|
+
result_family=PublicResultFamily.SEMANTIC,
|
|
101
|
+
result_category=PublicResultCategory.TRANSITION,
|
|
102
|
+
),
|
|
103
|
+
CommandCatalogEntry(
|
|
104
|
+
public_name="type",
|
|
105
|
+
route="commands_run",
|
|
106
|
+
daemon_kind="type",
|
|
107
|
+
result_command="type",
|
|
108
|
+
result_family=PublicResultFamily.SEMANTIC,
|
|
109
|
+
result_category=PublicResultCategory.TRANSITION,
|
|
110
|
+
),
|
|
111
|
+
CommandCatalogEntry(
|
|
112
|
+
public_name="submit",
|
|
113
|
+
route="commands_run",
|
|
114
|
+
daemon_kind="submit",
|
|
115
|
+
result_command="submit",
|
|
116
|
+
result_family=PublicResultFamily.SEMANTIC,
|
|
117
|
+
result_category=PublicResultCategory.TRANSITION,
|
|
118
|
+
),
|
|
119
|
+
CommandCatalogEntry(
|
|
120
|
+
public_name="scroll",
|
|
121
|
+
route="commands_run",
|
|
122
|
+
daemon_kind="scroll",
|
|
123
|
+
result_command="scroll",
|
|
124
|
+
result_family=PublicResultFamily.SEMANTIC,
|
|
125
|
+
result_category=PublicResultCategory.TRANSITION,
|
|
126
|
+
),
|
|
127
|
+
CommandCatalogEntry(
|
|
128
|
+
public_name="back",
|
|
129
|
+
route="commands_run",
|
|
130
|
+
daemon_kind="back",
|
|
131
|
+
result_command="back",
|
|
132
|
+
result_family=PublicResultFamily.SEMANTIC,
|
|
133
|
+
result_category=PublicResultCategory.TRANSITION,
|
|
134
|
+
),
|
|
135
|
+
CommandCatalogEntry(
|
|
136
|
+
public_name="home",
|
|
137
|
+
route="commands_run",
|
|
138
|
+
daemon_kind="home",
|
|
139
|
+
result_command="home",
|
|
140
|
+
result_family=PublicResultFamily.SEMANTIC,
|
|
141
|
+
result_category=PublicResultCategory.TRANSITION,
|
|
142
|
+
),
|
|
143
|
+
CommandCatalogEntry(
|
|
144
|
+
public_name="recents",
|
|
145
|
+
route="commands_run",
|
|
146
|
+
daemon_kind="recents",
|
|
147
|
+
result_command="recents",
|
|
148
|
+
result_family=PublicResultFamily.SEMANTIC,
|
|
149
|
+
result_category=PublicResultCategory.TRANSITION,
|
|
150
|
+
),
|
|
151
|
+
CommandCatalogEntry(
|
|
152
|
+
public_name="notifications",
|
|
153
|
+
route="commands_run",
|
|
154
|
+
daemon_kind="notifications",
|
|
155
|
+
result_command="notifications",
|
|
156
|
+
result_family=PublicResultFamily.SEMANTIC,
|
|
157
|
+
result_category=PublicResultCategory.TRANSITION,
|
|
158
|
+
),
|
|
159
|
+
CommandCatalogEntry(
|
|
160
|
+
public_name="wait",
|
|
161
|
+
route="commands_run",
|
|
162
|
+
daemon_kind="wait",
|
|
163
|
+
result_command="wait",
|
|
164
|
+
result_family=PublicResultFamily.SEMANTIC,
|
|
165
|
+
result_category=PublicResultCategory.WAIT,
|
|
166
|
+
),
|
|
167
|
+
CommandCatalogEntry(
|
|
168
|
+
public_name="list-apps",
|
|
169
|
+
route="commands_run",
|
|
170
|
+
daemon_kind="listApps",
|
|
171
|
+
result_command="list-apps",
|
|
172
|
+
result_family=PublicResultFamily.LIST_APPS,
|
|
173
|
+
result_category=None,
|
|
174
|
+
),
|
|
175
|
+
CommandCatalogEntry(
|
|
176
|
+
public_name="screenshot",
|
|
177
|
+
route="commands_run",
|
|
178
|
+
daemon_kind="screenshot",
|
|
179
|
+
result_command="screenshot",
|
|
180
|
+
result_family=PublicResultFamily.RETAINED,
|
|
181
|
+
result_category=None,
|
|
182
|
+
retained_envelope_kind=RetainedEnvelopeKind.ARTIFACT,
|
|
183
|
+
),
|
|
184
|
+
CommandCatalogEntry(
|
|
185
|
+
public_name="close",
|
|
186
|
+
route="runtime_close",
|
|
187
|
+
daemon_kind=None,
|
|
188
|
+
result_command="close",
|
|
189
|
+
result_family=PublicResultFamily.RETAINED,
|
|
190
|
+
result_category=None,
|
|
191
|
+
retained_envelope_kind=RetainedEnvelopeKind.LIFECYCLE,
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _build_unique_entry_index(
|
|
197
|
+
entries: tuple[CommandCatalogEntry, ...],
|
|
198
|
+
*,
|
|
199
|
+
field_name: str,
|
|
200
|
+
) -> dict[str, CommandCatalogEntry]:
|
|
201
|
+
index: dict[str, CommandCatalogEntry] = {}
|
|
202
|
+
for entry in entries:
|
|
203
|
+
key = getattr(entry, field_name)
|
|
204
|
+
if key is None:
|
|
205
|
+
continue
|
|
206
|
+
if key in index:
|
|
207
|
+
raise RuntimeError(f"duplicate command catalog {field_name}: {key!r}")
|
|
208
|
+
index[key] = entry
|
|
209
|
+
return index
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
_ENTRY_BY_PUBLIC_NAME = _build_unique_entry_index(
|
|
213
|
+
_COMMAND_CATALOG,
|
|
214
|
+
field_name="public_name",
|
|
215
|
+
)
|
|
216
|
+
_ENTRY_BY_DAEMON_KIND = _build_unique_entry_index(
|
|
217
|
+
_COMMAND_CATALOG,
|
|
218
|
+
field_name="daemon_kind",
|
|
219
|
+
)
|
|
220
|
+
_ENTRY_BY_RESULT_COMMAND = _build_unique_entry_index(
|
|
221
|
+
_COMMAND_CATALOG,
|
|
222
|
+
field_name="result_command",
|
|
223
|
+
)
|
|
224
|
+
_ENTRY_BY_RETAINED_RESULT_COMMAND = {
|
|
225
|
+
command: entry
|
|
226
|
+
for command, entry in _ENTRY_BY_RESULT_COMMAND.items()
|
|
227
|
+
if entry.result_family is PublicResultFamily.RETAINED
|
|
228
|
+
}
|
|
229
|
+
_ENTRY_BY_SEMANTIC_RESULT_COMMAND = {
|
|
230
|
+
command: entry
|
|
231
|
+
for command, entry in _ENTRY_BY_RESULT_COMMAND.items()
|
|
232
|
+
if entry.result_family is PublicResultFamily.SEMANTIC
|
|
233
|
+
}
|
|
234
|
+
_ENTRY_BY_LIST_APPS_RESULT_COMMAND = {
|
|
235
|
+
command: entry
|
|
236
|
+
for command, entry in _ENTRY_BY_RESULT_COMMAND.items()
|
|
237
|
+
if entry.result_family is PublicResultFamily.LIST_APPS
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
PUBLIC_COMMAND_NAMES = set(_ENTRY_BY_PUBLIC_NAME)
|
|
241
|
+
DAEMON_COMMAND_KINDS = set(_ENTRY_BY_DAEMON_KIND)
|
|
242
|
+
SEMANTIC_RESULT_COMMAND_NAMES = set(_ENTRY_BY_SEMANTIC_RESULT_COMMAND)
|
|
243
|
+
RETAINED_RESULT_COMMAND_NAMES = set(_ENTRY_BY_RETAINED_RESULT_COMMAND)
|
|
244
|
+
LIST_APPS_RESULT_COMMAND_NAMES = set(_ENTRY_BY_LIST_APPS_RESULT_COMMAND)
|
|
245
|
+
RESULT_COMMAND_NAMES = set(_ENTRY_BY_RESULT_COMMAND)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def entries_for_route(route: CommandRoute) -> tuple[CommandCatalogEntry, ...]:
|
|
249
|
+
return tuple(entry for entry in _COMMAND_CATALOG if entry.route == route)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def daemon_command_kinds_for_route(route: CommandRoute) -> frozenset[str]:
|
|
253
|
+
return frozenset(
|
|
254
|
+
entry.daemon_kind
|
|
255
|
+
for entry in entries_for_route(route)
|
|
256
|
+
if entry.daemon_kind is not None
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def runtime_close_entry() -> CommandCatalogEntry:
|
|
261
|
+
entries = entries_for_route("runtime_close")
|
|
262
|
+
if len(entries) != 1:
|
|
263
|
+
raise RuntimeError(
|
|
264
|
+
f"expected exactly one runtime_close command, found {len(entries)}"
|
|
265
|
+
)
|
|
266
|
+
entry = entries[0]
|
|
267
|
+
if entry.daemon_kind is not None:
|
|
268
|
+
raise RuntimeError("runtime_close command must not have a daemon kind")
|
|
269
|
+
return entry
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def entry_for_public_command(name: str) -> CommandCatalogEntry | None:
|
|
273
|
+
return _ENTRY_BY_PUBLIC_NAME.get(name)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def entry_for_daemon_kind(kind: str) -> CommandCatalogEntry | None:
|
|
277
|
+
return _ENTRY_BY_DAEMON_KIND.get(kind)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def entry_for_result_command(command: str) -> CommandCatalogEntry | None:
|
|
281
|
+
return _ENTRY_BY_RESULT_COMMAND.get(command)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def entry_for_retained_result_command(command: str) -> CommandCatalogEntry | None:
|
|
285
|
+
return _ENTRY_BY_RETAINED_RESULT_COMMAND.get(command)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def entry_for_semantic_result_command(command: str) -> CommandCatalogEntry | None:
|
|
289
|
+
return _ENTRY_BY_SEMANTIC_RESULT_COMMAND.get(command)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def entry_for_list_apps_result_command(command: str) -> CommandCatalogEntry | None:
|
|
293
|
+
return _ENTRY_BY_LIST_APPS_RESULT_COMMAND.get(command)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def daemon_kind_for_public_command(name: str) -> str | None:
|
|
297
|
+
entry = entry_for_public_command(name)
|
|
298
|
+
if entry is None:
|
|
299
|
+
return None
|
|
300
|
+
return entry.daemon_kind
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def public_command_for_daemon_kind(kind: str) -> str | None:
|
|
304
|
+
entry = entry_for_daemon_kind(kind)
|
|
305
|
+
if entry is None:
|
|
306
|
+
return None
|
|
307
|
+
return entry.public_name
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def result_category_for_public_command(
|
|
311
|
+
name: str,
|
|
312
|
+
) -> PublicResultCategory | None:
|
|
313
|
+
entry = entry_for_public_command(name)
|
|
314
|
+
if entry is None:
|
|
315
|
+
return None
|
|
316
|
+
return entry.result_category
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def result_family_for_public_command(
|
|
320
|
+
name: str,
|
|
321
|
+
) -> PublicResultFamily | None:
|
|
322
|
+
entry = entry_for_public_command(name)
|
|
323
|
+
if entry is None:
|
|
324
|
+
return None
|
|
325
|
+
return entry.result_family
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def result_family_for_daemon_kind(kind: str) -> PublicResultFamily | None:
|
|
329
|
+
entry = entry_for_daemon_kind(kind)
|
|
330
|
+
if entry is None:
|
|
331
|
+
return None
|
|
332
|
+
return entry.result_family
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def result_family_for_command(command: str) -> PublicResultFamily | None:
|
|
336
|
+
entry = entry_for_result_command(command)
|
|
337
|
+
if entry is None:
|
|
338
|
+
return None
|
|
339
|
+
return entry.result_family
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def result_category_for_command(command: str) -> PublicResultCategory | None:
|
|
343
|
+
entry = entry_for_semantic_result_command(command)
|
|
344
|
+
if entry is None:
|
|
345
|
+
return None
|
|
346
|
+
return entry.result_category
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def retained_envelope_kind_for_public_command(
|
|
350
|
+
name: str,
|
|
351
|
+
) -> RetainedEnvelopeKind | None:
|
|
352
|
+
entry = entry_for_public_command(name)
|
|
353
|
+
if entry is None:
|
|
354
|
+
return None
|
|
355
|
+
return entry.retained_envelope_kind
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def retained_envelope_kind_for_command(command: str) -> RetainedEnvelopeKind | None:
|
|
359
|
+
entry = entry_for_retained_result_command(command)
|
|
360
|
+
if entry is None:
|
|
361
|
+
return None
|
|
362
|
+
return entry.retained_envelope_kind
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def is_public_command(name: str) -> bool:
|
|
366
|
+
return entry_for_public_command(name) is not None
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def is_daemon_command_kind(kind: str) -> bool:
|
|
370
|
+
return entry_for_daemon_kind(kind) is not None
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def is_semantic_result_command(command: str) -> bool:
|
|
374
|
+
return entry_for_semantic_result_command(command) is not None
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def is_retained_result_command(command: str) -> bool:
|
|
378
|
+
return entry_for_retained_result_command(command) is not None
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def is_list_apps_result_command(command: str) -> bool:
|
|
382
|
+
return entry_for_list_apps_result_command(command) is not None
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
__all__ = [
|
|
386
|
+
"DAEMON_COMMAND_KINDS",
|
|
387
|
+
"LIST_APPS_RESULT_COMMAND_NAMES",
|
|
388
|
+
"PUBLIC_COMMAND_NAMES",
|
|
389
|
+
"RESULT_COMMAND_NAMES",
|
|
390
|
+
"RETAINED_RESULT_COMMAND_NAMES",
|
|
391
|
+
"SEMANTIC_RESULT_COMMAND_NAMES",
|
|
392
|
+
"CommandCatalogEntry",
|
|
393
|
+
"CommandRoute",
|
|
394
|
+
"daemon_kind_for_public_command",
|
|
395
|
+
"entry_for_daemon_kind",
|
|
396
|
+
"entry_for_list_apps_result_command",
|
|
397
|
+
"entry_for_public_command",
|
|
398
|
+
"entry_for_result_command",
|
|
399
|
+
"entry_for_retained_result_command",
|
|
400
|
+
"entry_for_semantic_result_command",
|
|
401
|
+
"is_daemon_command_kind",
|
|
402
|
+
"is_list_apps_result_command",
|
|
403
|
+
"is_public_command",
|
|
404
|
+
"is_retained_result_command",
|
|
405
|
+
"is_semantic_result_command",
|
|
406
|
+
"public_command_for_daemon_kind",
|
|
407
|
+
"result_category_for_command",
|
|
408
|
+
"result_category_for_public_command",
|
|
409
|
+
"result_family_for_command",
|
|
410
|
+
"result_family_for_daemon_kind",
|
|
411
|
+
"result_family_for_public_command",
|
|
412
|
+
"retained_envelope_kind_for_command",
|
|
413
|
+
"retained_envelope_kind_for_public_command",
|
|
414
|
+
]
|