deepctl-core 0.2.11__tar.gz → 0.2.13__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.11 → deepctl_core-0.2.13}/PKG-INFO +1 -1
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/pyproject.toml +1 -1
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core/base_command.py +92 -3
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core.egg-info/PKG-INFO +1 -1
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/README.md +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/setup.cfg +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core/__init__.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core/auth.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core/base_group_command.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core/client.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core/config.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core/models.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core/output.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core/plugin_env.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core/plugin_manager.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core/py.typed +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core/skill_generator.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core/timing.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core.egg-info/SOURCES.txt +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core.egg-info/dependency_links.txt +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core.egg-info/requires.txt +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.13}/src/deepctl_core.egg-info/top_level.txt +0 -0
|
@@ -39,6 +39,7 @@ class BaseCommand(ABC):
|
|
|
39
39
|
raise ValueError("Command must have a name")
|
|
40
40
|
if not self.help:
|
|
41
41
|
raise ValueError("Command must have help text")
|
|
42
|
+
self._guided: bool = True
|
|
42
43
|
|
|
43
44
|
def execute(self, ctx: click.Context, **kwargs: Any) -> None:
|
|
44
45
|
"""Execute the command with Click context.
|
|
@@ -104,26 +105,112 @@ class BaseCommand(ABC):
|
|
|
104
105
|
)
|
|
105
106
|
raise click.ClickException("Project ID required")
|
|
106
107
|
|
|
108
|
+
self._guided = self.is_guided(ctx)
|
|
109
|
+
self._tag_telemetry_start(ctx)
|
|
110
|
+
|
|
107
111
|
# Execute the command
|
|
108
112
|
try:
|
|
109
113
|
with TimingContext(f"command_{self.name}_handler"):
|
|
110
114
|
result = self.handle(config, auth_manager, client, **kwargs)
|
|
111
115
|
|
|
116
|
+
status = "ok"
|
|
117
|
+
if result is not None and hasattr(result, "status"):
|
|
118
|
+
status = str(result.status)
|
|
119
|
+
self._tag_telemetry_status(status)
|
|
120
|
+
|
|
112
121
|
# Handle command result
|
|
113
122
|
if result is not None:
|
|
114
123
|
with TimingContext("output_processing"):
|
|
115
|
-
|
|
124
|
+
try:
|
|
125
|
+
self.output_result(result, config)
|
|
126
|
+
except (BrokenPipeError, OSError):
|
|
127
|
+
# Downstream stream closed (e.g. an MCP host disconnected
|
|
128
|
+
# stdio after `dg mcp` finished). Nothing useful to log
|
|
129
|
+
# here because the logger writes to the same closed stream.
|
|
130
|
+
pass
|
|
131
|
+
except ValueError as exc:
|
|
132
|
+
if "closed file" not in str(exc):
|
|
133
|
+
raise
|
|
116
134
|
|
|
117
135
|
except KeyboardInterrupt:
|
|
136
|
+
self._tag_telemetry_status("cancelled")
|
|
118
137
|
stderr_console.print("\n[yellow]Command cancelled by user[/yellow]")
|
|
119
138
|
raise click.Abort()
|
|
120
139
|
|
|
121
140
|
except Exception as e:
|
|
141
|
+
self._tag_telemetry_status("error")
|
|
122
142
|
print_error(f"Command failed: {e}")
|
|
123
143
|
if config.get("output.verbose", False):
|
|
124
144
|
stderr_console.print_exception()
|
|
125
145
|
raise click.ClickException(str(e))
|
|
126
146
|
|
|
147
|
+
def is_guided(self, ctx: click.Context) -> bool:
|
|
148
|
+
"""True only when the user invoked this command with no input at all.
|
|
149
|
+
|
|
150
|
+
'No input' = no positional arg, no flag, no option value, no env-var
|
|
151
|
+
override. The single rule for every command: any user-provided signal
|
|
152
|
+
means scripting intent — skip prompts. Only the bare invocation gets
|
|
153
|
+
the guided/interactive flow.
|
|
154
|
+
|
|
155
|
+
Returns False whenever telemetry's `_agentic` heuristic fires
|
|
156
|
+
(CI=1, --agent-friendly, --non-interactive, AI tool env vars, etc.).
|
|
157
|
+
"""
|
|
158
|
+
if _agentic:
|
|
159
|
+
return False
|
|
160
|
+
for param in ctx.command.params:
|
|
161
|
+
if not param.name:
|
|
162
|
+
continue
|
|
163
|
+
src = ctx.get_parameter_source(param.name)
|
|
164
|
+
if src is not None and src.name in ("COMMANDLINE", "ENVIRONMENT"):
|
|
165
|
+
return False
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
def _tag_telemetry_start(self, ctx: click.Context) -> None:
|
|
169
|
+
"""Annotate the active Sentry transaction with command-level usage signal.
|
|
170
|
+
|
|
171
|
+
Sets the transaction name to the full Click command path
|
|
172
|
+
(e.g. 'deepctl debug audio' instead of just 'debug') and tags it with
|
|
173
|
+
the user-provided flag NAMES (never values), the requested output
|
|
174
|
+
format, and the auth method. All values are bounded enums or
|
|
175
|
+
already-public flag identifiers — no PII risk.
|
|
176
|
+
|
|
177
|
+
Wrapped in a bare except so a Sentry hiccup, missing scope, or unknown
|
|
178
|
+
Click parameter-source enum value can never crash the user's command.
|
|
179
|
+
"""
|
|
180
|
+
try:
|
|
181
|
+
import sentry_sdk
|
|
182
|
+
|
|
183
|
+
scope = sentry_sdk.get_current_scope()
|
|
184
|
+
transaction = getattr(scope, "transaction", None)
|
|
185
|
+
if transaction is not None and ctx.command_path:
|
|
186
|
+
transaction.name = ctx.command_path
|
|
187
|
+
|
|
188
|
+
used: list[str] = []
|
|
189
|
+
for param in ctx.command.params:
|
|
190
|
+
if not param.name:
|
|
191
|
+
continue
|
|
192
|
+
src = ctx.get_parameter_source(param.name)
|
|
193
|
+
if src is not None and src.name == "COMMANDLINE":
|
|
194
|
+
used.append(param.name)
|
|
195
|
+
scope.set_tag("cmd.flags", ",".join(sorted(used)) or "(none)")
|
|
196
|
+
scope.set_tag("cmd.output_format", ctx.params.get("output") or "default")
|
|
197
|
+
except Exception:
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
def _tag_telemetry_status(self, status: str) -> None:
|
|
201
|
+
"""Tag the active Sentry transaction with the command outcome.
|
|
202
|
+
|
|
203
|
+
Status is a bounded enum: 'ok', 'cancelled', 'error', or whatever the
|
|
204
|
+
command's BaseResult.status returned (also bounded by the result
|
|
205
|
+
model). Bare except so telemetry can't crash the command teardown.
|
|
206
|
+
"""
|
|
207
|
+
try:
|
|
208
|
+
import sentry_sdk
|
|
209
|
+
|
|
210
|
+
sentry_sdk.get_current_scope().set_tag("cmd.status", status)
|
|
211
|
+
except Exception:
|
|
212
|
+
pass
|
|
213
|
+
|
|
127
214
|
@abstractmethod
|
|
128
215
|
def handle(
|
|
129
216
|
self,
|
|
@@ -294,7 +381,7 @@ class BaseCommand(ABC):
|
|
|
294
381
|
Returns:
|
|
295
382
|
True if confirmed, False otherwise
|
|
296
383
|
"""
|
|
297
|
-
if _agentic or not self.ci_friendly:
|
|
384
|
+
if _agentic or not self.ci_friendly or not getattr(self, "_guided", True):
|
|
298
385
|
return default
|
|
299
386
|
|
|
300
387
|
try:
|
|
@@ -318,7 +405,9 @@ class BaseCommand(ABC):
|
|
|
318
405
|
Returns:
|
|
319
406
|
User input
|
|
320
407
|
"""
|
|
321
|
-
if (
|
|
408
|
+
if (
|
|
409
|
+
_agentic or not self.ci_friendly or not getattr(self, "_guided", True)
|
|
410
|
+
) and default is not None:
|
|
322
411
|
return default
|
|
323
412
|
|
|
324
413
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|