deepctl-core 0.2.19__tar.gz → 0.3.1__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.
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/PKG-INFO +1 -1
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/pyproject.toml +1 -1
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/__init__.py +6 -0
- deepctl_core-0.3.1/src/deepctl_core/_launcher.py +67 -0
- deepctl_core-0.3.1/src/deepctl_core/_plugin_trust.py +118 -0
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/auth.py +231 -241
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/base_command.py +70 -55
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/client.py +111 -44
- deepctl_core-0.3.1/src/deepctl_core/config.py +2316 -0
- deepctl_core-0.3.1/src/deepctl_core/hosts.py +410 -0
- deepctl_core-0.3.1/src/deepctl_core/messages.py +344 -0
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/models.py +12 -0
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/output.py +96 -6
- deepctl_core-0.3.1/src/deepctl_core/plugin_env.py +341 -0
- deepctl_core-0.3.1/src/deepctl_core/plugin_manager.py +1215 -0
- deepctl_core-0.3.1/src/deepctl_core/safe_text.py +27 -0
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/skill_bundle.py +3 -3
- deepctl_core-0.3.1/src/deepctl_core/telemetry_context.py +72 -0
- deepctl_core-0.3.1/src/deepctl_core/tools.py +48 -0
- deepctl_core-0.3.1/src/deepctl_core/websocket.py +192 -0
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core.egg-info/PKG-INFO +1 -1
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core.egg-info/SOURCES.txt +8 -0
- deepctl_core-0.2.19/src/deepctl_core/config.py +0 -323
- deepctl_core-0.2.19/src/deepctl_core/plugin_env.py +0 -185
- deepctl_core-0.2.19/src/deepctl_core/plugin_manager.py +0 -678
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/README.md +0 -0
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/setup.cfg +0 -0
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/base_group_command.py +0 -0
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/py.typed +0 -0
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/skill_generator.py +0 -0
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/timing.py +0 -0
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core.egg-info/dependency_links.txt +0 -0
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core.egg-info/requires.txt +0 -0
- {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core.egg-info/top_level.txt +0 -0
|
@@ -5,8 +5,10 @@ from .base_command import BaseCommand
|
|
|
5
5
|
from .base_group_command import BaseGroupCommand
|
|
6
6
|
from .client import DeepgramClient
|
|
7
7
|
from .config import Config
|
|
8
|
+
from .hosts import CredentialHostError
|
|
8
9
|
from .models import (
|
|
9
10
|
BaseResult,
|
|
11
|
+
CodedErrorResult,
|
|
10
12
|
ErrorResult,
|
|
11
13
|
PluginInfo,
|
|
12
14
|
ProfileInfo,
|
|
@@ -18,6 +20,7 @@ from .output import (
|
|
|
18
20
|
get_output_format,
|
|
19
21
|
get_status_console,
|
|
20
22
|
is_agentic,
|
|
23
|
+
is_ai_agent,
|
|
21
24
|
print_error,
|
|
22
25
|
print_info,
|
|
23
26
|
print_output,
|
|
@@ -41,7 +44,9 @@ __all__ = [
|
|
|
41
44
|
"BaseCommand",
|
|
42
45
|
"BaseGroupCommand",
|
|
43
46
|
"BaseResult",
|
|
47
|
+
"CodedErrorResult",
|
|
44
48
|
"Config",
|
|
49
|
+
"CredentialHostError",
|
|
45
50
|
"DeepgramClient",
|
|
46
51
|
"ErrorResult",
|
|
47
52
|
"OutputFormatter",
|
|
@@ -56,6 +61,7 @@ __all__ = [
|
|
|
56
61
|
"get_status_console",
|
|
57
62
|
"get_timing_summary",
|
|
58
63
|
"is_agentic",
|
|
64
|
+
"is_ai_agent",
|
|
59
65
|
"is_timing_enabled",
|
|
60
66
|
"print_error",
|
|
61
67
|
"print_info",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Which wrapper, if any, started deepctl (internal; not a public API).
|
|
2
|
+
|
|
3
|
+
The ``@deepgram/cli`` npm package runs deepctl from a private venv and says
|
|
4
|
+
so in ``DEEPCTL_LAUNCHER``: ``npm`` for an installed package (global, or a
|
|
5
|
+
project dependency: the launcher can't tell them apart), ``npx`` for
|
|
6
|
+
``npx``/``npm exec``. Nothing else sets it.
|
|
7
|
+
|
|
8
|
+
The variable is read once and removed from ``os.environ``, so it describes
|
|
9
|
+
this process only. Anything deepctl spawns -- a starter app, a shell, another
|
|
10
|
+
``dg`` -- must not inherit a claim about how *it* was installed.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
LAUNCHER_ENV_VAR = "DEEPCTL_LAUNCHER"
|
|
19
|
+
NPM_LAUNCHERS = frozenset({"npm", "npx"})
|
|
20
|
+
|
|
21
|
+
_captured = False
|
|
22
|
+
_launcher: str | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def capture_launcher() -> str | None:
|
|
26
|
+
"""The launcher that started deepctl, captured on the first call.
|
|
27
|
+
|
|
28
|
+
Reads and removes ``DEEPCTL_LAUNCHER``, unless ``deepctl.main`` already
|
|
29
|
+
did and says so with ``LAUNCHER_CAPTURED``: then its ``LAUNCHER`` is the
|
|
30
|
+
value. From 0.5.0 (#165), ``deepctl.main`` is stdlib-only and removes the
|
|
31
|
+
variable before deepctl_core is imported, so it is gone from
|
|
32
|
+
``os.environ`` by the time this runs. A ``deepctl.main`` that calls this
|
|
33
|
+
function itself sets no flag, and the check falls through.
|
|
34
|
+
"""
|
|
35
|
+
global _captured, _launcher
|
|
36
|
+
if not _captured:
|
|
37
|
+
entry = sys.modules.get("deepctl.main")
|
|
38
|
+
if entry is not None and getattr(entry, "LAUNCHER_CAPTURED", False):
|
|
39
|
+
_launcher = entry.LAUNCHER
|
|
40
|
+
else:
|
|
41
|
+
_launcher = os.environ.pop(LAUNCHER_ENV_VAR, None) or None
|
|
42
|
+
_captured = True
|
|
43
|
+
return _launcher
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_launcher() -> str | None:
|
|
47
|
+
"""Return the launcher that started deepctl, or ``None``."""
|
|
48
|
+
return capture_launcher()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def launched_via_npm() -> bool:
|
|
52
|
+
"""Return True if the npm package (global install or npx) started deepctl."""
|
|
53
|
+
return get_launcher() in NPM_LAUNCHERS
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def npm_upgrade_command() -> str:
|
|
57
|
+
"""Return the command that upgrades an npm-launched deepctl."""
|
|
58
|
+
if get_launcher() == "npx":
|
|
59
|
+
return "npx @deepgram/cli@latest"
|
|
60
|
+
return "npm install -g @deepgram/cli@latest"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def reset_launcher_for_tests() -> None:
|
|
64
|
+
"""Forget the captured value so the next call re-reads the environment."""
|
|
65
|
+
global _captured, _launcher
|
|
66
|
+
_captured = False
|
|
67
|
+
_launcher = None
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Trusted locations for plugin settings and the plugin venv.
|
|
2
|
+
|
|
3
|
+
Built on the config module's real-home trust helpers: the account's home
|
|
4
|
+
comes from the OS (``HOME`` aside, the known-folder API on Windows), and in
|
|
5
|
+
an AI agent session the user config is read only through the checked open
|
|
6
|
+
of the trust root (no symlinked component, owned by this user, one link, no
|
|
7
|
+
group or other write).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
from . import config as _config
|
|
16
|
+
from .output import is_ai_agent
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"agent_plugin_venv",
|
|
20
|
+
"allow_override_names",
|
|
21
|
+
"contained_in",
|
|
22
|
+
"is_ai_agent",
|
|
23
|
+
"real_home_plugin_venv",
|
|
24
|
+
"trusted_user_config_file",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _agent_trust_root() -> Path | None:
|
|
29
|
+
"""The trust root's config path, unless it lies inside the working
|
|
30
|
+
directory (and that directory isn't the home itself)."""
|
|
31
|
+
location = _config._trust_root_location()
|
|
32
|
+
home = _config._real_home()
|
|
33
|
+
if location is None or home is None:
|
|
34
|
+
return None
|
|
35
|
+
real = location[1]
|
|
36
|
+
try:
|
|
37
|
+
resolved = os.path.realpath(real)
|
|
38
|
+
cwd = os.path.realpath(os.getcwd())
|
|
39
|
+
at_home = cwd == os.path.realpath(home)
|
|
40
|
+
except (OSError, ValueError):
|
|
41
|
+
return None
|
|
42
|
+
if not at_home and _under(resolved, cwd):
|
|
43
|
+
return None
|
|
44
|
+
return real
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def trusted_user_config_file() -> Path | None:
|
|
48
|
+
"""The user config file at its default location. Under an AI-agent
|
|
49
|
+
signal, only the one derived from the real home directory."""
|
|
50
|
+
if is_ai_agent():
|
|
51
|
+
return _agent_trust_root()
|
|
52
|
+
return _config._default_config_file()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def allow_override_names() -> frozenset[str]:
|
|
56
|
+
"""Command paths listed in ``plugins.allow_override`` of the trusted file.
|
|
57
|
+
|
|
58
|
+
Never read from a ``--config`` file, ``deepgram.yaml`` or the environment.
|
|
59
|
+
Under an AI-agent signal the file must also pass the trust-root checks.
|
|
60
|
+
Any read or parse problem yields an empty set.
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
if is_ai_agent():
|
|
64
|
+
if _agent_trust_root() is None:
|
|
65
|
+
return frozenset()
|
|
66
|
+
root = _config._open_trust_root()
|
|
67
|
+
data = root[1] if root is not None else None
|
|
68
|
+
else:
|
|
69
|
+
_, data = _config._read_yaml_fd(_config._default_config_file())
|
|
70
|
+
except (OSError, ValueError, yaml.YAMLError):
|
|
71
|
+
return frozenset()
|
|
72
|
+
plugins = data.get("plugins") if isinstance(data, dict) else None
|
|
73
|
+
names = plugins.get("allow_override") if isinstance(plugins, dict) else None
|
|
74
|
+
if not isinstance(names, list):
|
|
75
|
+
return frozenset()
|
|
76
|
+
return frozenset(" ".join(n.split()) for n in names if isinstance(n, str))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _under(path: str, directory: str) -> bool:
|
|
80
|
+
return path == directory or path.startswith(directory.rstrip(os.sep) + os.sep)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def real_home_plugin_venv() -> Path | None:
|
|
84
|
+
"""``.deepctl/plugins/venv`` under the real home directory, unchecked."""
|
|
85
|
+
home = _config._real_home()
|
|
86
|
+
return None if home is None else Path(home, ".deepctl", "plugins", "venv")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def agent_plugin_venv() -> Path | None:
|
|
90
|
+
"""The plugin venv to load under an AI-agent signal, or ``None``.
|
|
91
|
+
|
|
92
|
+
It is ``.deepctl/plugins/venv`` under the account's real home directory
|
|
93
|
+
(``HOME`` aside). ``None`` when the home can't be read, when a directory
|
|
94
|
+
below the home on the way to the venv is a symlink, or when the venv lies
|
|
95
|
+
inside the working directory and that directory isn't the home itself.
|
|
96
|
+
"""
|
|
97
|
+
home = _config._real_home()
|
|
98
|
+
if home is None:
|
|
99
|
+
return None
|
|
100
|
+
try:
|
|
101
|
+
real_home = os.path.realpath(home)
|
|
102
|
+
venv = os.path.join(real_home, ".deepctl", "plugins", "venv")
|
|
103
|
+
if os.path.realpath(venv) != venv:
|
|
104
|
+
return None
|
|
105
|
+
cwd = os.path.realpath(os.getcwd())
|
|
106
|
+
except (OSError, ValueError):
|
|
107
|
+
return None
|
|
108
|
+
if cwd != real_home and _under(venv, cwd):
|
|
109
|
+
return None
|
|
110
|
+
return Path(venv)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def contained_in(path: Path, directory: Path) -> bool:
|
|
114
|
+
"""True when ``path`` resolves inside ``directory`` (resolved)."""
|
|
115
|
+
try:
|
|
116
|
+
return _under(os.path.realpath(path), os.path.realpath(directory))
|
|
117
|
+
except (OSError, ValueError):
|
|
118
|
+
return False
|