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.
Files changed (34) hide show
  1. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/PKG-INFO +1 -1
  2. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/pyproject.toml +1 -1
  3. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/__init__.py +6 -0
  4. deepctl_core-0.3.1/src/deepctl_core/_launcher.py +67 -0
  5. deepctl_core-0.3.1/src/deepctl_core/_plugin_trust.py +118 -0
  6. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/auth.py +231 -241
  7. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/base_command.py +70 -55
  8. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/client.py +111 -44
  9. deepctl_core-0.3.1/src/deepctl_core/config.py +2316 -0
  10. deepctl_core-0.3.1/src/deepctl_core/hosts.py +410 -0
  11. deepctl_core-0.3.1/src/deepctl_core/messages.py +344 -0
  12. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/models.py +12 -0
  13. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/output.py +96 -6
  14. deepctl_core-0.3.1/src/deepctl_core/plugin_env.py +341 -0
  15. deepctl_core-0.3.1/src/deepctl_core/plugin_manager.py +1215 -0
  16. deepctl_core-0.3.1/src/deepctl_core/safe_text.py +27 -0
  17. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/skill_bundle.py +3 -3
  18. deepctl_core-0.3.1/src/deepctl_core/telemetry_context.py +72 -0
  19. deepctl_core-0.3.1/src/deepctl_core/tools.py +48 -0
  20. deepctl_core-0.3.1/src/deepctl_core/websocket.py +192 -0
  21. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core.egg-info/PKG-INFO +1 -1
  22. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core.egg-info/SOURCES.txt +8 -0
  23. deepctl_core-0.2.19/src/deepctl_core/config.py +0 -323
  24. deepctl_core-0.2.19/src/deepctl_core/plugin_env.py +0 -185
  25. deepctl_core-0.2.19/src/deepctl_core/plugin_manager.py +0 -678
  26. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/README.md +0 -0
  27. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/setup.cfg +0 -0
  28. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/base_group_command.py +0 -0
  29. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/py.typed +0 -0
  30. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/skill_generator.py +0 -0
  31. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core/timing.py +0 -0
  32. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core.egg-info/dependency_links.txt +0 -0
  33. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core.egg-info/requires.txt +0 -0
  34. {deepctl_core-0.2.19 → deepctl_core-0.3.1}/src/deepctl_core.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepctl-core
3
- Version: 0.2.19
3
+ Version: 0.3.1
4
4
  Summary: Core components for deepctl
5
5
  Author-email: Deepgram <devrel@deepgram.com>
6
6
  Maintainer-email: Deepgram <devrel@deepgram.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deepctl-core"
7
- version = "0.2.19" # x-release-please-version
7
+ version = "0.3.1" # x-release-please-version
8
8
  description = "Core components for deepctl"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -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